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 = true; 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 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 96 private FromHeader createFromHeader(SipProfile profile, String tag) 97 throws ParseException { 98 return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); 99 } 100 101 private ToHeader createToHeader(SipProfile profile) throws ParseException { 102 return createToHeader(profile, null); 103 } 104 105 private ToHeader createToHeader(SipProfile profile, String tag) 106 throws ParseException { 107 return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); 108 } 109 110 private CallIdHeader createCallIdHeader() { 111 return mSipProvider.getNewCallId(); 112 } 113 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 120 private MaxForwardsHeader createMaxForwardsHeader() 121 throws InvalidArgumentException { 122 return mHeaderFactory.createMaxForwardsHeader(70); 123 } 124 125 private MaxForwardsHeader createMaxForwardsHeader(int max) 126 throws InvalidArgumentException { 127 return mHeaderFactory.createMaxForwardsHeader(max); 128 } 129 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 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 154 private ContactHeader createContactHeader(SipProfile profile) 155 throws ParseException, SipException { 156 return createContactHeader(profile, null, 0); 157 } 158 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 174 private ContactHeader createWildcardContactHeader() { 175 ContactHeader contactHeader = mHeaderFactory.createContactHeader(); 176 contactHeader.setWildCard(); 177 return contactHeader; 178 } 179 180 private SipURI createSipUri(String username, String transport, 181 ListeningPoint lp) throws ParseException { 182 return createSipUri(username, transport, lp.getIPAddress(), lp.getPort()); 183 } 184 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 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 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 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 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 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 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 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 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 */ 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 */ 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 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 */ 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 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 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 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 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 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 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 521 public static String getCallId(Transaction transaction) { 522 return ((transaction != null) ? getCallId(transaction.getRequest()) 523 : ""); 524 } 525 526 private static String getCallId(Message message) { 527 CallIdHeader callIdHeader = 528 (CallIdHeader) message.getHeader(CallIdHeader.NAME); 529 return callIdHeader.getCallId(); 530 } 531 532 private static String getCallId(Dialog dialog) { 533 return dialog.getCallId().getCallId(); 534 } 535 } 536