diff options
Diffstat (limited to 'services/java/com/android/server/sip/SipSessionGroup.java')
-rw-r--r-- | services/java/com/android/server/sip/SipSessionGroup.java | 1393 |
1 files changed, 0 insertions, 1393 deletions
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java deleted file mode 100644 index 91677a2..0000000 --- a/services/java/com/android/server/sip/SipSessionGroup.java +++ /dev/null @@ -1,1393 +0,0 @@ -/* - * 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.clientauthutils.AccountManager; -import gov.nist.javax.sip.clientauthutils.UserCredentials; -import gov.nist.javax.sip.header.SIPHeaderNames; -import gov.nist.javax.sip.header.ProxyAuthenticate; -import gov.nist.javax.sip.header.WWWAuthenticate; -import gov.nist.javax.sip.message.SIPMessage; - -import android.net.sip.ISipSession; -import android.net.sip.ISipSessionListener; -import android.net.sip.SipErrorCode; -import android.net.sip.SipProfile; -import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.DatagramSocket; -import java.net.UnknownHostException; -import java.text.ParseException; -import java.util.Collection; -import java.util.EventObject; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.TooManyListenersException; - -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.ServerTransaction; -import javax.sip.SipException; -import javax.sip.SipFactory; -import javax.sip.SipListener; -import javax.sip.SipProvider; -import javax.sip.SipStack; -import javax.sip.TimeoutEvent; -import javax.sip.Transaction; -import javax.sip.TransactionState; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; -import javax.sip.address.Address; -import javax.sip.address.SipURI; -import javax.sip.header.CSeqHeader; -import javax.sip.header.ExpiresHeader; -import javax.sip.header.FromHeader; -import javax.sip.header.MinExpiresHeader; -import javax.sip.header.ViaHeader; -import javax.sip.message.Message; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/** - * Manages {@link ISipSession}'s for a SIP account. - */ -class SipSessionGroup implements SipListener { - private static final String TAG = "SipSession"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_PING = DEBUG && false; - private static final String ANONYMOUS = "anonymous"; - private static final String SERVER_ERROR_PREFIX = "Response: "; - private static final int EXPIRY_TIME = 3600; // in seconds - private static final int CANCEL_CALL_TIMER = 3; // in seconds - - private static final EventObject DEREGISTER = new EventObject("Deregister"); - private static final EventObject END_CALL = new EventObject("End call"); - private static final EventObject HOLD_CALL = new EventObject("Hold call"); - private static final EventObject CONTINUE_CALL - = new EventObject("Continue call"); - - private final SipProfile mLocalProfile; - private final String mPassword; - - private SipStack mSipStack; - private SipHelper mSipHelper; - private String mLastNonce; - private int mRPort; - - // session that processes INVITE requests - private SipSessionImpl mCallReceiverSession; - private String mLocalIp; - - // call-id-to-SipSession map - private Map<String, SipSessionImpl> mSessionMap = - new HashMap<String, SipSessionImpl>(); - - /** - * @param myself the local profile with password crossed out - * @param password the password of the profile - * @throws IOException if cannot assign requested address - */ - public SipSessionGroup(String localIp, SipProfile myself, String password) - throws SipException, IOException { - mLocalProfile = myself; - mPassword = password; - reset(localIp); - } - - synchronized void reset(String localIp) throws SipException, IOException { - mLocalIp = localIp; - if (localIp == null) return; - - SipProfile myself = mLocalProfile; - SipFactory sipFactory = SipFactory.getInstance(); - Properties properties = new Properties(); - properties.setProperty("javax.sip.STACK_NAME", getStackName()); - String outboundProxy = myself.getProxyAddress(); - if (!TextUtils.isEmpty(outboundProxy)) { - Log.v(TAG, "outboundProxy is " + outboundProxy); - properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy - + ":" + myself.getPort() + "/" + myself.getProtocol()); - } - SipStack stack = mSipStack = sipFactory.createSipStack(properties); - - try { - SipProvider provider = stack.createSipProvider( - stack.createListeningPoint(localIp, allocateLocalPort(), - myself.getProtocol())); - provider.addSipListener(this); - mSipHelper = new SipHelper(stack, provider); - } catch (InvalidArgumentException e) { - throw new IOException(e.getMessage()); - } catch (TooManyListenersException e) { - // must never happen - throw new SipException("SipSessionGroup constructor", e); - } - Log.d(TAG, " start stack for " + myself.getUriString()); - stack.start(); - - mLastNonce = null; - mCallReceiverSession = null; - mSessionMap.clear(); - } - - synchronized void onConnectivityChanged() { - for (SipSessionImpl s : mSessionMap.values()) { - s.onError(SipErrorCode.DATA_CONNECTION_LOST, - "data connection lost"); - } - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public String getLocalProfileUri() { - return mLocalProfile.getUriString(); - } - - private String getStackName() { - return "stack" + System.currentTimeMillis(); - } - - public synchronized void close() { - Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); - mSessionMap.clear(); - closeToNotReceiveCalls(); - if (mSipStack != null) { - mSipStack.stop(); - mSipStack = null; - mSipHelper = null; - } - } - - public synchronized boolean isClosed() { - return (mSipStack == null); - } - - // For internal use, require listener not to block in callbacks. - public synchronized void openToReceiveCalls(ISipSessionListener listener) { - if (mCallReceiverSession == null) { - mCallReceiverSession = new SipSessionCallReceiverImpl(listener); - } else { - mCallReceiverSession.setListener(listener); - } - } - - public synchronized void closeToNotReceiveCalls() { - mCallReceiverSession = null; - } - - public ISipSession createSession(ISipSessionListener listener) { - return (isClosed() ? null : new SipSessionImpl(listener)); - } - - private static int allocateLocalPort() throws SipException { - try { - DatagramSocket s = new DatagramSocket(); - int localPort = s.getLocalPort(); - s.close(); - return localPort; - } catch (IOException e) { - throw new SipException("allocateLocalPort()", e); - } - } - - private synchronized SipSessionImpl getSipSession(EventObject event) { - String key = SipHelper.getCallId(event); - SipSessionImpl session = mSessionMap.get(key); - if ((session != null) && isLoggable(session)) { - Log.d(TAG, "session key from event: " + key); - Log.d(TAG, "active sessions:"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); - } - } - return ((session != null) ? session : mCallReceiverSession); - } - - private synchronized void addSipSession(SipSessionImpl newSession) { - removeSipSession(newSession); - String key = newSession.getCallId(); - mSessionMap.put(key, newSession); - if (isLoggable(newSession)) { - Log.d(TAG, "+++ add a session with key: '" + key + "'"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); - } - } - } - - private synchronized void removeSipSession(SipSessionImpl session) { - if (session == mCallReceiverSession) return; - String key = session.getCallId(); - SipSessionImpl s = mSessionMap.remove(key); - // sanity check - if ((s != null) && (s != session)) { - Log.w(TAG, "session " + session + " is not associated with key '" - + key + "'"); - mSessionMap.put(key, s); - for (Map.Entry<String, SipSessionImpl> entry - : mSessionMap.entrySet()) { - if (entry.getValue() == s) { - key = entry.getKey(); - mSessionMap.remove(key); - } - } - } - - if ((s != null) && isLoggable(s)) { - Log.d(TAG, "remove session " + session + " @key '" + key + "'"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); - } - } - } - - public void processRequest(RequestEvent event) { - process(event); - } - - public void processResponse(ResponseEvent event) { - process(event); - } - - public void processIOException(IOExceptionEvent event) { - process(event); - } - - public void processTimeout(TimeoutEvent event) { - process(event); - } - - public void processTransactionTerminated(TransactionTerminatedEvent event) { - process(event); - } - - public void processDialogTerminated(DialogTerminatedEvent event) { - process(event); - } - - private synchronized void process(EventObject event) { - SipSessionImpl session = getSipSession(event); - try { - boolean isLoggable = isLoggable(session, event); - boolean processed = (session != null) && session.process(event); - if (isLoggable && processed) { - Log.d(TAG, "new state after: " - + SipSession.State.toString(session.mState)); - } - } catch (Throwable e) { - Log.w(TAG, "event process error: " + event, e); - session.onError(e); - } - } - - private String extractContent(Message message) { - // Currently we do not support secure MIME bodies. - byte[] bytes = message.getRawContent(); - if (bytes != null) { - try { - if (message instanceof SIPMessage) { - return ((SIPMessage) message).getMessageContent(); - } else { - return new String(bytes, "UTF-8"); - } - } catch (UnsupportedEncodingException e) { - } - } - return null; - } - - private class SipSessionCallReceiverImpl extends SipSessionImpl { - public SipSessionCallReceiverImpl(ISipSessionListener listener) { - super(listener); - } - - public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + log(evt)); - if (isRequestEvent(Request.INVITE, evt)) { - RequestEvent event = (RequestEvent) evt; - SipSessionImpl newSession = new SipSessionImpl(mProxy); - newSession.mServerTransaction = mSipHelper.sendRinging(event, - generateTag()); - newSession.mDialog = newSession.mServerTransaction.getDialog(); - newSession.mInviteReceived = event; - newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mState = SipSession.State.INCOMING_CALL; - newSession.mPeerSessionDescription = - extractContent(event.getRequest()); - addSipSession(newSession); - mProxy.onRinging(newSession, newSession.mPeerProfile, - newSession.mPeerSessionDescription); - return true; - } else if (isRequestEvent(Request.OPTIONS, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - return true; - } else { - return false; - } - } - } - - class SipSessionImpl extends ISipSession.Stub { - SipProfile mPeerProfile; - SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - int mState = SipSession.State.READY_TO_CALL; - RequestEvent mInviteReceived; - Dialog mDialog; - ServerTransaction mServerTransaction; - ClientTransaction mClientTransaction; - String mPeerSessionDescription; - boolean mInCall; - boolean mReRegisterFlag = false; - SessionTimer mTimer; - - // lightweight timer - class SessionTimer { - private boolean mRunning = true; - - void start(final int timeout) { - new Thread(new Runnable() { - public void run() { - sleep(timeout); - if (mRunning) timeout(); - } - }, "SipSessionTimerThread").start(); - } - - synchronized void cancel() { - mRunning = false; - this.notify(); - } - - private void timeout() { - synchronized (SipSessionGroup.this) { - onError(SipErrorCode.TIME_OUT, "Session timed out!"); - } - } - - private synchronized void sleep(int timeout) { - try { - this.wait(timeout * 1000); - } catch (InterruptedException e) { - Log.e(TAG, "session timer interrupted!"); - } - } - } - - public SipSessionImpl(ISipSessionListener listener) { - setListener(listener); - } - - SipSessionImpl duplicate() { - return new SipSessionImpl(mProxy.getListener()); - } - - private void reset() { - mInCall = false; - removeSipSession(this); - mPeerProfile = null; - mState = SipSession.State.READY_TO_CALL; - mInviteReceived = null; - mDialog = null; - mServerTransaction = null; - mClientTransaction = null; - mPeerSessionDescription = null; - - cancelSessionTimer(); - } - - public boolean isInCall() { - return mInCall; - } - - public String getLocalIp() { - return mLocalIp; - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public SipProfile getPeerProfile() { - return mPeerProfile; - } - - public String getCallId() { - return SipHelper.getCallId(getTransaction()); - } - - private Transaction getTransaction() { - if (mClientTransaction != null) return mClientTransaction; - if (mServerTransaction != null) return mServerTransaction; - return null; - } - - public int getState() { - return mState; - } - - public void setListener(ISipSessionListener listener) { - mProxy.setListener((listener instanceof SipSessionListenerProxy) - ? ((SipSessionListenerProxy) listener).getListener() - : listener); - } - - // process the command in a new thread - private void doCommandAsync(final EventObject command) { - new Thread(new Runnable() { - public void run() { - try { - processCommand(command); - } catch (SipException e) { - Log.w(TAG, "command error: " + command, e); - onError(e); - } - } - }, "SipSessionAsyncCmdThread").start(); - } - - public void makeCall(SipProfile peerProfile, String sessionDescription, - int timeout) { - doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, - timeout)); - } - - public void answerCall(String sessionDescription, int timeout) { - try { - processCommand(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } catch (SipException e) { - onError(e); - } - } - - public void endCall() { - doCommandAsync(END_CALL); - } - - public void changeCall(String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); - } - - public void changeCallWithTimeout( - String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); - } - - public void register(int duration) { - doCommandAsync(new RegisterCommand(duration)); - } - - public void unregister() { - doCommandAsync(DEREGISTER); - } - - public boolean isReRegisterRequired() { - return mReRegisterFlag; - } - - public void clearReRegisterRequired() { - mReRegisterFlag = false; - } - - public void sendKeepAlive() { - mState = SipSession.State.PINGING; - try { - processCommand(new OptionsCommand()); - while (SipSession.State.PINGING == mState) { - Thread.sleep(1000); - } - } catch (SipException e) { - Log.e(TAG, "sendKeepAlive failed", e); - } catch (InterruptedException e) { - Log.e(TAG, "sendKeepAlive interrupted", e); - } - } - - private void processCommand(EventObject command) throws SipException { - if (!process(command)) { - onError(SipErrorCode.IN_PROGRESS, - "cannot initiate a new transaction to execute: " - + command); - } - } - - protected String generateTag() { - // 32-bit randomness - return String.valueOf((long) (Math.random() * 0x100000000L)); - } - - public String toString() { - try { - String s = super.toString(); - return s.substring(s.indexOf("@")) + ":" - + SipSession.State.toString(mState); - } catch (Throwable e) { - return super.toString(); - } - } - - public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + log(evt)); - synchronized (SipSessionGroup.this) { - if (isClosed()) return false; - - Dialog dialog = null; - if (evt instanceof RequestEvent) { - dialog = ((RequestEvent) evt).getDialog(); - } else if (evt instanceof ResponseEvent) { - dialog = ((ResponseEvent) evt).getDialog(); - } - if (dialog != null) mDialog = dialog; - - boolean processed; - - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - processed = registeringToReady(evt); - break; - case SipSession.State.PINGING: - processed = keepAliveProcess(evt); - break; - case SipSession.State.READY_TO_CALL: - processed = readyForCall(evt); - break; - case SipSession.State.INCOMING_CALL: - processed = incomingCall(evt); - break; - case SipSession.State.INCOMING_CALL_ANSWERING: - processed = incomingCallToInCall(evt); - break; - case SipSession.State.OUTGOING_CALL: - case SipSession.State.OUTGOING_CALL_RING_BACK: - processed = outgoingCall(evt); - break; - case SipSession.State.OUTGOING_CALL_CANCELING: - processed = outgoingCallToReady(evt); - break; - case SipSession.State.IN_CALL: - processed = inCall(evt); - break; - default: - processed = false; - } - return (processed || processExceptions(evt)); - } - } - - private boolean processExceptions(EventObject evt) throws SipException { - if (isRequestEvent(Request.BYE, evt)) { - // terminate the call whenever a BYE is received - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, - Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); - return true; - } else if (evt instanceof TransactionTerminatedEvent) { - if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { - if (evt instanceof TimeoutEvent) { - processTimeout((TimeoutEvent) evt); - } else { - processTransactionTerminated( - (TransactionTerminatedEvent) evt); - } - return true; - } - } else if (isRequestEvent(Request.OPTIONS, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - return true; - } else if (evt instanceof DialogTerminatedEvent) { - processDialogTerminated((DialogTerminatedEvent) evt); - return true; - } - return false; - } - - private void processDialogTerminated(DialogTerminatedEvent event) { - if (mDialog == event.getDialog()) { - onError(new SipException("dialog terminated")); - } else { - Log.d(TAG, "not the current dialog; current=" + mDialog - + ", terminated=" + event.getDialog()); - } - } - - private boolean isCurrentTransaction(TransactionTerminatedEvent event) { - Transaction current = event.isServerTransaction() - ? mServerTransaction - : mClientTransaction; - Transaction target = event.isServerTransaction() - ? event.getServerTransaction() - : event.getClientTransaction(); - - if ((current != target) && (mState != SipSession.State.PINGING)) { - Log.d(TAG, "not the current transaction; current=" - + toString(current) + ", target=" + toString(target)); - return false; - } else if (current != null) { - Log.d(TAG, "transaction terminated: " + toString(current)); - return true; - } else { - // no transaction; shouldn't be here; ignored - return true; - } - } - - private String toString(Transaction transaction) { - if (transaction == null) return "null"; - Request request = transaction.getRequest(); - Dialog dialog = transaction.getDialog(); - CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); - return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), - cseq.getSeqNumber(), transaction.getState(), - ((dialog == null) ? "-" : dialog.getState())); - } - - private void processTransactionTerminated( - TransactionTerminatedEvent event) { - switch (mState) { - case SipSession.State.IN_CALL: - case SipSession.State.READY_TO_CALL: - Log.d(TAG, "Transaction terminated; do nothing"); - break; - default: - Log.d(TAG, "Transaction terminated early: " + this); - onError(SipErrorCode.TRANSACTION_TERMINTED, - "transaction terminated"); - } - } - - private void processTimeout(TimeoutEvent event) { - Log.d(TAG, "processing Timeout..."); - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - reset(); - mProxy.onRegistrationTimeout(this); - break; - case SipSession.State.INCOMING_CALL: - case SipSession.State.INCOMING_CALL_ANSWERING: - case SipSession.State.OUTGOING_CALL: - case SipSession.State.OUTGOING_CALL_CANCELING: - onError(SipErrorCode.TIME_OUT, event.toString()); - break; - case SipSession.State.PINGING: - reset(); - mReRegisterFlag = true; - mState = SipSession.State.READY_TO_CALL; - break; - - default: - Log.d(TAG, " do nothing"); - break; - } - } - - private int getExpiryTime(Response response) { - int expires = EXPIRY_TIME; - ExpiresHeader expiresHeader = (ExpiresHeader) - response.getHeader(ExpiresHeader.NAME); - if (expiresHeader != null) expires = expiresHeader.getExpires(); - expiresHeader = (ExpiresHeader) - response.getHeader(MinExpiresHeader.NAME); - if (expiresHeader != null) { - expires = Math.max(expires, expiresHeader.getExpires()); - } - return expires; - } - - private boolean keepAliveProcess(EventObject evt) throws SipException { - if (evt instanceof OptionsCommand) { - mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - return true; - } else if (evt instanceof ResponseEvent) { - return parseOptionsResult(evt); - } - return false; - } - - private boolean parseOptionsResult(EventObject evt) { - if (expectResponse(Request.OPTIONS, evt)) { - ResponseEvent event = (ResponseEvent) evt; - int rPort = getRPortFromResponse(event.getResponse()); - if (rPort != -1) { - if (mRPort == 0) mRPort = rPort; - if (mRPort != rPort) { - mReRegisterFlag = true; - if (DEBUG) Log.w(TAG, String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); - } - } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); - } - reset(); - return true; - } - return false; - } - - private int getRPortFromResponse(Response response) { - ViaHeader viaHeader = (ViaHeader)(response.getHeader( - SIPHeaderNames.VIA)); - return (viaHeader == null) ? -1 : viaHeader.getRPort(); - } - - private boolean registeringToReady(EventObject evt) - throws SipException { - if (expectResponse(Request.REGISTER, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.OK: - int state = mState; - onRegistrationDone((state == SipSession.State.REGISTERING) - ? getExpiryTime(((ResponseEvent) evt).getResponse()) - : -1); - mLastNonce = null; - mRPort = 0; - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - if (!handleAuthentication(event)) { - if (mLastNonce == null) { - onRegistrationFailed(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - Log.v(TAG, "Incorrect username/password"); - onRegistrationFailed( - SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - } - return true; - default: - if (statusCode >= 500) { - onRegistrationFailed(response); - return true; - } - } - } - return false; - } - - private boolean handleAuthentication(ResponseEvent event) - throws SipException { - Response response = event.getResponse(); - String nonce = getNonceFromResponse(response); - if (((nonce != null) && nonce.equals(mLastNonce)) || - (nonce == null)) { - mLastNonce = nonce; - return false; - } else { - mClientTransaction = mSipHelper.handleChallenge( - event, getAccountManager()); - mDialog = mClientTransaction.getDialog(); - mLastNonce = nonce; - return true; - } - } - - private boolean crossDomainAuthenticationRequired(Response response) { - String realm = getRealmFromResponse(response); - if (realm == null) realm = ""; - return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); - } - - private AccountManager getAccountManager() { - return new AccountManager() { - public UserCredentials getCredentials(ClientTransaction - challengedTransaction, String realm) { - return new UserCredentials() { - public String getUserName() { - return mLocalProfile.getUserName(); - } - - public String getPassword() { - return mPassword; - } - - public String getSipDomain() { - return mLocalProfile.getSipDomain(); - } - }; - } - }; - } - - private String getRealmFromResponse(Response response) { - WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( - SIPHeaderNames.WWW_AUTHENTICATE); - if (wwwAuth != null) return wwwAuth.getRealm(); - ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( - SIPHeaderNames.PROXY_AUTHENTICATE); - return (proxyAuth == null) ? null : proxyAuth.getRealm(); - } - - private String getNonceFromResponse(Response response) { - WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( - SIPHeaderNames.WWW_AUTHENTICATE); - if (wwwAuth != null) return wwwAuth.getNonce(); - ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( - SIPHeaderNames.PROXY_AUTHENTICATE); - return (proxyAuth == null) ? null : proxyAuth.getNonce(); - } - - private boolean readyForCall(EventObject evt) throws SipException { - // expect MakeCallCommand, RegisterCommand, DEREGISTER - if (evt instanceof MakeCallCommand) { - MakeCallCommand cmd = (MakeCallCommand) evt; - mPeerProfile = cmd.getPeerProfile(); - mClientTransaction = mSipHelper.sendInvite(mLocalProfile, - mPeerProfile, cmd.getSessionDescription(), - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.OUTGOING_CALL; - mProxy.onCalling(this); - startSessionTimer(cmd.getTimeout()); - return true; - } else if (evt instanceof RegisterCommand) { - int duration = ((RegisterCommand) evt).getDuration(); - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), duration); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.REGISTERING; - mProxy.onRegistering(this); - return true; - } else if (DEREGISTER == evt) { - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), 0); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.DEREGISTERING; - mProxy.onRegistering(this); - return true; - } - return false; - } - - private boolean incomingCall(EventObject evt) throws SipException { - // expect MakeCallCommand(answering) , END_CALL cmd , Cancel - if (evt instanceof MakeCallCommand) { - // answer call - mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, - mLocalProfile, - ((MakeCallCommand) evt).getSessionDescription(), - mServerTransaction); - mState = SipSession.State.INCOMING_CALL_ANSWERING; - startSessionTimer(((MakeCallCommand) evt).getTimeout()); - return true; - } else if (END_CALL == evt) { - mSipHelper.sendInviteBusyHere(mInviteReceived, - mServerTransaction); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - RequestEvent event = (RequestEvent) evt; - mSipHelper.sendResponse(event, Response.OK); - mSipHelper.sendInviteRequestTerminated( - mInviteReceived.getRequest(), mServerTransaction); - endCallNormally(); - return true; - } - return false; - } - - private boolean incomingCallToInCall(EventObject evt) - throws SipException { - // expect ACK, CANCEL request - if (isRequestEvent(Request.ACK, evt)) { - establishCall(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - // http://tools.ietf.org/html/rfc3261#section-9.2 - // Final response has been sent; do nothing here. - return true; - } - return false; - } - - private boolean outgoingCall(EventObject evt) throws SipException { - if (expectResponse(Request.INVITE, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.RINGING: - if (mState == SipSession.State.OUTGOING_CALL) { - mState = SipSession.State.OUTGOING_CALL_RING_BACK; - mProxy.onRingingBack(this); - cancelSessionTimer(); - } - return true; - case Response.OK: - mSipHelper.sendInviteAck(event, mDialog); - mPeerSessionDescription = extractContent(response); - establishCall(); - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - if (crossDomainAuthenticationRequired(response)) { - onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, - getRealmFromResponse(response)); - } else if (handleAuthentication(event)) { - addSipSession(this); - } else if (mLastNonce == null) { - onError(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - onError(SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - return true; - case Response.REQUEST_PENDING: - // TODO: - // rfc3261#section-14.1; re-schedule invite - return true; - default: - if (statusCode >= 400) { - // error: an ack is sent automatically by the stack - onError(response); - return true; - } else if (statusCode >= 300) { - // TODO: handle 3xx (redirect) - } else { - return true; - } - } - return false; - } else if (END_CALL == evt) { - // RFC says that UA should not send out cancel when no - // response comes back yet. We are cheating for not checking - // response. - mSipHelper.sendCancel(mClientTransaction); - mState = SipSession.State.OUTGOING_CALL_CANCELING; - startSessionTimer(CANCEL_CALL_TIMER); - return true; - } - return false; - } - - private boolean outgoingCallToReady(EventObject evt) - throws SipException { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - int statusCode = response.getStatusCode(); - if (expectResponse(Request.CANCEL, evt)) { - if (statusCode == Response.OK) { - // do nothing; wait for REQUEST_TERMINATED - return true; - } - } else if (expectResponse(Request.INVITE, evt)) { - switch (statusCode) { - case Response.OK: - outgoingCall(evt); // abort Cancel - return true; - case Response.REQUEST_TERMINATED: - endCallNormally(); - return true; - } - } else { - return false; - } - - if (statusCode >= 400) { - onError(response); - return true; - } - } else if (evt instanceof TransactionTerminatedEvent) { - // rfc3261#section-14.1: - // if re-invite gets timed out, terminate the dialog; but - // re-invite is not reliable, just let it go and pretend - // nothing happened. - onError(new SipException("timed out")); - } - return false; - } - - private boolean inCall(EventObject evt) throws SipException { - // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) - // OK retransmission is handled in SipStack - if (END_CALL == evt) { - // rfc3261#section-15.1.1 - mSipHelper.sendBye(mDialog); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.INVITE, evt)) { - // got Re-INVITE - RequestEvent event = mInviteReceived = (RequestEvent) evt; - mState = SipSession.State.INCOMING_CALL; - mPeerSessionDescription = extractContent(event.getRequest()); - mServerTransaction = null; - mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); - return true; - } else if (isRequestEvent(Request.BYE, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - endCallNormally(); - return true; - } else if (evt instanceof MakeCallCommand) { - // to change call - mClientTransaction = mSipHelper.sendReinvite(mDialog, - ((MakeCallCommand) evt).getSessionDescription()); - mState = SipSession.State.OUTGOING_CALL; - startSessionTimer(((MakeCallCommand) evt).getTimeout()); - return true; - } - return false; - } - - // timeout in seconds - private void startSessionTimer(int timeout) { - if (timeout > 0) { - mTimer = new SessionTimer(); - mTimer.start(timeout); - } - } - - private void cancelSessionTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; - } - } - - private String createErrorMessage(Response response) { - return String.format(SERVER_ERROR_PREFIX + "%s (%d)", - response.getReasonPhrase(), response.getStatusCode()); - } - - private void establishCall() { - mState = SipSession.State.IN_CALL; - mInCall = true; - cancelSessionTimer(); - mProxy.onCallEstablished(this, mPeerSessionDescription); - } - - private void fallbackToPreviousInCall(int errorCode, String message) { - mState = SipSession.State.IN_CALL; - mProxy.onCallChangeFailed(this, errorCode, message); - } - - private void endCallNormally() { - reset(); - mProxy.onCallEnded(this); - } - - private void endCallOnError(int errorCode, String message) { - reset(); - mProxy.onError(this, errorCode, message); - } - - private void endCallOnBusy() { - reset(); - mProxy.onCallBusy(this); - } - - private void onError(int errorCode, String message) { - cancelSessionTimer(); - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - onRegistrationFailed(errorCode, message); - break; - default: - if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST) - && mInCall) { - fallbackToPreviousInCall(errorCode, message); - } else { - endCallOnError(errorCode, message); - } - } - } - - - private void onError(Throwable exception) { - exception = getRootCause(exception); - onError(getErrorCode(exception), exception.toString()); - } - - private void onError(Response response) { - int statusCode = response.getStatusCode(); - if (!mInCall && (statusCode == Response.BUSY_HERE)) { - endCallOnBusy(); - } else { - onError(getErrorCode(statusCode), createErrorMessage(response)); - } - } - - private int getErrorCode(int responseStatusCode) { - switch (responseStatusCode) { - case Response.TEMPORARILY_UNAVAILABLE: - case Response.FORBIDDEN: - case Response.GONE: - case Response.NOT_FOUND: - case Response.NOT_ACCEPTABLE: - case Response.NOT_ACCEPTABLE_HERE: - return SipErrorCode.PEER_NOT_REACHABLE; - - case Response.REQUEST_URI_TOO_LONG: - case Response.ADDRESS_INCOMPLETE: - case Response.AMBIGUOUS: - return SipErrorCode.INVALID_REMOTE_URI; - - case Response.REQUEST_TIMEOUT: - return SipErrorCode.TIME_OUT; - - default: - if (responseStatusCode < 500) { - return SipErrorCode.CLIENT_ERROR; - } else { - return SipErrorCode.SERVER_ERROR; - } - } - } - - private Throwable getRootCause(Throwable exception) { - Throwable cause = exception.getCause(); - while (cause != null) { - exception = cause; - cause = exception.getCause(); - } - return exception; - } - - private int getErrorCode(Throwable exception) { - String message = exception.getMessage(); - if (exception instanceof UnknownHostException) { - return SipErrorCode.INVALID_REMOTE_URI; - } else if (exception instanceof IOException) { - return SipErrorCode.SOCKET_ERROR; - } else if (message.startsWith(SERVER_ERROR_PREFIX)) { - return SipErrorCode.SERVER_ERROR; - } else { - return SipErrorCode.CLIENT_ERROR; - } - } - - private void onRegistrationDone(int duration) { - reset(); - mProxy.onRegistrationDone(this, duration); - } - - private void onRegistrationFailed(int errorCode, String message) { - reset(); - mProxy.onRegistrationFailed(this, errorCode, message); - } - - private void onRegistrationFailed(Throwable exception) { - reset(); - exception = getRootCause(exception); - onRegistrationFailed(getErrorCode(exception), - exception.toString()); - } - - private void onRegistrationFailed(Response response) { - reset(); - int statusCode = response.getStatusCode(); - onRegistrationFailed(getErrorCode(statusCode), - createErrorMessage(response)); - } - } - - /** - * @return true if the event is a request event matching the specified - * method; false otherwise - */ - private static boolean isRequestEvent(String method, EventObject event) { - try { - if (event instanceof RequestEvent) { - RequestEvent requestEvent = (RequestEvent) event; - return method.equals(requestEvent.getRequest().getMethod()); - } - } catch (Throwable e) { - } - return false; - } - - private static String getCseqMethod(Message message) { - return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); - } - - /** - * @return true if the event is a response event and the CSeqHeader method - * match the given arguments; false otherwise - */ - private static boolean expectResponse( - String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - return false; - } - - /** - * @return true if the event is a response event and the response code and - * CSeqHeader method match the given arguments; false otherwise - */ - private static boolean expectResponse( - int responseCode, String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - if (response.getStatusCode() == responseCode) { - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - } - return false; - } - - private static SipProfile createPeerProfile(Request request) - throws SipException { - try { - FromHeader fromHeader = - (FromHeader) request.getHeader(FromHeader.NAME); - Address address = fromHeader.getAddress(); - SipURI uri = (SipURI) address.getURI(); - String username = uri.getUser(); - if (username == null) username = ANONYMOUS; - return new SipProfile.Builder(username, uri.getHost()) - .setPort(uri.getPort()) - .setDisplayName(address.getDisplayName()) - .build(); - } catch (IllegalArgumentException e) { - throw new SipException("createPeerProfile()", e); - } catch (ParseException e) { - throw new SipException("createPeerProfile()", e); - } - } - - private static boolean isLoggable(SipSessionImpl s) { - if (s != null) { - switch (s.mState) { - case SipSession.State.PINGING: - return DEBUG_PING; - } - } - return DEBUG; - } - - private static boolean isLoggable(SipSessionImpl s, EventObject evt) { - if (!isLoggable(s)) return false; - if (evt == null) return false; - - if (evt instanceof OptionsCommand) { - return DEBUG_PING; - } else if (evt instanceof ResponseEvent) { - Response response = ((ResponseEvent) evt).getResponse(); - if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { - return DEBUG_PING; - } - return DEBUG; - } else if (evt instanceof RequestEvent) { - return DEBUG; - } - return false; - } - - private static String log(EventObject evt) { - if (evt instanceof RequestEvent) { - return ((RequestEvent) evt).getRequest().toString(); - } else if (evt instanceof ResponseEvent) { - return ((ResponseEvent) evt).getResponse().toString(); - } else { - return evt.toString(); - } - } - - private class OptionsCommand extends EventObject { - public OptionsCommand() { - super(SipSessionGroup.this); - } - } - - private class RegisterCommand extends EventObject { - private int mDuration; - - public RegisterCommand(int duration) { - super(SipSessionGroup.this); - mDuration = duration; - } - - public int getDuration() { - return mDuration; - } - } - - private class MakeCallCommand extends EventObject { - private String mSessionDescription; - private int mTimeout; // in seconds - - public MakeCallCommand(SipProfile peerProfile, - String sessionDescription) { - this(peerProfile, sessionDescription, -1); - } - - public MakeCallCommand(SipProfile peerProfile, - String sessionDescription, int timeout) { - super(peerProfile); - mSessionDescription = sessionDescription; - mTimeout = timeout; - } - - public SipProfile getPeerProfile() { - return (SipProfile) getSource(); - } - - public String getSessionDescription() { - return mSessionDescription; - } - - public int getTimeout() { - return mTimeout; - } - } -} |