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 
SipHelper(SipStack sipStack, SipProvider sipProvider)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 
createFromHeader(SipProfile profile, String tag)94     private FromHeader createFromHeader(SipProfile profile, String tag)
95             throws ParseException {
96         return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
97     }
98 
createToHeader(SipProfile profile)99     private ToHeader createToHeader(SipProfile profile) throws ParseException {
100         return createToHeader(profile, null);
101     }
102 
createToHeader(SipProfile profile, String tag)103     private ToHeader createToHeader(SipProfile profile, String tag)
104             throws ParseException {
105         return mHeaderFactory.createToHeader(profile.getSipAddress(), tag);
106     }
107 
createCallIdHeader()108     private CallIdHeader createCallIdHeader() {
109         return mSipProvider.getNewCallId();
110     }
111 
createCSeqHeader(String method)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 
createMaxForwardsHeader()118     private MaxForwardsHeader createMaxForwardsHeader()
119             throws InvalidArgumentException {
120         return mHeaderFactory.createMaxForwardsHeader(70);
121     }
122 
createMaxForwardsHeader(int max)123     private MaxForwardsHeader createMaxForwardsHeader(int max)
124             throws InvalidArgumentException {
125         return mHeaderFactory.createMaxForwardsHeader(max);
126     }
127 
getListeningPoint()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 
createViaHeaders()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 
createContactHeader(SipProfile profile)152     private ContactHeader createContactHeader(SipProfile profile)
153             throws ParseException, SipException {
154         return createContactHeader(profile, null, 0);
155     }
156 
createContactHeader(SipProfile profile, String ip, int port)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 
createWildcardContactHeader()172     private ContactHeader createWildcardContactHeader() {
173         ContactHeader contactHeader  = mHeaderFactory.createContactHeader();
174         contactHeader.setWildCard();
175         return contactHeader;
176     }
177 
createSipUri(String username, String transport, ListeningPoint lp)178     private SipURI createSipUri(String username, String transport,
179             ListeningPoint lp) throws ParseException {
180         return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
181     }
182 
createSipUri(String username, String transport, String ip, int port)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 
sendOptions(SipProfile caller, SipProfile callee, String tag)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 
sendRegister(SipProfile userProfile, String tag, int expiry)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 
createRequest(String requestType, SipProfile userProfile, String tag)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 
handleChallenge(ResponseEvent responseEvent, AccountManager accountManager)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 
createRequest(String requestType, SipProfile caller, SipProfile callee, String tag)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 
sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag, ReferredByHeader referredBy, String replaces)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 
sendReinvite(Dialog dialog, String sessionDescription)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 
getServerTransaction(RequestEvent event)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      */
sendRinging(RequestEvent event, String tag)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      */
sendInviteOk(RequestEvent event, SipProfile localProfile, String sessionDescription, ServerTransaction inviteTransaction, String externalIp, int externalPort)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 
sendInviteBusyHere(RequestEvent event, ServerTransaction inviteTransaction)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      */
sendInviteAck(ResponseEvent event, Dialog dialog)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 
sendBye(Dialog dialog)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 
sendCancel(ClientTransaction inviteTransaction)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 
sendResponse(RequestEvent event, int responseCode)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 
sendReferNotify(Dialog dialog, String content)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 
sendInviteRequestTerminated(Request inviteRequest, ServerTransaction inviteTransaction)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 
getCallId(EventObject event)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 
getCallId(Transaction transaction)519     public static String getCallId(Transaction transaction) {
520         return ((transaction != null) ? getCallId(transaction.getRequest())
521                                       : "");
522     }
523 
getCallId(Message message)524     private static String getCallId(Message message) {
525         CallIdHeader callIdHeader =
526                 (CallIdHeader) message.getHeader(CallIdHeader.NAME);
527         return callIdHeader.getCallId();
528     }
529 
getCallId(Dialog dialog)530     private static String getCallId(Dialog dialog) {
531         return dialog.getCallId().getCallId();
532     }
533 
log(String s)534     private void log(String s) {
535         Rlog.d(TAG, s);
536     }
537 }
538