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