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.telephony.Rlog;
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.TransactionTerminatedEvent;
50import javax.sip.TransactionState;
51import javax.sip.address.Address;
52import javax.sip.address.AddressFactory;
53import javax.sip.address.SipURI;
54import javax.sip.header.CSeqHeader;
55import javax.sip.header.CallIdHeader;
56import javax.sip.header.ContactHeader;
57import javax.sip.header.FromHeader;
58import javax.sip.header.Header;
59import javax.sip.header.HeaderFactory;
60import javax.sip.header.MaxForwardsHeader;
61import javax.sip.header.ToHeader;
62import javax.sip.header.ViaHeader;
63import javax.sip.message.Message;
64import javax.sip.message.MessageFactory;
65import javax.sip.message.Request;
66import 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 */
72class 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