Home | History | Annotate | Download | only in sip
      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