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
17package com.android.server.sip;
18
19import gov.nist.javax.sip.SipStackExt;
20import gov.nist.javax.sip.clientauthutils.AccountManager;
21import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
22import gov.nist.javax.sip.header.extensions.ReferencesHeader;
23import gov.nist.javax.sip.header.extensions.ReferredByHeader;
24import gov.nist.javax.sip.header.extensions.ReplacesHeader;
25
26import android.net.sip.SipProfile;
27import android.util.Log;
28
29import java.text.ParseException;
30import java.util.ArrayList;
31import java.util.EventObject;
32import java.util.List;
33import java.util.regex.Pattern;
34
35import javax.sip.ClientTransaction;
36import javax.sip.Dialog;
37import javax.sip.DialogTerminatedEvent;
38import javax.sip.InvalidArgumentException;
39import javax.sip.ListeningPoint;
40import javax.sip.PeerUnavailableException;
41import javax.sip.RequestEvent;
42import javax.sip.ResponseEvent;
43import javax.sip.ServerTransaction;
44import javax.sip.SipException;
45import javax.sip.SipFactory;
46import javax.sip.SipProvider;
47import javax.sip.SipStack;
48import javax.sip.Transaction;
49import javax.sip.TransactionAlreadyExistsException;
50import javax.sip.TransactionTerminatedEvent;
51import javax.sip.TransactionUnavailableException;
52import javax.sip.TransactionState;
53import javax.sip.address.Address;
54import javax.sip.address.AddressFactory;
55import javax.sip.address.SipURI;
56import javax.sip.header.CSeqHeader;
57import javax.sip.header.CallIdHeader;
58import javax.sip.header.ContactHeader;
59import javax.sip.header.FromHeader;
60import javax.sip.header.Header;
61import javax.sip.header.HeaderFactory;
62import javax.sip.header.MaxForwardsHeader;
63import javax.sip.header.ToHeader;
64import javax.sip.header.ViaHeader;
65import javax.sip.message.Message;
66import javax.sip.message.MessageFactory;
67import javax.sip.message.Request;
68import 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 */
74class SipHelper {
75    private static final String TAG = SipHelper.class.getSimpleName();
76    private static final boolean DEBUG = false;
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