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.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