/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.sip; import gov.nist.javax.sip.SipStackExt; import gov.nist.javax.sip.clientauthutils.AccountManager; import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; import gov.nist.javax.sip.header.extensions.ReferencesHeader; import gov.nist.javax.sip.header.extensions.ReferredByHeader; import gov.nist.javax.sip.header.extensions.ReplacesHeader; import android.net.sip.SipProfile; import android.telephony.Rlog; import java.text.ParseException; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import java.util.regex.Pattern; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.PeerUnavailableException; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.ServerTransaction; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.SipProvider; import javax.sip.SipStack; import javax.sip.Transaction; import javax.sip.TransactionTerminatedEvent; import javax.sip.TransactionState; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.FromHeader; import javax.sip.header.Header; import javax.sip.header.HeaderFactory; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Message; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; /** * Helper class for holding SIP stack related classes and for various low-level * SIP tasks like sending messages. */ class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); private static final boolean DBG = false; private static final boolean DBG_PING = false; private SipStack mSipStack; private SipProvider mSipProvider; private AddressFactory mAddressFactory; private HeaderFactory mHeaderFactory; private MessageFactory mMessageFactory; public SipHelper(SipStack sipStack, SipProvider sipProvider) throws PeerUnavailableException { mSipStack = sipStack; mSipProvider = sipProvider; SipFactory sipFactory = SipFactory.getInstance(); mAddressFactory = sipFactory.createAddressFactory(); mHeaderFactory = sipFactory.createHeaderFactory(); mMessageFactory = sipFactory.createMessageFactory(); } private FromHeader createFromHeader(SipProfile profile, String tag) throws ParseException { return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); } private ToHeader createToHeader(SipProfile profile) throws ParseException { return createToHeader(profile, null); } private ToHeader createToHeader(SipProfile profile, String tag) throws ParseException { return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); } private CallIdHeader createCallIdHeader() { return mSipProvider.getNewCallId(); } private CSeqHeader createCSeqHeader(String method) throws ParseException, InvalidArgumentException { long sequence = (long) (Math.random() * 10000); return mHeaderFactory.createCSeqHeader(sequence, method); } private MaxForwardsHeader createMaxForwardsHeader() throws InvalidArgumentException { return mHeaderFactory.createMaxForwardsHeader(70); } private MaxForwardsHeader createMaxForwardsHeader(int max) throws InvalidArgumentException { return mHeaderFactory.createMaxForwardsHeader(max); } private ListeningPoint getListeningPoint() throws SipException { ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); if (lp == null) { ListeningPoint[] lps = mSipProvider.getListeningPoints(); if ((lps != null) && (lps.length > 0)) lp = lps[0]; } if (lp == null) { throw new SipException("no listening point is available"); } return lp; } private List createViaHeaders() throws ParseException, SipException { List viaHeaders = new ArrayList(1); ListeningPoint lp = getListeningPoint(); ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeader.setRPort(); viaHeaders.add(viaHeader); return viaHeaders; } private ContactHeader createContactHeader(SipProfile profile) throws ParseException, SipException { return createContactHeader(profile, null, 0); } private ContactHeader createContactHeader(SipProfile profile, String ip, int port) throws ParseException, SipException { SipURI contactURI = (ip == null) ? createSipUri(profile.getUserName(), profile.getProtocol(), getListeningPoint()) : createSipUri(profile.getUserName(), profile.getProtocol(), ip, port); Address contactAddress = mAddressFactory.createAddress(contactURI); contactAddress.setDisplayName(profile.getDisplayName()); return mHeaderFactory.createContactHeader(contactAddress); } private ContactHeader createWildcardContactHeader() { ContactHeader contactHeader = mHeaderFactory.createContactHeader(); contactHeader.setWildCard(); return contactHeader; } private SipURI createSipUri(String username, String transport, ListeningPoint lp) throws ParseException { return createSipUri(username, transport, lp.getIPAddress(), lp.getPort()); } private SipURI createSipUri(String username, String transport, String ip, int port) throws ParseException { SipURI uri = mAddressFactory.createSipURI(username, ip); try { uri.setPort(port); uri.setTransportParam(transport); } catch (InvalidArgumentException e) { throw new RuntimeException(e); } return uri; } public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, String tag) throws SipException { try { Request request = (caller == callee) ? createRequest(Request.OPTIONS, caller, tag) : createRequest(Request.OPTIONS, caller, callee, tag); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); clientTransaction.sendRequest(); return clientTransaction; } catch (Exception e) { throw new SipException("sendOptions()", e); } } public ClientTransaction sendRegister(SipProfile userProfile, String tag, int expiry) throws SipException { try { Request request = createRequest(Request.REGISTER, userProfile, tag); if (expiry == 0) { // remove all previous registrations by wildcard // rfc3261#section-10.2.2 request.addHeader(createWildcardContactHeader()); } else { request.addHeader(createContactHeader(userProfile)); } request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); clientTransaction.sendRequest(); return clientTransaction; } catch (ParseException e) { throw new SipException("sendRegister()", e); } } private Request createRequest(String requestType, SipProfile userProfile, String tag) throws ParseException, SipException { FromHeader fromHeader = createFromHeader(userProfile, tag); ToHeader toHeader = createToHeader(userProfile); String replaceStr = Pattern.quote(userProfile.getUserName() + "@"); SipURI requestURI = mAddressFactory.createSipURI( userProfile.getUriString().replaceFirst(replaceStr, "")); List viaHeaders = createViaHeaders(); CallIdHeader callIdHeader = createCallIdHeader(); CSeqHeader cSeqHeader = createCSeqHeader(requestType); MaxForwardsHeader maxForwards = createMaxForwardsHeader(); Request request = mMessageFactory.createRequest(requestURI, requestType, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", "SIPAUA/0.1.001"); request.addHeader(userAgentHeader); return request; } public ClientTransaction handleChallenge(ResponseEvent responseEvent, AccountManager accountManager) throws SipException { AuthenticationHelper authenticationHelper = ((SipStackExt) mSipStack).getAuthenticationHelper( accountManager, mHeaderFactory); ClientTransaction tid = responseEvent.getClientTransaction(); ClientTransaction ct = authenticationHelper.handleChallenge( responseEvent.getResponse(), tid, mSipProvider, 5); if (DBG) log("send request with challenge response: " + ct.getRequest()); ct.sendRequest(); return ct; } private Request createRequest(String requestType, SipProfile caller, SipProfile callee, String tag) throws ParseException, SipException { FromHeader fromHeader = createFromHeader(caller, tag); ToHeader toHeader = createToHeader(callee); SipURI requestURI = callee.getUri(); List viaHeaders = createViaHeaders(); CallIdHeader callIdHeader = createCallIdHeader(); CSeqHeader cSeqHeader = createCSeqHeader(requestType); MaxForwardsHeader maxForwards = createMaxForwardsHeader(); Request request = mMessageFactory.createRequest(requestURI, requestType, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); request.addHeader(createContactHeader(caller)); return request; } public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag, ReferredByHeader referredBy, String replaces) throws SipException { try { Request request = createRequest(Request.INVITE, caller, callee, tag); if (referredBy != null) request.addHeader(referredBy); if (replaces != null) { request.addHeader(mHeaderFactory.createHeader( ReplacesHeader.NAME, replaces)); } request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); if (DBG) log("send INVITE: " + request); clientTransaction.sendRequest(); return clientTransaction; } catch (ParseException e) { throw new SipException("sendInvite()", e); } } public ClientTransaction sendReinvite(Dialog dialog, String sessionDescription) throws SipException { try { Request request = dialog.createRequest(Request.INVITE); request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); // Adding rport argument in the request could fix some SIP servers // in resolving the initiator's NAT port mapping for relaying the // response message from the other end. ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); if (viaHeader != null) viaHeader.setRPort(); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); if (DBG) log("send RE-INVITE: " + request); dialog.sendRequest(clientTransaction); return clientTransaction; } catch (ParseException e) { throw new SipException("sendReinvite()", e); } } public ServerTransaction getServerTransaction(RequestEvent event) throws SipException { ServerTransaction transaction = event.getServerTransaction(); if (transaction == null) { Request request = event.getRequest(); return mSipProvider.getNewServerTransaction(request); } else { return transaction; } } /** * @param event the INVITE request event */ public ServerTransaction sendRinging(RequestEvent event, String tag) throws SipException { try { Request request = event.getRequest(); ServerTransaction transaction = getServerTransaction(event); Response response = mMessageFactory.createResponse(Response.RINGING, request); ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); toHeader.setTag(tag); response.addHeader(toHeader); if (DBG) log("send RINGING: " + response); transaction.sendResponse(response); return transaction; } catch (ParseException e) { throw new SipException("sendRinging()", e); } } /** * @param event the INVITE request event */ public ServerTransaction sendInviteOk(RequestEvent event, SipProfile localProfile, String sessionDescription, ServerTransaction inviteTransaction, String externalIp, int externalPort) throws SipException { try { Request request = event.getRequest(); Response response = mMessageFactory.createResponse(Response.OK, request); response.addHeader(createContactHeader(localProfile, externalIp, externalPort)); response.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); if (inviteTransaction == null) { inviteTransaction = getServerTransaction(event); } if (inviteTransaction.getState() != TransactionState.COMPLETED) { if (DBG) log("send OK: " + response); inviteTransaction.sendResponse(response); } return inviteTransaction; } catch (ParseException e) { throw new SipException("sendInviteOk()", e); } } public void sendInviteBusyHere(RequestEvent event, ServerTransaction inviteTransaction) throws SipException { try { Request request = event.getRequest(); Response response = mMessageFactory.createResponse( Response.BUSY_HERE, request); if (inviteTransaction == null) { inviteTransaction = getServerTransaction(event); } if (inviteTransaction.getState() != TransactionState.COMPLETED) { if (DBG) log("send BUSY HERE: " + response); inviteTransaction.sendResponse(response); } } catch (ParseException e) { throw new SipException("sendInviteBusyHere()", e); } } /** * @param event the INVITE ACK request event */ public void sendInviteAck(ResponseEvent event, Dialog dialog) throws SipException { Response response = event.getResponse(); long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) .getSeqNumber(); Request ack = dialog.createAck(cseq); if (DBG) log("send ACK: " + ack); dialog.sendAck(ack); } public void sendBye(Dialog dialog) throws SipException { Request byeRequest = dialog.createRequest(Request.BYE); if (DBG) log("send BYE: " + byeRequest); dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); } public void sendCancel(ClientTransaction inviteTransaction) throws SipException { Request cancelRequest = inviteTransaction.createCancel(); if (DBG) log("send CANCEL: " + cancelRequest); mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); } public void sendResponse(RequestEvent event, int responseCode) throws SipException { try { Request request = event.getRequest(); Response response = mMessageFactory.createResponse( responseCode, request); if (DBG && (!Request.OPTIONS.equals(request.getMethod()) || DBG_PING)) { log("send response: " + response); } getServerTransaction(event).sendResponse(response); } catch (ParseException e) { throw new SipException("sendResponse()", e); } } public void sendReferNotify(Dialog dialog, String content) throws SipException { try { Request request = dialog.createRequest(Request.NOTIFY); request.addHeader(mHeaderFactory.createSubscriptionStateHeader( "active;expires=60")); // set content here request.setContent(content, mHeaderFactory.createContentTypeHeader( "message", "sipfrag")); request.addHeader(mHeaderFactory.createEventHeader( ReferencesHeader.REFER)); if (DBG) log("send NOTIFY: " + request); dialog.sendRequest(mSipProvider.getNewClientTransaction(request)); } catch (ParseException e) { throw new SipException("sendReferNotify()", e); } } public void sendInviteRequestTerminated(Request inviteRequest, ServerTransaction inviteTransaction) throws SipException { try { Response response = mMessageFactory.createResponse( Response.REQUEST_TERMINATED, inviteRequest); if (DBG) log("send response: " + response); inviteTransaction.sendResponse(response); } catch (ParseException e) { throw new SipException("sendInviteRequestTerminated()", e); } } public static String getCallId(EventObject event) { if (event == null) return null; if (event instanceof RequestEvent) { return getCallId(((RequestEvent) event).getRequest()); } else if (event instanceof ResponseEvent) { return getCallId(((ResponseEvent) event).getResponse()); } else if (event instanceof DialogTerminatedEvent) { Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); return getCallId(((DialogTerminatedEvent) event).getDialog()); } else if (event instanceof TransactionTerminatedEvent) { TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; return getCallId(e.isServerTransaction() ? e.getServerTransaction() : e.getClientTransaction()); } else { Object source = event.getSource(); if (source instanceof Transaction) { return getCallId(((Transaction) source)); } else if (source instanceof Dialog) { return getCallId((Dialog) source); } } return ""; } public static String getCallId(Transaction transaction) { return ((transaction != null) ? getCallId(transaction.getRequest()) : ""); } private static String getCallId(Message message) { CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); return callIdHeader.getCallId(); } private static String getCallId(Dialog dialog) { return dialog.getCallId().getCallId(); } private void log(String s) { Rlog.d(TAG, s); } }