1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.sip; 18 19 import gov.nist.javax.sip.SipStackExt; 20 import gov.nist.javax.sip.clientauthutils.AccountManager; 21 import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; 22 import gov.nist.javax.sip.header.extensions.ReferencesHeader; 23 import gov.nist.javax.sip.header.extensions.ReferredByHeader; 24 import gov.nist.javax.sip.header.extensions.ReplacesHeader; 25 26 import android.net.sip.SipProfile; 27 import android.util.Log; 28 29 import java.text.ParseException; 30 import java.util.ArrayList; 31 import java.util.EventObject; 32 import java.util.List; 33 import java.util.regex.Pattern; 34 35 import javax.sip.ClientTransaction; 36 import javax.sip.Dialog; 37 import javax.sip.DialogTerminatedEvent; 38 import javax.sip.InvalidArgumentException; 39 import javax.sip.ListeningPoint; 40 import javax.sip.PeerUnavailableException; 41 import javax.sip.RequestEvent; 42 import javax.sip.ResponseEvent; 43 import javax.sip.ServerTransaction; 44 import javax.sip.SipException; 45 import javax.sip.SipFactory; 46 import javax.sip.SipProvider; 47 import javax.sip.SipStack; 48 import javax.sip.Transaction; 49 import javax.sip.TransactionAlreadyExistsException; 50 import javax.sip.TransactionTerminatedEvent; 51 import javax.sip.TransactionUnavailableException; 52 import javax.sip.TransactionState; 53 import javax.sip.address.Address; 54 import javax.sip.address.AddressFactory; 55 import javax.sip.address.SipURI; 56 import javax.sip.header.CSeqHeader; 57 import javax.sip.header.CallIdHeader; 58 import javax.sip.header.ContactHeader; 59 import javax.sip.header.FromHeader; 60 import javax.sip.header.Header; 61 import javax.sip.header.HeaderFactory; 62 import javax.sip.header.MaxForwardsHeader; 63 import javax.sip.header.ToHeader; 64 import javax.sip.header.ViaHeader; 65 import javax.sip.message.Message; 66 import javax.sip.message.MessageFactory; 67 import javax.sip.message.Request; 68 import javax.sip.message.Response; 69 70 /** 71 * Helper class for holding SIP stack related classes and for various low-level 72 * SIP tasks like sending messages. 73 */ 74 class SipHelper { 75 private static final String TAG = SipHelper.class.getSimpleName(); 76 private static final boolean DEBUG = false; 77 private static final boolean DEBUG_PING = false; 78 79 private SipStack mSipStack; 80 private SipProvider mSipProvider; 81 private AddressFactory mAddressFactory; 82 private HeaderFactory mHeaderFactory; 83 private MessageFactory mMessageFactory; 84 SipHelper(SipStack sipStack, SipProvider sipProvider)85 public SipHelper(SipStack sipStack, SipProvider sipProvider) 86 throws PeerUnavailableException { 87 mSipStack = sipStack; 88 mSipProvider = sipProvider; 89 90 SipFactory sipFactory = SipFactory.getInstance(); 91 mAddressFactory = sipFactory.createAddressFactory(); 92 mHeaderFactory = sipFactory.createHeaderFactory(); 93 mMessageFactory = sipFactory.createMessageFactory(); 94 } 95 createFromHeader(SipProfile profile, String tag)96 private FromHeader createFromHeader(SipProfile profile, String tag) 97 throws ParseException { 98 return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); 99 } 100 createToHeader(SipProfile profile)101 private ToHeader createToHeader(SipProfile profile) throws ParseException { 102 return createToHeader(profile, null); 103 } 104 createToHeader(SipProfile profile, String tag)105 private ToHeader createToHeader(SipProfile profile, String tag) 106 throws ParseException { 107 return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); 108 } 109 createCallIdHeader()110 private CallIdHeader createCallIdHeader() { 111 return mSipProvider.getNewCallId(); 112 } 113 createCSeqHeader(String method)114 private CSeqHeader createCSeqHeader(String method) 115 throws ParseException, InvalidArgumentException { 116 long sequence = (long) (Math.random() * 10000); 117 return mHeaderFactory.createCSeqHeader(sequence, method); 118 } 119 createMaxForwardsHeader()120 private MaxForwardsHeader createMaxForwardsHeader() 121 throws InvalidArgumentException { 122 return mHeaderFactory.createMaxForwardsHeader(70); 123 } 124 createMaxForwardsHeader(int max)125 private MaxForwardsHeader createMaxForwardsHeader(int max) 126 throws InvalidArgumentException { 127 return mHeaderFactory.createMaxForwardsHeader(max); 128 } 129 getListeningPoint()130 private ListeningPoint getListeningPoint() throws SipException { 131 ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); 132 if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); 133 if (lp == null) { 134 ListeningPoint[] lps = mSipProvider.getListeningPoints(); 135 if ((lps != null) && (lps.length > 0)) lp = lps[0]; 136 } 137 if (lp == null) { 138 throw new SipException("no listening point is available"); 139 } 140 return lp; 141 } 142 createViaHeaders()143 private List<ViaHeader> createViaHeaders() 144 throws ParseException, SipException { 145 List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); 146 ListeningPoint lp = getListeningPoint(); 147 ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), 148 lp.getPort(), lp.getTransport(), null); 149 viaHeader.setRPort(); 150 viaHeaders.add(viaHeader); 151 return viaHeaders; 152 } 153 createContactHeader(SipProfile profile)154 private ContactHeader createContactHeader(SipProfile profile) 155 throws ParseException, SipException { 156 return createContactHeader(profile, null, 0); 157 } 158 createContactHeader(SipProfile profile, String ip, int port)159 private ContactHeader createContactHeader(SipProfile profile, 160 String ip, int port) throws ParseException, 161 SipException { 162 SipURI contactURI = (ip == null) 163 ? createSipUri(profile.getUserName(), profile.getProtocol(), 164 getListeningPoint()) 165 : createSipUri(profile.getUserName(), profile.getProtocol(), 166 ip, port); 167 168 Address contactAddress = mAddressFactory.createAddress(contactURI); 169 contactAddress.setDisplayName(profile.getDisplayName()); 170 171 return mHeaderFactory.createContactHeader(contactAddress); 172 } 173 createWildcardContactHeader()174 private ContactHeader createWildcardContactHeader() { 175 ContactHeader contactHeader = mHeaderFactory.createContactHeader(); 176 contactHeader.setWildCard(); 177 return contactHeader; 178 } 179 createSipUri(String username, String transport, ListeningPoint lp)180 private SipURI createSipUri(String username, String transport, 181 ListeningPoint lp) throws ParseException { 182 return createSipUri(username, transport, lp.getIPAddress(), lp.getPort()); 183 } 184 createSipUri(String username, String transport, String ip, int port)185 private SipURI createSipUri(String username, String transport, 186 String ip, int port) throws ParseException { 187 SipURI uri = mAddressFactory.createSipURI(username, ip); 188 try { 189 uri.setPort(port); 190 uri.setTransportParam(transport); 191 } catch (InvalidArgumentException e) { 192 throw new RuntimeException(e); 193 } 194 return uri; 195 } 196 sendOptions(SipProfile caller, SipProfile callee, String tag)197 public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, 198 String tag) throws SipException { 199 try { 200 Request request = (caller == callee) 201 ? createRequest(Request.OPTIONS, caller, tag) 202 : createRequest(Request.OPTIONS, caller, callee, tag); 203 204 ClientTransaction clientTransaction = 205 mSipProvider.getNewClientTransaction(request); 206 clientTransaction.sendRequest(); 207 return clientTransaction; 208 } catch (Exception e) { 209 throw new SipException("sendOptions()", e); 210 } 211 } 212 sendRegister(SipProfile userProfile, String tag, int expiry)213 public ClientTransaction sendRegister(SipProfile userProfile, String tag, 214 int expiry) throws SipException { 215 try { 216 Request request = createRequest(Request.REGISTER, userProfile, tag); 217 if (expiry == 0) { 218 // remove all previous registrations by wildcard 219 // rfc3261#section-10.2.2 220 request.addHeader(createWildcardContactHeader()); 221 } else { 222 request.addHeader(createContactHeader(userProfile)); 223 } 224 request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); 225 226 ClientTransaction clientTransaction = 227 mSipProvider.getNewClientTransaction(request); 228 clientTransaction.sendRequest(); 229 return clientTransaction; 230 } catch (ParseException e) { 231 throw new SipException("sendRegister()", e); 232 } 233 } 234 createRequest(String requestType, SipProfile userProfile, String tag)235 private Request createRequest(String requestType, SipProfile userProfile, 236 String tag) throws ParseException, SipException { 237 FromHeader fromHeader = createFromHeader(userProfile, tag); 238 ToHeader toHeader = createToHeader(userProfile); 239 240 String replaceStr = Pattern.quote(userProfile.getUserName() + "@"); 241 SipURI requestURI = mAddressFactory.createSipURI( 242 userProfile.getUriString().replaceFirst(replaceStr, "")); 243 244 List<ViaHeader> viaHeaders = createViaHeaders(); 245 CallIdHeader callIdHeader = createCallIdHeader(); 246 CSeqHeader cSeqHeader = createCSeqHeader(requestType); 247 MaxForwardsHeader maxForwards = createMaxForwardsHeader(); 248 Request request = mMessageFactory.createRequest(requestURI, 249 requestType, callIdHeader, cSeqHeader, fromHeader, 250 toHeader, viaHeaders, maxForwards); 251 Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", 252 "SIPAUA/0.1.001"); 253 request.addHeader(userAgentHeader); 254 return request; 255 } 256 handleChallenge(ResponseEvent responseEvent, AccountManager accountManager)257 public ClientTransaction handleChallenge(ResponseEvent responseEvent, 258 AccountManager accountManager) throws SipException { 259 AuthenticationHelper authenticationHelper = 260 ((SipStackExt) mSipStack).getAuthenticationHelper( 261 accountManager, mHeaderFactory); 262 ClientTransaction tid = responseEvent.getClientTransaction(); 263 ClientTransaction ct = authenticationHelper.handleChallenge( 264 responseEvent.getResponse(), tid, mSipProvider, 5); 265 if (DEBUG) Log.d(TAG, "send request with challenge response: " 266 + ct.getRequest()); 267 ct.sendRequest(); 268 return ct; 269 } 270 createRequest(String requestType, SipProfile caller, SipProfile callee, String tag)271 private Request createRequest(String requestType, SipProfile caller, 272 SipProfile callee, String tag) throws ParseException, SipException { 273 FromHeader fromHeader = createFromHeader(caller, tag); 274 ToHeader toHeader = createToHeader(callee); 275 SipURI requestURI = callee.getUri(); 276 List<ViaHeader> viaHeaders = createViaHeaders(); 277 CallIdHeader callIdHeader = createCallIdHeader(); 278 CSeqHeader cSeqHeader = createCSeqHeader(requestType); 279 MaxForwardsHeader maxForwards = createMaxForwardsHeader(); 280 281 Request request = mMessageFactory.createRequest(requestURI, 282 requestType, callIdHeader, cSeqHeader, fromHeader, 283 toHeader, viaHeaders, maxForwards); 284 285 request.addHeader(createContactHeader(caller)); 286 return request; 287 } 288 sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag, ReferredByHeader referredBy, String replaces)289 public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, 290 String sessionDescription, String tag, ReferredByHeader referredBy, 291 String replaces) throws SipException { 292 try { 293 Request request = createRequest(Request.INVITE, caller, callee, tag); 294 if (referredBy != null) request.addHeader(referredBy); 295 if (replaces != null) { 296 request.addHeader(mHeaderFactory.createHeader( 297 ReplacesHeader.NAME, replaces)); 298 } 299 request.setContent(sessionDescription, 300 mHeaderFactory.createContentTypeHeader( 301 "application", "sdp")); 302 ClientTransaction clientTransaction = 303 mSipProvider.getNewClientTransaction(request); 304 if (DEBUG) Log.d(TAG, "send INVITE: " + request); 305 clientTransaction.sendRequest(); 306 return clientTransaction; 307 } catch (ParseException e) { 308 throw new SipException("sendInvite()", e); 309 } 310 } 311 sendReinvite(Dialog dialog, String sessionDescription)312 public ClientTransaction sendReinvite(Dialog dialog, 313 String sessionDescription) throws SipException { 314 try { 315 Request request = dialog.createRequest(Request.INVITE); 316 request.setContent(sessionDescription, 317 mHeaderFactory.createContentTypeHeader( 318 "application", "sdp")); 319 320 // Adding rport argument in the request could fix some SIP servers 321 // in resolving the initiator's NAT port mapping for relaying the 322 // response message from the other end. 323 324 ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); 325 if (viaHeader != null) viaHeader.setRPort(); 326 327 ClientTransaction clientTransaction = 328 mSipProvider.getNewClientTransaction(request); 329 if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request); 330 dialog.sendRequest(clientTransaction); 331 return clientTransaction; 332 } catch (ParseException e) { 333 throw new SipException("sendReinvite()", e); 334 } 335 } 336 getServerTransaction(RequestEvent event)337 public ServerTransaction getServerTransaction(RequestEvent event) 338 throws SipException { 339 ServerTransaction transaction = event.getServerTransaction(); 340 if (transaction == null) { 341 Request request = event.getRequest(); 342 return mSipProvider.getNewServerTransaction(request); 343 } else { 344 return transaction; 345 } 346 } 347 348 /** 349 * @param event the INVITE request event 350 */ sendRinging(RequestEvent event, String tag)351 public ServerTransaction sendRinging(RequestEvent event, String tag) 352 throws SipException { 353 try { 354 Request request = event.getRequest(); 355 ServerTransaction transaction = getServerTransaction(event); 356 357 Response response = mMessageFactory.createResponse(Response.RINGING, 358 request); 359 360 ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); 361 toHeader.setTag(tag); 362 response.addHeader(toHeader); 363 if (DEBUG) Log.d(TAG, "send RINGING: " + response); 364 transaction.sendResponse(response); 365 return transaction; 366 } catch (ParseException e) { 367 throw new SipException("sendRinging()", e); 368 } 369 } 370 371 /** 372 * @param event the INVITE request event 373 */ sendInviteOk(RequestEvent event, SipProfile localProfile, String sessionDescription, ServerTransaction inviteTransaction, String externalIp, int externalPort)374 public ServerTransaction sendInviteOk(RequestEvent event, 375 SipProfile localProfile, String sessionDescription, 376 ServerTransaction inviteTransaction, String externalIp, 377 int externalPort) throws SipException { 378 try { 379 Request request = event.getRequest(); 380 Response response = mMessageFactory.createResponse(Response.OK, 381 request); 382 response.addHeader(createContactHeader(localProfile, externalIp, 383 externalPort)); 384 response.setContent(sessionDescription, 385 mHeaderFactory.createContentTypeHeader( 386 "application", "sdp")); 387 388 if (inviteTransaction == null) { 389 inviteTransaction = getServerTransaction(event); 390 } 391 392 if (inviteTransaction.getState() != TransactionState.COMPLETED) { 393 if (DEBUG) Log.d(TAG, "send OK: " + response); 394 inviteTransaction.sendResponse(response); 395 } 396 397 return inviteTransaction; 398 } catch (ParseException e) { 399 throw new SipException("sendInviteOk()", e); 400 } 401 } 402 sendInviteBusyHere(RequestEvent event, ServerTransaction inviteTransaction)403 public void sendInviteBusyHere(RequestEvent event, 404 ServerTransaction inviteTransaction) throws SipException { 405 try { 406 Request request = event.getRequest(); 407 Response response = mMessageFactory.createResponse( 408 Response.BUSY_HERE, request); 409 410 if (inviteTransaction == null) { 411 inviteTransaction = getServerTransaction(event); 412 } 413 414 if (inviteTransaction.getState() != TransactionState.COMPLETED) { 415 if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response); 416 inviteTransaction.sendResponse(response); 417 } 418 } catch (ParseException e) { 419 throw new SipException("sendInviteBusyHere()", e); 420 } 421 } 422 423 /** 424 * @param event the INVITE ACK request event 425 */ sendInviteAck(ResponseEvent event, Dialog dialog)426 public void sendInviteAck(ResponseEvent event, Dialog dialog) 427 throws SipException { 428 Response response = event.getResponse(); 429 long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) 430 .getSeqNumber(); 431 Request ack = dialog.createAck(cseq); 432 if (DEBUG) Log.d(TAG, "send ACK: " + ack); 433 dialog.sendAck(ack); 434 } 435 sendBye(Dialog dialog)436 public void sendBye(Dialog dialog) throws SipException { 437 Request byeRequest = dialog.createRequest(Request.BYE); 438 if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest); 439 dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); 440 } 441 sendCancel(ClientTransaction inviteTransaction)442 public void sendCancel(ClientTransaction inviteTransaction) 443 throws SipException { 444 Request cancelRequest = inviteTransaction.createCancel(); 445 if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest); 446 mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); 447 } 448 sendResponse(RequestEvent event, int responseCode)449 public void sendResponse(RequestEvent event, int responseCode) 450 throws SipException { 451 try { 452 Request request = event.getRequest(); 453 Response response = mMessageFactory.createResponse( 454 responseCode, request); 455 if (DEBUG && (!Request.OPTIONS.equals(request.getMethod()) 456 || DEBUG_PING)) { 457 Log.d(TAG, "send response: " + response); 458 } 459 getServerTransaction(event).sendResponse(response); 460 } catch (ParseException e) { 461 throw new SipException("sendResponse()", e); 462 } 463 } 464 sendReferNotify(Dialog dialog, String content)465 public void sendReferNotify(Dialog dialog, String content) 466 throws SipException { 467 try { 468 Request request = dialog.createRequest(Request.NOTIFY); 469 request.addHeader(mHeaderFactory.createSubscriptionStateHeader( 470 "active;expires=60")); 471 // set content here 472 request.setContent(content, 473 mHeaderFactory.createContentTypeHeader( 474 "message", "sipfrag")); 475 request.addHeader(mHeaderFactory.createEventHeader( 476 ReferencesHeader.REFER)); 477 if (DEBUG) Log.d(TAG, "send NOTIFY: " + request); 478 dialog.sendRequest(mSipProvider.getNewClientTransaction(request)); 479 } catch (ParseException e) { 480 throw new SipException("sendReferNotify()", e); 481 } 482 } 483 sendInviteRequestTerminated(Request inviteRequest, ServerTransaction inviteTransaction)484 public void sendInviteRequestTerminated(Request inviteRequest, 485 ServerTransaction inviteTransaction) throws SipException { 486 try { 487 Response response = mMessageFactory.createResponse( 488 Response.REQUEST_TERMINATED, inviteRequest); 489 if (DEBUG) Log.d(TAG, "send response: " + response); 490 inviteTransaction.sendResponse(response); 491 } catch (ParseException e) { 492 throw new SipException("sendInviteRequestTerminated()", e); 493 } 494 } 495 getCallId(EventObject event)496 public static String getCallId(EventObject event) { 497 if (event == null) return null; 498 if (event instanceof RequestEvent) { 499 return getCallId(((RequestEvent) event).getRequest()); 500 } else if (event instanceof ResponseEvent) { 501 return getCallId(((ResponseEvent) event).getResponse()); 502 } else if (event instanceof DialogTerminatedEvent) { 503 Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); 504 return getCallId(((DialogTerminatedEvent) event).getDialog()); 505 } else if (event instanceof TransactionTerminatedEvent) { 506 TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; 507 return getCallId(e.isServerTransaction() 508 ? e.getServerTransaction() 509 : e.getClientTransaction()); 510 } else { 511 Object source = event.getSource(); 512 if (source instanceof Transaction) { 513 return getCallId(((Transaction) source)); 514 } else if (source instanceof Dialog) { 515 return getCallId((Dialog) source); 516 } 517 } 518 return ""; 519 } 520 getCallId(Transaction transaction)521 public static String getCallId(Transaction transaction) { 522 return ((transaction != null) ? getCallId(transaction.getRequest()) 523 : ""); 524 } 525 getCallId(Message message)526 private static String getCallId(Message message) { 527 CallIdHeader callIdHeader = 528 (CallIdHeader) message.getHeader(CallIdHeader.NAME); 529 return callIdHeader.getCallId(); 530 } 531 getCallId(Dialog dialog)532 private static String getCallId(Dialog dialog) { 533 return dialog.getCallId().getCallId(); 534 } 535 } 536