/*
* 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<ViaHeader> createViaHeaders()
throws ParseException, SipException {
List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(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<ViaHeader> 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<ViaHeader> 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);
}
}