All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nist.javax.sip.stack.SIPDialog Maven / Gradle / Ivy

There is a newer version: 1.3.0-91
Show newest version
/*
 * Conditions Of Use
 *
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 *
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 *
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *
 * .
 *
 */
/**************************************************************************/
/* Product of NIST Advanced Networking Technologies Division      */
/**************************************************************************/
package android.gov.nist.javax.sip.stack;

import android.gov.nist.core.CommonLogger;
import android.gov.nist.core.InternalErrorHandler;
import android.gov.nist.core.LogLevels;
import android.gov.nist.core.LogWriter;
import android.gov.nist.core.NameValueList;
import android.gov.nist.core.StackLogger;
import android.gov.nist.javax.sip.DialogExt;
import android.gov.nist.javax.sip.ListeningPointImpl;
import android.gov.nist.javax.sip.ReleaseReferencesStrategy;
import android.gov.nist.javax.sip.SipListenerExt;
import android.gov.nist.javax.sip.SipProviderImpl;
import android.gov.nist.javax.sip.SipStackImpl;
import android.gov.nist.javax.sip.Utils;
import android.gov.nist.javax.sip.address.AddressImpl;
import android.gov.nist.javax.sip.address.SipUri;
import android.gov.nist.javax.sip.header.Authorization;
import android.gov.nist.javax.sip.header.CSeq;
import android.gov.nist.javax.sip.header.Contact;
import android.gov.nist.javax.sip.header.ContactList;
import android.gov.nist.javax.sip.header.Event;
import android.gov.nist.javax.sip.header.From;
import android.gov.nist.javax.sip.header.MaxForwards;
import android.gov.nist.javax.sip.header.RAck;
import android.gov.nist.javax.sip.header.RSeq;
import android.gov.nist.javax.sip.header.Reason;
import android.gov.nist.javax.sip.header.RecordRoute;
import android.gov.nist.javax.sip.header.RecordRouteList;
import android.gov.nist.javax.sip.header.Require;
import android.gov.nist.javax.sip.header.Route;
import android.gov.nist.javax.sip.header.RouteList;
import android.gov.nist.javax.sip.header.SIPHeader;
import android.gov.nist.javax.sip.header.TimeStamp;
import android.gov.nist.javax.sip.header.To;
import android.gov.nist.javax.sip.header.Via;
import android.gov.nist.javax.sip.message.MessageFactoryImpl;
import android.gov.nist.javax.sip.message.SIPMessage;
import android.gov.nist.javax.sip.message.SIPRequest;
import android.gov.nist.javax.sip.message.SIPResponse;
import android.gov.nist.javax.sip.parser.AddressParser;
import android.gov.nist.javax.sip.parser.CallIDParser;
import android.gov.nist.javax.sip.parser.ContactParser;
import android.gov.nist.javax.sip.parser.RecordRouteParser;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import android.javax.sip.ClientTransaction;
import android.javax.sip.Dialog;
import android.javax.sip.DialogDoesNotExistException;
import android.javax.sip.DialogState;
import android.javax.sip.IOExceptionEvent;
import android.javax.sip.InvalidArgumentException;
import android.javax.sip.ListeningPoint;
import android.javax.sip.ObjectInUseException;
import android.javax.sip.SipException;
import android.javax.sip.Transaction;
import android.javax.sip.TransactionDoesNotExistException;
import android.javax.sip.TransactionState;
import android.javax.sip.address.Address;
import android.javax.sip.address.Hop;
import android.javax.sip.address.SipURI;
import android.javax.sip.header.CallIdHeader;
import android.javax.sip.header.ContactHeader;
import android.javax.sip.header.EventHeader;
import android.javax.sip.header.OptionTag;
import android.javax.sip.header.ProxyAuthorizationHeader;
import android.javax.sip.header.RAckHeader;
import android.javax.sip.header.RSeqHeader;
import android.javax.sip.header.ReasonHeader;
import android.javax.sip.header.RequireHeader;
import android.javax.sip.header.RouteHeader;
import android.javax.sip.header.SupportedHeader;
import android.javax.sip.header.TimeStampHeader;
import android.javax.sip.message.Request;
import android.javax.sip.message.Response;

/*
 * Acknowledgements:
 * 
 * Bugs in this class were reported by Antonis Karydas, Brad Templeton, Jeff Adams, Alex Rootham ,
 * Martin Le Clerk, Christophe Anzille, Andreas Bystrom, Lebing Xie, Jeroen van Bemmel. Hagai Sela
 * reported a bug in updating the route set (on RE-INVITE). Jens Tinfors submitted a bug fix and
 * the .equals method. Jan Schaumloeffel contributed a buf fix ( memory leak was happening when
 * 180 contained a To tag. Bug fixes by Vladimir Ralev (Redhat).
 * Performance enhancements and memory reduction enhancements by Jean Deruelle.
 * 
 */

/**
 * Tracks dialogs. A dialog is a peer to peer association of communicating SIP
 * entities. For INVITE transactions, a Dialog is created when a success message
 * is received (i.e. a response that has a To tag). The SIP Protocol stores
 * enough state in the message structure to extract a dialog identifier that can
 * be used to retrieve this structure from the SipStack.
 * 
 * @version 1.2 $Revision: 1.207 $ $Date: 2010-12-02 22:04:14 $
 * 
 * @author M. Ranganathan
 * 
 * 
 */

public class SIPDialog implements android.javax.sip.Dialog, DialogExt {
	private static StackLogger logger = CommonLogger.getLogger(SIPDialog.class);

    private static final long serialVersionUID = -1429794423085204069L;

    private transient boolean dialogTerminatedEventDelivered; // prevent
    // duplicate

    private transient String stackTrace; // for semaphore debugging.

    protected String method;

    // delivery of the event
    protected transient boolean isAssigned;

    protected boolean reInviteFlag;

    private transient Object applicationData; // Opaque pointer to application
    // data.

    private transient SIPRequest originalRequest;
    // jeand : avoid keeping the original request ref above for too long (mem
    // saving)
    protected transient String originalRequestRecordRouteHeadersString;
    protected transient RecordRouteList originalRequestRecordRouteHeaders;

    // Last response (JvB: either sent or received).
    // jeand replaced the last response with only the data from it needed to
    // save on mem
    protected String lastResponseDialogId;
    private Via lastResponseTopMostVia;
    protected Integer lastResponseStatusCode;
    protected long lastResponseCSeqNumber;
    protected long lastInviteResponseCSeqNumber;
    protected int lastInviteResponseCode;
    protected String lastResponseMethod;
    protected String lastResponseFromTag;
    protected String lastResponseToTag;

    // jeand: needed for reliable response sending but nullifyed right after the
    // ACK has been received or sent to let go of the ref ASAP
    protected SIPTransaction firstTransaction;
    // jeand needed for checking 491 but nullifyed right after the ACK has been
    // received or sent to let go of the ref ASAP
    protected SIPTransaction lastTransaction;

    protected String dialogId;

    protected transient String earlyDialogId;

    protected long localSequenceNumber;

    protected long remoteSequenceNumber;

    protected String myTag;

    protected String hisTag;

    protected RouteList routeList;

    private transient SIPTransactionStack sipStack;

    private int dialogState;

    protected transient SIPRequest lastAckSent;

    // jeand : replaced the lastAckReceived message with only the data needed to
    // save on mem
    protected Long lastAckReceivedCSeqNumber;

    // could be set on recovery by examining the method looks like a duplicate
    // of ackSeen
    protected transient boolean ackProcessed;

    protected transient DialogTimerTask timerTask;

    protected transient long nextSeqno;

    private transient int retransmissionTicksLeft;

    private transient int prevRetransmissionTicks;

    protected long originalLocalSequenceNumber;

    // This is for debugging only.
    private transient int ackLine;

    // Audit tag used by the SIP Stack audit
    public transient long auditTag = 0;

    // The following fields are extracted from the request that created the
    // Dialog.

    protected android.javax.sip.address.Address localParty;
    protected String localPartyStringified;

    protected android.javax.sip.address.Address remoteParty;
    protected String remotePartyStringified;

    protected CallIdHeader callIdHeader;
    protected String callIdHeaderString;

    public final static int NULL_STATE = -1;

    public final static int EARLY_STATE = DialogState._EARLY;

    public final static int CONFIRMED_STATE = DialogState._CONFIRMED;

    public final static int TERMINATED_STATE = DialogState._TERMINATED;

    protected boolean serverTransactionFlag;

    private transient SipProviderImpl sipProvider;

    protected boolean terminateOnBye;

    protected transient boolean byeSent; // Flag set when BYE is sent, to
    // disallow new

    // requests

    protected Address remoteTarget;
    protected String remoteTargetStringified;

    protected EventHeader eventHeader; // for Subscribe notify

    // Stores the last OK for the INVITE
    // Used in createAck.
    protected transient long lastInviteOkReceived;

    private transient Semaphore ackSem = new Semaphore(1);

    protected transient int reInviteWaitTime = 100;

    private transient DialogDeleteTask dialogDeleteTask;

    private transient DialogDeleteIfNoAckSentTask dialogDeleteIfNoAckSentTask;

    protected transient boolean isAcknowledged;

    private transient long highestSequenceNumberAcknowledged = -1;

    protected boolean isBackToBackUserAgent;

    protected boolean sequenceNumberValidation = true;

    // List of event listeners for this dialog
    private transient Set eventListeners;
    // added for Issue 248 :
    // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=248
    private transient Semaphore timerTaskLock = new Semaphore(1);

    // We store here the useful data from the first transaction without having
    // to
    // keep the whole transaction object for the duration of the dialog. It also
    // contains the non-transient information used in the replication of
    // dialogs.
    protected boolean firstTransactionSecure;
    protected boolean firstTransactionSeen;
    protected String firstTransactionMethod;
    protected String firstTransactionId;
    protected boolean firstTransactionIsServerTransaction;
    protected String firstTransactionMergeId;
    protected int firstTransactionPort = 5060;
    protected Contact contactHeader;
    protected String contactHeaderStringified;

    private boolean pendingRouteUpdateOn202Response;

    protected ProxyAuthorizationHeader proxyAuthorizationHeader; // For
                                                                 // subequent
                                                                 // requests.

    // aggressive flag to optimize eagerly
    private ReleaseReferencesStrategy releaseReferencesStrategy;

    private transient EarlyStateTimerTask earlyStateTimerTask;

    private int earlyDialogTimeout = 180;

	private Set responsesReceivedInForkingCase = new HashSet(0);

  private SIPDialog originalDialog;

  private transient AckSendingStrategy ackSendingStrategy = new AckSendingStrategyImpl();

	
    // //////////////////////////////////////////////////////
    // Inner classes
    // //////////////////////////////////////////////////////
  
    public class AckSendingStrategyImpl implements AckSendingStrategy {
      
        private Hop hop = null;

        @Override
        public void send(SIPRequest ackRequest) throws SipException, IOException {
            hop = sipStack.getNextHop(ackRequest);
            if (hop == null)
            throw new SipException("No route!");
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                logger.logDebug("hop = " + hop);
            ListeningPointImpl lp = (ListeningPointImpl) sipProvider
                    .getListeningPoint(hop.getTransport());
            if (lp == null)
                throw new SipException(
                        "No listening point for this provider registered at "
                                + hop);
            InetAddress inetAddress = InetAddress.getByName(hop.getHost());
            MessageChannel messageChannel = lp.getMessageProcessor()
                    .createMessageChannel(inetAddress, hop.getPort());
                        messageChannel.sendMessage(ackRequest);
        }


        @Override
        public Hop getLastHop() {
          return hop;
        }
    }

    class EarlyStateTimerTask extends SIPStackTimerTask implements Serializable {

        public EarlyStateTimerTask() {

        }

        @Override
        public void runTask() {
            try {
                if (SIPDialog.this.getState().equals(DialogState.EARLY)) {
                    
                    SIPDialog.this
                            .raiseErrorEvent(SIPDialogErrorEvent.EARLY_STATE_TIMEOUT);
                } else {
                    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                        logger.logDebug("EarlyStateTimerTask : Dialog state is " + SIPDialog.this.getState());
                    }
                }
            } catch (Exception ex) {
                logger.logError(
                        "Unexpected exception delivering event", ex);
            }
        }

        @Override
        public Object getThreadHash() {
            return getCallId().getCallId();
        }

    }

    /**
     * This task waits till a pending ACK has been recorded and then sends out a
     * re-INVITE. This is to prevent interleaving INVITEs ( which will result in
     * a 493 from the UA that receives the out of order INVITE). This is
     * primarily for B2BUA support. A B2BUA may send a delayed ACK while it does
     * mid call codec renegotiation. In the meanwhile, it cannot send an
     * intervening re-INVITE otherwise the othr end will respond with a
     * REQUEST_PENDING. We want to avoid this condition. Hence we wait till the
     * ACK for the previous re-INVITE has been sent before sending the next
     * re-INVITE.
     */
    public class ReInviteSender implements Runnable, Serializable {
        private static final long serialVersionUID = 1019346148741070635L;
        ClientTransaction ctx;

        public void terminate() {
            try {
            	if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            		logger.logDebug("ReInviteSender::terminate: ctx = " + ctx);
            	}
            	
                ctx.terminate();
                Thread.currentThread().interrupt();
            } catch (ObjectInUseException e) {
                logger.logError("unexpected error", e);
            }
        }

        public ReInviteSender(ClientTransaction ctx) {
            this.ctx = ctx;
            if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            	logger.logDebug("ReInviteSender::ReInviteSender: ctx = " + ctx );
            	logger.logStackTrace();
            }
        }

        public void run() {
            try {
                long timeToWait = 0;
                long startTime = System.currentTimeMillis();
                boolean dialogTimedOut = false;
                boolean busyWait = false;

                // If we have an INVITE transaction, make sure that it is TERMINATED
                // before sending a re-INVITE.. Not the cleanest solution but it works.
                if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                	logger.logDebug("SIPDialog::reInviteSender: dialog = " + ctx.getDialog()  + " lastTransaction = " + lastTransaction + " lastTransactionState " + lastTransaction.getState());
                }
                if (SIPDialog.this.lastTransaction != null &&
                			SIPDialog.this.lastTransaction instanceof SIPServerTransaction && 
                			SIPDialog.this.lastTransaction.isInviteTransaction() &&
                		    SIPDialog.this.lastTransaction.getState() != TransactionState.TERMINATED)
                {
                	((SIPServerTransaction)SIPDialog.this.lastTransaction).waitForTermination();
                	Thread.sleep(50);
                }
                
               

                if (!SIPDialog.this.takeAckSem()) {
                    /*
                     * Could not send re-INVITE fire a timeout on the INVITE.
                     */
                    if (logger.isLoggingEnabled())
                        logger
                                .logError(
                                        "Could not send re-INVITE time out ClientTransaction");
                    ((SIPClientTransaction) ctx).fireTimeoutTimer();
                    /*
                     * Send BYE to the Dialog.
                     */
                    if (sipProvider.getSipListener() != null
                            && sipProvider.getSipListener() instanceof SipListenerExt) {
                        dialogTimedOut = true;
                        raiseErrorEvent(SIPDialogErrorEvent.DIALOG_REINVITE_TIMEOUT,(SIPClientTransaction)ctx);
                    } else {
                        Request byeRequest = SIPDialog.this
                                .createRequest(Request.BYE);
                        if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
                            byeRequest.addHeader(MessageFactoryImpl
                                    .getDefaultUserAgentHeader());
                        }
                        ReasonHeader reasonHeader = new Reason();
                        reasonHeader.setCause(1024);
                        reasonHeader.setText("Timed out waiting to re-INVITE");
                        byeRequest.addHeader(reasonHeader);
                        ClientTransaction byeCtx = SIPDialog.this
                                .getSipProvider().getNewClientTransaction(
                                        byeRequest);
                        SIPDialog.this.sendRequest(byeCtx);
                        return;
                    }
                }
                if (getState() != DialogState.TERMINATED) {

                    timeToWait = System.currentTimeMillis() - startTime;
                }

                /*
                 * If we had to wait for ACK then wait for the ACK to actually
                 * get to the other side. Wait for any ACK retransmissions to
                 * finish. Then send out the request. This is a hack in support
                 * of some UA that want re-INVITEs to be spaced out in time (
                 * else they return a 400 error code ).
                 */
                try {
                    if (timeToWait != 0) {
                        Thread.sleep(SIPDialog.this.reInviteWaitTime);
                    }
                } catch (InterruptedException ex) {
                    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                        logger.logDebug("Interrupted sleep");
                    return;
                }
                if (SIPDialog.this.getState() != DialogState.TERMINATED && !dialogTimedOut && ctx.getState() != TransactionState.TERMINATED ) {
                    SIPDialog.this.sendRequest(ctx, true);
                    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) 
                        logger.logDebug(
                                "re-INVITE successfully sent");
                }
               
            } catch (Exception ex) {
                logger.logError("Error sending re-INVITE",
                        ex);
            } finally {
                this.ctx = null;
            }
        }
    }

    class LingerTimer extends SIPStackTimerTask implements Serializable {

        public void runTask() {
            SIPDialog dialog = SIPDialog.this;
            sipStack.removeDialog(dialog);
            // Issue 279 :
            // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=279
            // if non reentrant listener is used the event delivery of
            // DialogTerminated
            // can happen after the clean
            if (((SipStackImpl) getStack()).isReEntrantListener()) {
                cleanUp();
            }
        }

        @Override
        public Object getThreadHash() {
            return getCallId().getCallId();
        }

    }

    class DialogTimerTask extends SIPStackTimerTask implements Serializable {
        int nRetransmissions;

        SIPServerTransaction transaction;

        // long cseqNumber;

        public DialogTimerTask(SIPServerTransaction transaction) {
            this.transaction = transaction;
            this.nRetransmissions = 0;
         }

        public void runTask() {
            // If I ACK has not been seen on Dialog,
            // resend last response.
            SIPDialog dialog = SIPDialog.this;
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                logger.logDebug("Running dialog timer");
            nRetransmissions++;
            SIPServerTransaction transaction = this.transaction;
            /*
             * Issue 106. Section 13.3.1.4 RFC 3261 The 2xx response is passed
             * to the transport with an interval that starts at T1 seconds and
             * doubles for each retransmission until it reaches T2 seconds If
             * the server retransmits the 2xx response for 64T1 seconds without
             * receiving an ACK, the dialog is confirmed, but the session SHOULD
             * be terminated.
             */

            if (nRetransmissions > sipStack.getAckTimeoutFactor()
                    * SIPTransaction.T1) {
                if (SIPDialog.this.getSipProvider().getSipListener() != null
                        && SIPDialog.this.getSipProvider().getSipListener() instanceof SipListenerExt) {
                    raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_RECEIVED_TIMEOUT);
                } else {
                    SIPDialog.this.delete();
                }
                if (transaction != null
                        && transaction.getState() != android.javax.sip.TransactionState.TERMINATED) {
                    transaction
                            .raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR);
                }
            } else if ((transaction != null) && (!dialog.isAckSeen())) {
                // Retransmit to 2xx until ack receivedialog.
                if (lastResponseStatusCode.intValue() / 100 == 2) {
                    try {

                        // resend the last response.
                        if (dialog.toRetransmitFinalResponse(transaction.getTimerT2())) {
                            transaction.resendLastResponseAsBytes();
                        }
                    } catch (IOException ex) {

                        raiseIOException(transaction.getPeerAddress(),
                                transaction.getPeerPort(), transaction
                                        .getPeerProtocol());

                    } finally {
                        // Need to fire the timer so
                        // transaction will eventually
                        // time out whether or not
                        // the IOException occurs
                        // Note that this firing also
                        // drives Listener timeout.
                        SIPTransactionStack stack = dialog.sipStack;
                        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                            logger.logDebug(
                                    "resend 200 response from " + dialog);
                        }
                        transaction.fireTimer();
                    }
                }
            }

            // Stop running this timer if the dialog is in the
            // confirmed state or ack seen if retransmit filter on.
            if (dialog.isAckSeen() || dialog.dialogState == TERMINATED_STATE) {
                this.transaction = null;
                getStack().getTimer().cancel(this);

            }

        }

        @Override
        public void cleanUpBeforeCancel() {
            transaction = null;
           // lastAckSent = null;
            cleanUpOnAck();
            super.cleanUpBeforeCancel();
        }

        @Override
        public Object getThreadHash() {
            return getCallId().getCallId();
        }

    }

    /**
     * This timer task is used to garbage collect the dialog after some time.
     * 
     */

    class DialogDeleteTask extends SIPStackTimerTask implements Serializable {

        public void runTask() {
            delete();
        }

        @Override
        public Object getThreadHash() {
            return getCallId().getCallId();
        }

    }

    /**
     * This timer task is used to garbage collect the dialog after some time.
     * 
     */

    class DialogDeleteIfNoAckSentTask extends SIPStackTimerTask implements
            Serializable {
        private long seqno;

        public DialogDeleteIfNoAckSentTask(long seqno) {
            this.seqno = seqno;
        }
        
        @Override
        public Object getThreadHash() {
            return getCallId().getCallId();
        }        

        public void runTask() {
            if (SIPDialog.this.highestSequenceNumberAcknowledged < seqno) {
                /*
                 * Did not send ACK so we need to delete the dialog. B2BUA NOTE:
                 * we may want to send BYE to the Dialog at this point. Do we
                 * want to make this behavior tailorable?
                 */
                dialogDeleteIfNoAckSentTask = null;
                if (!SIPDialog.this.isBackToBackUserAgent) {
                    if (logger.isLoggingEnabled())
                        logger.logError(
                                "ACK Was not sent. killing dialog " + dialogId);
                    if (((SipProviderImpl) sipProvider).getSipListener() instanceof SipListenerExt) {
                        raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT);
                    } else {
                        delete();
                    }
                } else {
                    if (logger.isLoggingEnabled())
                        logger.logError(
                                "ACK Was not sent. Sending BYE " + dialogId);
                    if (((SipProviderImpl) sipProvider).getSipListener() instanceof SipListenerExt) {
                        raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT);
                    } else {

                        /*
                         * Send BYE to the Dialog. This will be removed for the
                         * next spec revision.
                         */
                        try {
                            Request byeRequest = SIPDialog.this
                                    .createRequest(Request.BYE);
                            if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
                                byeRequest.addHeader(MessageFactoryImpl
                                        .getDefaultUserAgentHeader());
                            }
                            ReasonHeader reasonHeader = new Reason();
                            reasonHeader.setProtocol("SIP");
                            reasonHeader.setCause(1025);
                            reasonHeader
                                    .setText("Timed out waiting to send ACK " + dialogId);
                            byeRequest.addHeader(reasonHeader);
                            ClientTransaction byeCtx = SIPDialog.this
                                    .getSipProvider().getNewClientTransaction(
                                            byeRequest);
                            SIPDialog.this.sendRequest(byeCtx);
                            return;
                        } catch (Exception ex) {
                            SIPDialog.this.delete();
                        }
                    }
                }
            }
        }

    }

    // ///////////////////////////////////////////////////////////
    // Constructors.
    // ///////////////////////////////////////////////////////////
    /**
     * Protected Dialog constructor.
     */
    private SIPDialog(SipProviderImpl provider) {
        this.terminateOnBye = true;
        this.routeList = new RouteList();
        this.dialogState = NULL_STATE; // not yet initialized.
        localSequenceNumber = 0;
        remoteSequenceNumber = -1;
        this.sipProvider = provider;
        eventListeners = new CopyOnWriteArraySet();
        this.earlyDialogTimeout = ((SIPTransactionStack) provider.getSipStack())
                .getEarlyDialogTimeout();
    }

    private void recordStackTrace() {
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        new Exception().printStackTrace(writer);
        String stackTraceSignature = Integer.toString(Math.abs(new Random().nextInt()));
        logger.logDebug("TraceRecord = " + stackTraceSignature);
        this.stackTrace = "TraceRecord = " + stackTraceSignature + ":" +  stringWriter.getBuffer().toString();
    }

    /**
     * Constructor given the first transaction.
     * 
     * @param transaction
     *            is the first transaction.
     */
    public SIPDialog(SIPTransaction transaction) {
        this(transaction.getSipProvider());

        SIPRequest sipRequest = (SIPRequest) transaction.getRequest();
        this.callIdHeader = sipRequest.getCallId();
        this.earlyDialogId = sipRequest.getDialogId(false);
        if (transaction == null)
            throw new NullPointerException("Null tx");
        this.sipStack = transaction.getSIPStack();

        // this.defaultRouter = new DefaultRouter((SipStack) sipStack,
        // sipStack.outboundProxy);

        this.sipProvider = (SipProviderImpl) transaction.getSipProvider();
        if (sipProvider == null)
            throw new NullPointerException("Null Provider!");
        this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent;

        this.addTransaction(transaction);
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug("Creating a dialog : " + this);
            logger.logDebug(
                    "provider port = "
                            + this.sipProvider.getListeningPoint().getPort());
            logger.logStackTrace();
        }
        addEventListener(sipStack);
        releaseReferencesStrategy = sipStack.getReleaseReferencesStrategy();
    }

    /**
     * Constructor given a transaction and a response.
     * 
     * @param transaction
     *            -- the transaction ( client/server)
     * @param sipResponse
     *            -- response with the appropriate tags.
     */
    public SIPDialog(SIPClientTransaction transaction, SIPResponse sipResponse) {
        this(transaction);
        if (sipResponse == null)
            throw new NullPointerException("Null SipResponse");
        this.setLastResponse(transaction, sipResponse);
        this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent;
    }

    /**
     * create a sip dialog with a response ( no tx)
     */
    public SIPDialog(SipProviderImpl sipProvider, SIPResponse sipResponse) {
        this(sipProvider);
        this.sipStack = (SIPTransactionStack) sipProvider.getSipStack();
        this.setLastResponse(null, sipResponse);
        this.localSequenceNumber = sipResponse.getCSeq().getSeqNumber();
        this.originalLocalSequenceNumber = localSequenceNumber;
        this.localParty = sipResponse.getFrom().getAddress();
        this.remoteParty = sipResponse.getTo().getAddress();
        this.method = sipResponse.getCSeq().getMethod();
        this.callIdHeader = sipResponse.getCallId();
        this.serverTransactionFlag = false;
        this.setLocalTag(sipResponse.getFrom().getTag());
        this.setRemoteTag(sipResponse.getTo().getTag());
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug("Creating a dialog : " + this);
            logger.logStackTrace();
        }
        this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent;
        addEventListener(sipStack);
        releaseReferencesStrategy = sipStack.getReleaseReferencesStrategy();
    }
    
    /**
     * Creates a new dialog based on a received NOTIFY. The dialog state is
     * initialized appropriately. The NOTIFY differs in the From tag
     * 
     * Made this a separate method to clearly distinguish what's happening here
     * - this is a non-trivial case
     * 
     * @param subscribeTx
     *            - the transaction started with the SUBSCRIBE that we sent
     * @param notifyST
     *            - the ServerTransaction created for an incoming NOTIFY
     * @return -- a new dialog created from the subscribe original SUBSCRIBE
     *         transaction.
     * 
     * 
     */
    public SIPDialog(SIPClientTransaction subscribeTx, SIPTransaction notifyST) {
        this(notifyST);
        //
        // The above sets firstTransaction to NOTIFY (ST), correct that
        //
        serverTransactionFlag = false;
        // they share this one
        lastTransaction = subscribeTx;
        storeFirstTransactionInfo(this, subscribeTx);
        terminateOnBye = false;
        localSequenceNumber = subscribeTx.getCSeq();
        SIPRequest not = (SIPRequest) notifyST.getRequest();
        remoteSequenceNumber = not.getCSeq().getSeqNumber();
        setDialogId(not.getDialogId(true));
        setLocalTag(not.getToTag());
        setRemoteTag(not.getFromTag());
        // to properly create the Dialog object.
        // If not the stack will throw an exception when creating the response.
        setLastResponse(subscribeTx, subscribeTx.getLastResponse());

        // Dont use setLocal / setRemote here, they make other assumptions
        localParty = not.getTo().getAddress();
        remoteParty = not.getFrom().getAddress();

        // initialize d's route set based on the NOTIFY. Any proxies must have
        // Record-Routed
        addRoute(not);
        setState(CONFIRMED_STATE); // set state, *after* setting route set!
    }
    

    // ///////////////////////////////////////////////////////////
    // Private methods
    // ///////////////////////////////////////////////////////////
    /**
     * A debugging print routine.
     */
    private void printRouteList() {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug("this : " + this);
            logger.logDebug(
                    "printRouteList : " + this.routeList.encode());
        }
    }

    /**
     * Raise an io exception for asyncrhonous retransmission of responses
     * 
     * @param host
     *            -- host to where the io was headed
     * @param port
     *            -- remote port
     * @param protocol
     *            -- protocol (udp/tcp/tls)
     */
    private void raiseIOException(String host, int port, String protocol) {
        // Error occured in retransmitting response.
        // Deliver the error event to the listener
        // Kill the dialog.

        IOExceptionEvent ioError = new IOExceptionEvent(this, host, port,
                protocol);
        sipProvider.handleEvent(ioError, null);

        setState(SIPDialog.TERMINATED_STATE);
    }

    /**
     * Raise a dialog timeout if an ACK has not been sent or received
     * 
     * @param dialogTimeoutError
     */
    private void raiseErrorEvent(int dialogTimeoutError, SIPClientTransaction clientTransaction) {
        // Error event to send to all listeners
        SIPDialogErrorEvent newErrorEvent;
        // Iterator through the list of listeners
        Iterator listenerIterator;
        // Next listener in the list
        SIPDialogEventListener nextListener;

        // Create the error event
        newErrorEvent = new SIPDialogErrorEvent(this, dialogTimeoutError);
        
        
        // The client transaction can be retrieved by the application timeout handler
        // when a re-INVITE is being sent.
        newErrorEvent.setClientTransaction(clientTransaction);

        // Loop through all listeners of this transaction
        synchronized (eventListeners) {
            
            listenerIterator = eventListeners.iterator();
            while (listenerIterator.hasNext()) {
                // Send the event to the next listener
                nextListener = (SIPDialogEventListener) listenerIterator.next();
                nextListener.dialogErrorEvent(newErrorEvent);
            }
        }
        // Clear the event listeners after propagating the error.
        eventListeners.clear();
        // Errors always terminate a dialog except if a timeout has occured
        // because an ACK was not sent or received, then it is the
        // responsibility of the app to terminate
        // the dialog, either by sending a BYE or by calling delete() on the
        // dialog
        if (dialogTimeoutError != SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT
                && dialogTimeoutError != SIPDialogErrorEvent.DIALOG_ACK_NOT_RECEIVED_TIMEOUT
                && dialogTimeoutError != SIPDialogErrorEvent.EARLY_STATE_TIMEOUT
                && dialogTimeoutError != SIPDialogErrorEvent.DIALOG_REINVITE_TIMEOUT) {
            delete();
        }

        // we stop the timer in any case
        stopTimer();
    }
    
    /**
     * Raise a dialog timeout if an ACK has not been sent or received
     * 
     * @param dialogTimeoutError
     */
    private void raiseErrorEvent(int dialogTimeoutError) {
    	raiseErrorEvent(dialogTimeoutError,null);
    }

    /**
     * Set the remote party for this Dialog.
     * 
     * @param sipMessage
     *            -- SIP Message to extract the relevant information from.
     */
    protected void setRemoteParty(SIPMessage sipMessage) {

        if (!isServer()) {

            this.remoteParty = sipMessage.getTo().getAddress();
        } else {
            this.remoteParty = sipMessage.getFrom().getAddress();

        }
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                    "settingRemoteParty " + this.remoteParty);
        }
    }

    /**
     * Add a route list extracted from a record route list. If this is a server
     * dialog then we assume that the record are added to the route list IN
     * order. If this is a client dialog then we assume that the record route
     * headers give us the route list to add in reverse order.
     * 
     * @param recordRouteList
     *            -- the record route list from the incoming message.
     */

    private void addRoute(RecordRouteList recordRouteList) {
        try {
            if (!this.isServer()) {
                // This is a client dialog so we extract the record
                // route from the response and reverse its order to
                // careate a route list.
                this.routeList = new RouteList();
                // start at the end of the list and walk backwards

                ListIterator li = recordRouteList.listIterator(recordRouteList
                        .size());
                while (li.hasPrevious()) {
                    RecordRoute rr = (RecordRoute) li.previous();

                    Route route = new Route();
                    AddressImpl address = ((AddressImpl) ((AddressImpl) rr
                            .getAddress()).clone());

                    route.setAddress(address);
                    route.setParameters((NameValueList) rr.getParameters()
                            .clone());

                    this.routeList.add(route);
                }
            } else {
                // This is a server dialog. The top most record route
                // header is the one that is closest to us. We extract the
                // route list in the same order as the addresses in the
                // incoming request.
                this.routeList = new RouteList();
                ListIterator li = recordRouteList.listIterator();
                while (li.hasNext()) {
                    RecordRoute rr = (RecordRoute) li.next();

                    Route route = new Route();
                    AddressImpl address = ((AddressImpl) ((AddressImpl) rr
                            .getAddress()).clone());
                    route.setAddress(address);
                    route.setParameters((NameValueList) rr.getParameters()
                            .clone());
                    routeList.add(route);

                }
            }
        } finally {
            if (logger.isLoggingEnabled()) {
                Iterator it = routeList.iterator();

                while (it.hasNext()) {
                    SipURI sipUri = (SipURI) (((Route) it.next()).getAddress()
                            .getURI());
                    if (!sipUri.hasLrParam()) {
                        if (logger.isLoggingEnabled()) {
                            logger.logWarning(
                                    "NON LR route in Route set detected for dialog : "
                                            + this);
                            logger.logStackTrace();
                        }
                    } else {
                        if (logger.isLoggingEnabled(
                                LogWriter.TRACE_DEBUG))
                            logger.logDebug(
                                    "route = " + sipUri);
                    }
                }
            }
        }
    }

    /**
     * Add a route list extacted from the contact list of the incoming message.
     * 
     * @param contactList
     *            -- contact list extracted from the incoming message.
     * 
     */

    protected void setRemoteTarget(ContactHeader contact) {
        this.remoteTarget = contact.getAddress();
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                    "Dialog.setRemoteTarget: " + this.remoteTarget);
            logger.logStackTrace();
        }

    }

    /**
     * Extract the route information from this SIP Message and add the relevant
     * information to the route set.
     * 
     * @param sipMessage
     *            is the SIP message for which we want to add the route.
     */
    private synchronized void addRoute(SIPResponse sipResponse) {

        try {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                logger.logDebug(
                        "setContact: dialogState: " + this + "state = "
                                + this.getState());
            }
            if (sipResponse.getStatusCode() == 100) {
                // Do nothing for trying messages.
                return;
            } else if (this.dialogState == TERMINATED_STATE) {
                // Do nothing if the dialog state is terminated.
                return;
            } else if (this.dialogState == CONFIRMED_STATE) {
                // cannot add route list after the dialog is initialized.
                // Remote target is updated on RE-INVITE but not
                // the route list.
                if (sipResponse.getStatusCode() / 100 == 2 && !this.isServer()) {
                    ContactList contactList = sipResponse.getContactHeaders();
                    if (contactList != null
                            && SIPRequest.isTargetRefresh(sipResponse.getCSeq()
                                    .getMethod())) {
                        this.setRemoteTarget((ContactHeader) contactList
                                .getFirst());
                    }
                }
                if (!this.pendingRouteUpdateOn202Response)
                    return;
            }

            // Update route list on response if I am a client dialog.
            if (!isServer() || this.pendingRouteUpdateOn202Response) {

                // only update the route set if the dialog is not in the
                // confirmed state.
                if ((this.getState() != DialogState.CONFIRMED && this
                        .getState() != DialogState.TERMINATED)
                        || this.pendingRouteUpdateOn202Response) {
                    RecordRouteList rrlist = sipResponse
                            .getRecordRouteHeaders();
                    // Add the route set from the incoming response in reverse
                    // order for record route headers.
                    if (rrlist != null) {
                        this.addRoute(rrlist);
                    } else {
                        // Set the rotue list to the last seen route list.
                        this.routeList = new RouteList();
                    }
                }

                ContactList contactList = sipResponse.getContactHeaders();
                if (contactList != null) {
                    this
                            .setRemoteTarget((ContactHeader) contactList
                                    .getFirst());
                }
            }

        } finally {
            if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
                logger.logStackTrace();
            }
        }
    }

    /**
     * Get a cloned copy of route list for the Dialog.
     * 
     * @return -- a cloned copy of the dialog route list.
     */
    private synchronized RouteList getRouteList() {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            logger.logDebug("getRouteList " + this);
        // Find the top via in the route list.
        ListIterator li;
        RouteList retval = new RouteList();

        retval = new RouteList();
        if (this.routeList != null) {
            li = routeList.listIterator();
            while (li.hasNext()) {
                Route route = (Route) li.next();
                retval.add((Route) route.clone());
            }
        }

        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug("----- ");
            logger.logDebug("getRouteList for " + this);
            if (retval != null)
                logger.logDebug(
                        "RouteList = " + retval.encode());
            if (routeList != null)
                logger.logDebug(
                        "myRouteList = " + routeList.encode());
            logger.logDebug("----- ");
        }
        return retval;
    }

    void setRouteList(RouteList routeList) {
        this.routeList = routeList;
    }

    /**
     * Sends ACK Request to the remote party of this Dialogue.
     * 
     * 
     * @param request
     *            the new ACK Request message to send.
     * @param throwIOExceptionAsSipException
     *            - throws SipException if IOEx encountered. Otherwise, no
     *            exception is propagated.
     * @param releaseAckSem
     *            - release ack semaphore.
     * @throws SipException
     *             if implementation cannot send the ACK Request for any other
     *             reason
     * 
     */
    private void sendAck(Request request, boolean throwIOExceptionAsSipException)
            throws SipException {

        SIPRequest ackRequest = (SIPRequest) request;
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            logger.logDebug("sendAck" + this);
        
        if (!ackRequest.getMethod().equals(Request.ACK))
            throw new SipException("Bad request method -- should be ACK");
        if (this.getState() == null
                || this.getState().getValue() == EARLY_STATE) {
            if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) {
                logger.logError(
                        "Bad Dialog State for " + this + " dialogID = "
                                + this.getDialogId());
            }
            throw new SipException("Bad dialog state " + this.getState());
        }

        if (!this.getCallId().getCallId().equals(
                ((SIPRequest) request).getCallId().getCallId())) {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                logger
                        .logError("CallID " + this.getCallId());
                logger
                        .logError(
                                "RequestCallID = "
                                        + ackRequest.getCallId().getCallId());
                logger.logError("dialog =  " + this);
            }
            throw new SipException("Bad call ID in request");
        }
        try {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                logger.logDebug(
                        "setting from tag For outgoing ACK= "
                                + this.getLocalTag());
                logger.logDebug(
                        "setting To tag for outgoing ACK = "
                                + this.getRemoteTag());
                logger.logDebug("ack = " + ackRequest);
            }
            if (this.getLocalTag() != null)
                ackRequest.getFrom().setTag(this.getLocalTag());
            if (this.getRemoteTag() != null)
                ackRequest.getTo().setTag(this.getRemoteTag());
        } catch (ParseException ex) {
            throw new SipException(ex.getMessage());
        }

        boolean releaseAckSem = false;
        long cseqNo = ((SIPRequest) request).getCSeq().getSeqNumber();
        if (!this.isAckSent(cseqNo)) {
            releaseAckSem = true;
        }
        // we clone the request that we store to make sure that if getNextHop modifies the request
        // we store it as it was passed to the method originally
        this.setLastAckSent((SIPRequest)ackRequest.clone());
        
        try {
            ackSendingStrategy.send(ackRequest);            
            // Sent atleast one ACK.
            this.isAcknowledged = true;
            this.highestSequenceNumberAcknowledged = Math.max(
                    this.highestSequenceNumberAcknowledged,
                    ((SIPRequest) ackRequest).getCSeq().getSeqNumber());
            if (releaseAckSem && this.isBackToBackUserAgent) {
                this.releaseAckSem();
            } else {
                if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                    logger.logDebug(
                            "Not releasing ack sem for " + this + " isAckSent "
                                    + releaseAckSem);
                }
            }
        } catch (IOException ex) {
            if (throwIOExceptionAsSipException)
                throw new SipException("Could not send ack", ex);
            Hop hop = ackSendingStrategy.getLastHop();
            if (hop == null) {
              hop = sipStack.getNextHop(ackRequest);
            }
            this.raiseIOException(hop.getHost(), hop.getPort(), hop
                      .getTransport());
        } catch (SipException ex) {
            if (logger.isLoggingEnabled())
                logger.logException(ex);
            throw ex;
        } catch (Exception ex) {
            if (logger.isLoggingEnabled())
                logger.logException(ex);
            throw new SipException("Could not create message channel", ex);
        }
        if (this.dialogDeleteTask != null) {
            this.getStack().getTimer().cancel(dialogDeleteTask);
            this.dialogDeleteTask = null;
        }

    }

    // /////////////////////////////////////////////////////////////
    // Package local methods
    // /////////////////////////////////////////////////////////////

    /**
     * Set the stack address. Prevent us from routing messages to ourselves.
     * 
     * @param sipStack
     *            the address of the SIP stack.
     * 
     */
    void setStack(SIPTransactionStack sipStack) {
        this.sipStack = sipStack;

    }

    /**
     * Get the stack .
     * 
     * @return sipStack the SIP stack of the dialog.
     * 
     */
    SIPTransactionStack getStack() {
        return sipStack;
    }

    /**
     * Return True if this dialog is terminated on BYE.
     * 
     */
    boolean isTerminatedOnBye() {

        return this.terminateOnBye;
    }

    /**
     * Mark that the dialog has seen an ACK.
     */
    void ackReceived(long cseqNumber) {
        // Suppress retransmission of the final response
        if (this.isAckSeen()) {
            if (logger.isLoggingEnabled(
                    LogWriter.TRACE_DEBUG))
                logger.logDebug(
                        "Ack already seen for response -- dropping");
            return;
        }
        SIPServerTransaction tr = this.getInviteTransaction();
        if (tr != null) {
            if (tr.getCSeq() == cseqNumber) {
                acquireTimerTaskSem();
                try {
                    if (this.timerTask != null) {
                        this.getStack().getTimer().cancel(timerTask);
                        this.timerTask = null;
                    }
                } finally {
                    releaseTimerTaskSem();
                }
                if (this.dialogDeleteTask != null) {
                    this.getStack().getTimer().cancel(dialogDeleteTask);
                    this.dialogDeleteTask = null;
                }
                lastAckReceivedCSeqNumber = Long.valueOf(cseqNumber);
                if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                    logger.logDebug(
                            "ackReceived for "
                                    + ((SIPTransaction) tr).getMethod());
                    this.ackLine = logger.getLineCount();
                    this.printDebugInfo();
                }
                if (this.isBackToBackUserAgent) {
                    this.releaseAckSem();
                }
                this.setState(CONFIRMED_STATE);
            }
        } else {
            if (logger.isLoggingEnabled(
                    LogWriter.TRACE_DEBUG))
                logger.logDebug(
                        "tr is null -- not updating the ack state");
        }
    }

    /**
     * Return true if a terminated event was delivered to the application as a
     * result of the dialog termination.
     * 
     */
    synchronized boolean testAndSetIsDialogTerminatedEventDelivered() {
        boolean retval = this.dialogTerminatedEventDelivered;
        this.dialogTerminatedEventDelivered = true;
        return retval;
    }

    // /////////////////////////////////////////////////////////
    // Public methods
    // /////////////////////////////////////////////////////////

    /**
     * Adds a new event listener to this dialog.
     * 
     * @param newListener
     *            Listener to add.
     */
    public void addEventListener(SIPDialogEventListener newListener) {
        eventListeners.add(newListener);
    }

    /**
     * Removed an event listener from this dialog.
     * 
     * @param oldListener
     *            Listener to remove.
     */
    public void removeEventListener(SIPDialogEventListener oldListener) {
        eventListeners.remove(oldListener);
    }

    /*
     * @see android.javax.sip.Dialog#setApplicationData()
     */
    public void setApplicationData(Object applicationData) {
        this.applicationData = applicationData;
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.javax.sip.Dialog#getApplicationData()
     */
    public Object getApplicationData() {
        return this.applicationData;
    }

    /**
     * Updates the next consumable seqno.
     * 
     */
    public synchronized void requestConsumed() {
        this.nextSeqno = this.getRemoteSeqNumber() + 1;

        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                    "Request Consumed -- next consumable Request Seqno = "
                            + this.nextSeqno);
        }

    }

    /**
     * Return true if this request can be consumed by the dialog.
     * 
     * @param dialogRequest
     *            is the request to check with the dialog.
     * @return true if the dialogRequest sequence number matches the next
     *         consumable seqno.
     */
    public synchronized boolean isRequestConsumable(SIPRequest dialogRequest) {
        // have not yet set remote seqno - this is a fresh
        if (dialogRequest.getMethod().equals(Request.ACK))
            throw new RuntimeException("Illegal method");

        // For loose validation this function is delegated to the application
        if (!this.isSequenceNumberValidation()) {
            return true;
        }

        // JvB: Acceptable iff remoteCSeq < cseq. remoteCSeq==-1
        // when not defined yet, so that works too
        return remoteSequenceNumber < dialogRequest.getCSeq().getSeqNumber();
    }

    /**
     * This method is called when a forked dialog is created from the client
     * side. It starts a timer task. If the timer task expires before an ACK is
     * sent then the dialog is cancelled (i.e. garbage collected ).
     * 
     */
    public void doDeferredDelete() {
        if (sipStack.getTimer() == null)
            this.setState(TERMINATED_STATE);
        else {
            this.dialogDeleteTask = new DialogDeleteTask();
            // Delete the transaction after the max ack timeout.
            if (sipStack.getTimer() != null && sipStack.getTimer().isStarted()) {
            	int delay = SIPTransactionStack.BASE_TIMER_INTERVAL;
            	if(lastTransaction != null) {
            		delay = lastTransaction.getBaseTimerInterval();
            	}
            	sipStack.getTimer().schedule(
                    this.dialogDeleteTask,
                    SIPTransaction.TIMER_H
                            * delay);
            } else {
            	this.delete();
            }
        }

    }

    /**
     * Set the state for this dialog.
     * 
     * @param state
     *            is the state to set for the dialog.
     */

    public void setState(int state) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                    "SIPDialog::setState:Setting dialog state for " + this + "newState = " + state);
            logger.logStackTrace();
            if (state != NULL_STATE && state != this.dialogState)
                if (logger.isLoggingEnabled()) {
                    logger.logDebug("SIPDialog::setState:" +	
                            this + "  old dialog state is " + this.getState());
                    logger.logDebug("SIPDialog::setState:" +
                            this + "  New dialog state is "
                                    + DialogState.getObject(state));
                }

        }
        if ( state == EARLY_STATE ) {
            this.addEventListener(this.getSipProvider());
        }

        this.dialogState = state;
        // Dialog is in terminated state set it up for GC.
        if (state == TERMINATED_STATE) {
            this.removeEventListener(this.getSipProvider());
            if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { // may be null after shutdown
            	if(sipStack.getConnectionLingerTimer() > 0) {
                sipStack.getTimer().schedule(new LingerTimer(),
                        sipStack.getConnectionLingerTimer() * 1000);
            	} else {
            		new LingerTimer().runTask();
            	}
            }
            this.stopTimer();

        }
    }

    /**
     * Debugging print for the dialog.
     */
    public void printDebugInfo() {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug("isServer = " + isServer());
            logger.logDebug("localTag = " + getLocalTag());
            logger.logDebug("remoteTag = " + getRemoteTag());
            logger.logDebug(
                    "localSequenceNumer = " + getLocalSeqNumber());
            logger.logDebug(
                    "remoteSequenceNumer = " + getRemoteSeqNumber());
            logger.logDebug(
                    "ackLine:" + this.getRemoteTag() + " " + ackLine);
        }
    }

    /**
     * Return true if the dialog has already seen the ack.
     * 
     * @return flag that records if the ack has been seen.
     */
    public boolean isAckSeen() {

        if (lastAckReceivedCSeqNumber == null
                && lastResponseStatusCode == Response.OK) {
            if (logger.isLoggingEnabled(
                    LogWriter.TRACE_DEBUG)) {
                logger.logDebug("SIPDialog::isAckSeen:"+
                        this + "lastAckReceived is null -- returning false");
            }
            return false;
        } else if (lastResponseMethod == null) {
            if (logger.isLoggingEnabled(
                    LogWriter.TRACE_DEBUG)) {
                logger.logDebug("SIPDialog::isAckSeen:"+
                        this + "lastResponse is null -- returning false");
            }
            return false;
        } else if (lastAckReceivedCSeqNumber == null
                && lastResponseStatusCode / 100 > 2) {
            if (logger.isLoggingEnabled(
                    LogWriter.TRACE_DEBUG)) {
                logger.logDebug("SIPDialog::isAckSeen:"+
                        this + "lastResponse statusCode "
                                + lastResponseStatusCode);
            }
            return true;
        } else {
        	if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        		logger.logDebug("SIPDialog::isAckSeen:lastAckReceivedCSeqNumber = " + lastAckReceivedCSeqNumber + " remoteCSeqNumber = " + this.getRemoteSeqNumber());
        	}
            return this.lastAckReceivedCSeqNumber != null
                    && this.lastAckReceivedCSeqNumber >= this
                            .getRemoteSeqNumber();
        }
    }

    /**
     * Get the last ACK for this transaction.
     */
    public SIPRequest getLastAckSent() {
        return this.lastAckSent;
    }

    /**
     * Return true if ACK was sent ( for client tx ). For server tx, this is a
     * NO-OP ( we dont send ACK).
     */
    public boolean isAckSent(long cseqNo) {
        if (this.getLastTransaction() == null)
            return true;
        if (this.getLastTransaction() instanceof ClientTransaction) {
            if (this.getLastAckSent() == null) {
                return false;
            } else {
                return cseqNo <= ((SIPRequest) this.getLastAckSent()).getCSeq()
                        .getSeqNumber();
            }
        } else {
            return true;
        }
    }

    @Deprecated
    public Transaction getFirstTransaction() {
        throw new UnsupportedOperationException(
                "This method has been deprecated and is no longer supported");
    }

    /**
     * This is for internal use only.
     * 
     */
    public Transaction getFirstTransactionInt() {
        // jeand : we try to avoid keeping the ref around for too long to help
        // the GC
        if (firstTransaction != null) {
            return firstTransaction;
        }
        return this.sipStack.findTransaction(firstTransactionId,
                firstTransactionIsServerTransaction);
    }

    /**
     * Gets the route set for the dialog. When acting as an User Agent Server
     * the route set MUST be set to the list of URIs in the Record-Route header
     * field from the request, taken in order and preserving all URI parameters.
     * When acting as an User Agent Client the route set MUST be set to the list
     * of URIs in the Record-Route header field from the response, taken in
     * reverse order and preserving all URI parameters. If no Record-Route
     * header field is present in the request or response, the route set MUST be
     * set to the empty set. This route set, even if empty, overrides any
     * pre-existing route set for future requests in this dialog.
     * 

* Requests within a dialog MAY contain Record-Route and Contact header * fields. However, these requests do not cause the dialog's route set to be * modified. *

* The User Agent Client uses the remote target and route set to build the * Request-URI and Route header field of the request. * * @return an Iterator containing a list of route headers to be used for * forwarding. Empty iterator is returned if route has not been * established. */ public Iterator getRouteSet() { if (this.routeList == null) { return new LinkedList().listIterator(); } else { return this.getRouteList().listIterator(); } } /** * Add a Route list extracted from a SIPRequest to this Dialog. * * @param sipRequest */ public synchronized void addRoute(SIPRequest sipRequest) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "setContact: dialogState: " + this + "state = " + this.getState()); } if (this.dialogState == CONFIRMED_STATE && SIPRequest.isTargetRefresh(sipRequest.getMethod())) { this.doTargetRefresh(sipRequest); } if (this.dialogState == CONFIRMED_STATE || this.dialogState == TERMINATED_STATE) { return; } // put the contact header from the incoming request into // the route set. JvB: some duplication here, ref. doTargetRefresh ContactList contactList = sipRequest.getContactHeaders(); if (contactList != null) { this.setRemoteTarget((ContactHeader) contactList.getFirst()); } // Fix for issue #225: mustn't learn Route set from mid-dialog requests if (sipRequest.getToTag() != null) return; // Incoming Request has the route list RecordRouteList rrlist = sipRequest.getRecordRouteHeaders(); // Add the route set from the incoming response in reverse // order if (rrlist != null) { this.addRoute(rrlist); } else { // Set the rotue list to the last seen route list. this.routeList = new RouteList(); } } /** * Set the dialog identifier. */ public void setDialogId(String dialogId) { if (firstTransaction != null) { firstTransaction.setDialog(this, dialogId); } this.dialogId = dialogId; } /** * Return true if is server. * * @return true if is server transaction created this dialog. */ public boolean isServer() { if (this.firstTransactionSeen == false) return this.serverTransactionFlag; else return this.firstTransactionIsServerTransaction; } /** * Return true if this is a re-establishment of the dialog. * * @return true if the reInvite flag is set. */ protected boolean isReInvite() { return this.reInviteFlag; } /** * Get the id for this dialog. * * @return the string identifier for this dialog. * */ public String getDialogId() { if (this.dialogId == null && this.lastResponseDialogId != null) this.dialogId = this.lastResponseDialogId; return this.dialogId; } protected void storeFirstTransactionInfo(SIPDialog dialog, SIPTransaction transaction) { dialog.firstTransactionSeen = true; dialog.firstTransaction = transaction; dialog.firstTransactionIsServerTransaction = transaction .isServerTransaction(); if (dialog.firstTransactionIsServerTransaction) { dialog.firstTransactionSecure = transaction.getRequest() .getRequestURI().getScheme().equalsIgnoreCase("sips"); } else { dialog.firstTransactionSecure = ((SIPClientTransaction) transaction) .getOriginalRequestScheme().equalsIgnoreCase("sips"); } dialog.firstTransactionPort = transaction.getPort(); dialog.firstTransactionId = transaction.getBranchId(); dialog.firstTransactionMethod = transaction.getMethod(); if (transaction instanceof SIPServerTransaction && dialog.firstTransactionMethod.equals(Request.INVITE)) { sipStack.removeMergeDialog(firstTransactionMergeId); dialog.firstTransactionMergeId = transaction.getMergeId(); sipStack.putMergeDialog(this); } if (transaction.isServerTransaction()) { SIPServerTransaction st = (SIPServerTransaction) transaction; SIPResponse response = st.getLastResponse(); dialog.contactHeader = response != null ? response .getContactHeader() : null; } else { SIPClientTransaction ct = (SIPClientTransaction) transaction; if (ct != null) { dialog.contactHeader = ct.getOriginalRequestContact(); } } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("firstTransaction = " + dialog.firstTransaction); logger.logDebug("firstTransactionIsServerTransaction = " + firstTransactionIsServerTransaction); logger.logDebug("firstTransactionSecure = " + firstTransactionSecure); logger.logDebug("firstTransactionPort = " + firstTransactionPort); logger.logDebug("firstTransactionId = " + firstTransactionId); logger.logDebug("firstTransactionMethod = " + firstTransactionMethod); logger.logDebug("firstTransactionMergeId = " + firstTransactionMergeId); } } /** * Add a transaction record to the dialog. * * @param transaction * is the transaction to add to the dialog. */ public boolean addTransaction(SIPTransaction transaction) { SIPRequest sipRequest = (SIPRequest) transaction.getOriginalRequest(); // Proessing a re-invite. if (firstTransactionSeen && !firstTransactionId.equals(transaction.getBranchId()) && transaction.getMethod().equals(firstTransactionMethod)) { setReInviteFlag(true); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SipDialog.addTransaction() " + this + " transaction = " + transaction); } if (firstTransactionSeen == false) { // Record the local and remote sequenc // numbers and the from and to tags for future // use on this dialog. storeFirstTransactionInfo(this, transaction); if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) this.eventHeader = (EventHeader) sipRequest .getHeader(EventHeader.NAME); this.setLocalParty(sipRequest); this.setRemoteParty(sipRequest); this.setCallId(sipRequest); if (this.originalRequest == null && transaction.isInviteTransaction()) { this.originalRequest = sipRequest; } else if (originalRequest != null) { originalRequestRecordRouteHeaders = sipRequest .getRecordRouteHeaders(); } if (this.method == null) { this.method = sipRequest.getMethod(); } if (transaction instanceof SIPServerTransaction) { this.hisTag = sipRequest.getFrom().getTag(); // My tag is assigned when sending response } else { setLocalSequenceNumber(sipRequest.getCSeq().getSeqNumber()); this.originalLocalSequenceNumber = getLocalSeqNumber(); this.setLocalTag(sipRequest.getFrom().getTag()); if (myTag == null) if (logger.isLoggingEnabled()) logger .logError( "The request's From header is missing the required Tag parameter."); } } else if (transaction.getMethod().equals(firstTransactionMethod) && firstTransactionIsServerTransaction != transaction .isServerTransaction()) { // This case occurs when you are processing a re-invite. // Switch from client side to server side for re-invite // (put the other side on hold). storeFirstTransactionInfo(this, transaction); this.setLocalParty(sipRequest); this.setRemoteParty(sipRequest); this.setCallId(sipRequest); if (transaction.isInviteTransaction()) { this.originalRequest = sipRequest; } else { originalRequestRecordRouteHeaders = sipRequest .getRecordRouteHeaders(); } this.method = sipRequest.getMethod(); } else if (firstTransaction == null && transaction.isInviteTransaction()) { // jeand needed for reinvite reliable processing firstTransaction = transaction; } if (transaction instanceof SIPServerTransaction) { setRemoteSequenceNumber(sipRequest.getCSeq().getSeqNumber()); } // If this is a server transaction record the remote // sequence number to avoid re-processing of requests // with the same sequence number directed towards this // dialog. if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "isBackToBackUserAgent = " + this.isBackToBackUserAgent); } if (transaction.isInviteTransaction()) { if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("SIPDialog::setLastTransaction:dialog= " + SIPDialog.this + " lastTransaction = " + transaction); } this.lastTransaction = transaction; } try { if (transaction.getRequest().getMethod().equals(Request.REFER) && transaction instanceof SIPServerTransaction) { /* * RFC-3515 Section - 2.4.6, if there are multiple REFER transactions in a dialog then the * NOTIFY MUST include an id parameter in the Event header containing the sequence number * (the number from the CSeq header field value) of the REFER this NOTIFY is associated with. * This id parameter MAY be included in NOTIFYs to the first REFER a UA receives in a given dialog */ long lastReferCSeq = ((SIPRequest) transaction.getRequest()).getCSeq().getSeqNumber(); this.eventHeader = new Event(); this.eventHeader.setEventType("refer"); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("SIPDialog::setLastTransaction:lastReferCSeq = " + lastReferCSeq); } this.eventHeader.setEventId(Long.toString(lastReferCSeq)); } } catch (Exception ex) { logger.logFatalError("Unexpected exception in REFER processing"); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "Transaction Added " + this + myTag + "/" + hisTag); logger.logDebug( "TID = " + transaction.getTransactionId() + "/" + transaction.isServerTransaction()); logger.logStackTrace(); } return true; } /** * Set the remote tag. * * @param hisTag * is the remote tag to set. */ protected void setRemoteTag(String hisTag) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "setRemoteTag(): " + this + " remoteTag = " + this.hisTag + " new tag = " + hisTag); } if (this.hisTag != null && hisTag != null && !hisTag.equals(this.hisTag)) { if (this.getState() != DialogState.EARLY) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger .logDebug( "Dialog is already established -- ignoring remote tag re-assignment"); return; } else if (sipStack.isRemoteTagReassignmentAllowed()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger .logDebug( "UNSAFE OPERATION ! tag re-assignment " + this.hisTag + " trying to set to " + hisTag + " can cause unexpected effects "); boolean removed = false; if (this.sipStack.getDialog(dialogId) == this) { this.sipStack.removeDialog(dialogId); removed = true; } // Force recomputation of Dialog ID; this.dialogId = null; this.hisTag = hisTag; if (removed) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger .logDebug("ReInserting Dialog"); this.sipStack.putDialog(this); } } } else { if (hisTag != null) { this.hisTag = hisTag; } else { if (logger.isLoggingEnabled()) logger.logWarning( "setRemoteTag : called with null argument "); } } } /** * Get the last transaction from the dialog. */ public SIPTransaction getLastTransaction() { return this.lastTransaction; } /** * Get the INVITE transaction (null if no invite transaction). */ public SIPServerTransaction getInviteTransaction() { DialogTimerTask t = this.timerTask; if (t != null) return t.transaction; else return null; } /** * Set the local sequece number for the dialog (defaults to 1 when the * dialog is created). * * @param lCseq * is the local cseq number. * */ private void setLocalSequenceNumber(long lCseq) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "setLocalSequenceNumber: original " + this.localSequenceNumber + " new = " + lCseq); if (lCseq <= this.localSequenceNumber) throw new RuntimeException("Sequence number should not decrease !"); this.localSequenceNumber = lCseq; } /** * Set the remote sequence number for the dialog. * * @param rCseq * is the remote cseq number. * */ public void setRemoteSequenceNumber(long rCseq) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "setRemoteSeqno " + this + "/" + rCseq); this.remoteSequenceNumber = rCseq; } /** * Increment the local CSeq # for the dialog. This is useful for if you want * to create a hole in the sequence number i.e. route a request outside the * dialog and then resume within the dialog. */ public void incrementLocalSequenceNumber() { ++this.localSequenceNumber; } /** * Get the remote sequence number (for cseq assignment of outgoing requests * within this dialog). * * @deprecated * @return local sequence number. */ public int getRemoteSequenceNumber() { return (int) this.remoteSequenceNumber; } /** * Get the local sequence number (for cseq assignment of outgoing requests * within this dialog). * * @deprecated * @return local sequence number. */ public int getLocalSequenceNumber() { return (int) this.localSequenceNumber; } /** * Get the sequence number for the request that origianlly created the * Dialog. * * @return -- the original starting sequence number for this dialog. */ public long getOriginalLocalSequenceNumber() { return this.originalLocalSequenceNumber; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getLocalSequenceNumberLong() */ public long getLocalSeqNumber() { return this.localSequenceNumber; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getRemoteSequenceNumberLong() */ public long getRemoteSeqNumber() { return this.remoteSequenceNumber; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getLocalTag() */ public String getLocalTag() { return this.myTag; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getRemoteTag() */ public String getRemoteTag() { return hisTag; } /** * Set local tag for the transaction. * * @param mytag * is the tag to use in From headers client transactions that * belong to this dialog and for generating To tags for Server * transaction requests that belong to this dialog. */ protected void setLocalTag(String mytag) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "set Local tag " + mytag + " dialog = " + this); logger.logStackTrace(); } this.myTag = mytag; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#delete() */ public void delete() { // the reaper will get him later. this.setState(TERMINATED_STATE); } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getCallId() */ public CallIdHeader getCallId() { // jeand : we save the header in a string form and reparse it, help GC // for dialogs updated not too often if (callIdHeader == null && callIdHeaderString != null) { try { this.callIdHeader = (CallIdHeader) new CallIDParser( callIdHeaderString).parse(); } catch (ParseException e) { logger.logError( "error reparsing the call id header", e); } } return this.callIdHeader; } /** * set the call id header for this dialog. */ private void setCallId(SIPRequest sipRequest) { this.callIdHeader = sipRequest.getCallId(); } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getLocalParty() */ public android.javax.sip.address.Address getLocalParty() { // jeand : we save the address in a string form and reparse it, help GC // for dialogs updated not too often if (localParty == null && localPartyStringified != null) { try { this.localParty = (Address) new AddressParser( localPartyStringified).address(true); } catch (ParseException e) { logger.logError( "error reparsing the localParty", e); } } return this.localParty; } protected void setLocalParty(SIPMessage sipMessage) { if (!isServer()) { this.localParty = sipMessage.getFrom().getAddress(); } else { this.localParty = sipMessage.getTo().getAddress(); } } /** * Returns the Address identifying the remote party. This is the value of * the To header of locally initiated requests in this dialogue when acting * as an User Agent Client. *

* This is the value of the From header of recieved responses in this * dialogue when acting as an User Agent Server. * * @return the address object of the remote party. */ public android.javax.sip.address.Address getRemoteParty() { // jeand : we save the address in a string form and reparse it, help GC // for dialogs updated not too often if (remoteParty == null && remotePartyStringified != null) { try { this.remoteParty = (Address) new AddressParser( remotePartyStringified).address(true); } catch (ParseException e) { logger.logError( "error reparsing the remoteParty", e); } } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "gettingRemoteParty " + this.remoteParty); } return this.remoteParty; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getRemoteTarget() */ public android.javax.sip.address.Address getRemoteTarget() { // jeand : we save the address in a string form and reparse it, help GC // for dialogs updated not too often if (remoteTarget == null && remoteTargetStringified != null) { try { this.remoteTarget = (Address) new AddressParser( remoteTargetStringified).address(true); } catch (ParseException e) { logger.logError( "error reparsing the remoteTarget", e); } } return this.remoteTarget; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#getState() */ public DialogState getState() { if (this.dialogState == NULL_STATE) return null; // not yet initialized return DialogState.getObject(this.dialogState); } /** * Returns true if this Dialog is secure i.e. if the request arrived over * TLS, and the Request-URI contained a SIPS URI, the "secure" flag is set * to TRUE. * * @return true if this dialogue was established using a sips * URI over TLS, and false otherwise. */ public boolean isSecure() { return this.firstTransactionSecure; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#sendAck(android.javax.sip.message.Request) */ public void sendAck(Request request) throws SipException { this.sendAck(request, true); } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#createRequest(java.lang.String) */ public Request createRequest(String method) throws SipException { if (method.equals(Request.ACK) || method.equals(Request.PRACK)) { throw new SipException( "Invalid method specified for createRequest:" + method); } if (lastResponseTopMostVia != null) return this.createRequest(method, this.lastResponseTopMostVia .getTransport()); else throw new SipException("Dialog not yet established -- no response!"); } /** * The method that actually does the work of creating a request. * * @param method * @param response * @return * @throws SipException */ private SIPRequest createRequest(String method, String topMostViaTransport) throws SipException { /* * Check if the dialog is in the right state (RFC 3261 section 15). The * caller's UA MAY send a BYE for either CONFIRMED or EARLY dialogs, and * the callee's UA MAY send a BYE on CONFIRMED dialogs, but MUST NOT * send a BYE on EARLY dialogs. * * Throw out cancel request. */ if (method == null || topMostViaTransport == null) throw new NullPointerException("null argument"); if (method.equals(Request.CANCEL)) throw new SipException("Dialog.createRequest(): Invalid request"); if (this.getState() == null || (this.getState().getValue() == TERMINATED_STATE && !method .equalsIgnoreCase(Request.BYE)) || (this.isServer() && this.getState().getValue() == EARLY_STATE && method .equalsIgnoreCase(Request.BYE))) throw new SipException("Dialog " + getDialogId() + " not yet established or terminated " + this.getState()); SipUri sipUri = null; if (this.getRemoteTarget() != null) sipUri = (SipUri) this.getRemoteTarget().getURI().clone(); else { sipUri = (SipUri) this.getRemoteParty().getURI().clone(); sipUri.clearUriParms(); } CSeq cseq = new CSeq(); try { cseq.setMethod(method); cseq.setSeqNumber(this.getLocalSeqNumber()); } catch (Exception ex) { if (logger.isLoggingEnabled()) logger.logError("Unexpected error"); InternalErrorHandler.handleException(ex); } /* * Add a via header for the outbound request based on the transport of * the message processor. */ ListeningPointImpl lp = (ListeningPointImpl) this.sipProvider .getListeningPoint(topMostViaTransport); if (lp == null) { if (logger.isLoggingEnabled()) logger.logError( "Cannot find listening point for transport " + topMostViaTransport); throw new SipException("Cannot find listening point for transport " + topMostViaTransport); } Via via = lp.getViaHeader(); From from = new From(); from.setAddress(this.getLocalParty()); To to = new To(); to.setAddress(this.getRemoteParty()); SIPRequest sipRequest = createRequest(sipUri, via, cseq, from, to); /* * The default contact header is obtained from the provider. The * application can override this. * * JvB: Should only do this for target refresh requests, ie not for BYE, * PRACK, etc */ if (SIPRequest.isTargetRefresh(method)) { ContactHeader contactHeader = ((ListeningPointImpl) this.sipProvider .getListeningPoint(lp.getTransport())) .createContactHeader(); ((SipURI) contactHeader.getAddress().getURI()).setSecure(this .isSecure()); sipRequest.setHeader(contactHeader); } try { /* * Guess of local sequence number - this is being re-set when the * request is actually dispatched */ cseq = (CSeq) sipRequest.getCSeq(); cseq.setSeqNumber(getLocalSeqNumber() + 1); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SIPDialog::createRequest:setting Request Seq Number to " + cseq.getSeqNumber()); } } catch (InvalidArgumentException ex) { InternalErrorHandler.handleException(ex); } if (method.equals(Request.SUBSCRIBE)) { if (eventHeader != null) sipRequest.addHeader(eventHeader); } /* * RFC-3515 Section - 2.4.6, if there are multiple REFER transactions in a dialog then the * NOTIFY MUST include an id parameter in the Event header containing the sequence number * (the number from the CSeq header field value) of the REFER this NOTIFY is associated with. * This id parameter MAY be included in NOTIFYs to the first REFER a UA receives in a given dialog */ if (method.equals(Request.NOTIFY)) { if (eventHeader != null ) { sipRequest.addHeader(eventHeader); } } /* * RFC3261, section 12.2.1.1: * * The URI in the To field of the request MUST be set to the remote URI * from the dialog state. The tag in the To header field of the request * MUST be set to the remote tag of the dialog ID. The From URI of the * request MUST be set to the local URI from the dialog state. The tag * in the From header field of the request MUST be set to the local tag * of the dialog ID. If the value of the remote or local tags is null, * the tag parameter MUST be omitted from the To or From header fields, * respectively. */ try { if (this.getLocalTag() != null) { from.setTag(this.getLocalTag()); } else { from.removeTag(); } if (this.getRemoteTag() != null) { to.setTag(this.getRemoteTag()); } else { to.removeTag(); } } catch (ParseException ex) { InternalErrorHandler.handleException(ex); } // get the route list from the dialog. this.updateRequest(sipRequest); return sipRequest; } /** * Generate a request from a response. * * @param requestURI * -- the request URI to assign to the request. * @param via * -- the Via header to assign to the request * @param cseq * -- the CSeq header to assign to the request * @param from * -- the From header to assign to the request * @param to * -- the To header to assign to the request * @return -- the newly generated sip request. */ public SIPRequest createRequest(SipUri requestURI, Via via, CSeq cseq, From from, To to) { SIPRequest newRequest = new SIPRequest(); String method = cseq.getMethod(); newRequest.setMethod(method); newRequest.setRequestURI(requestURI); this.setBranch(via, method); newRequest.setHeader(via); newRequest.setHeader(cseq); newRequest.setHeader(from); newRequest.setHeader(to); newRequest.setHeader(getCallId()); try { // JvB: all requests need a Max-Forwards newRequest.attachHeader(new MaxForwards(70), false); } catch (Exception d) { } if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { newRequest .setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); } return newRequest; } /** * Sets the Via branch for CANCEL or ACK requests * * @param via * @param method * @throws ParseException */ private final void setBranch(Via via, String method) { String branch; if (method.equals(Request.ACK)) { if (getLastResponseStatusCode().intValue() >= 300) { branch = lastResponseTopMostVia.getBranch(); // non-2xx ACK uses // same branch } else { branch = Utils.getInstance().generateBranchId(); // 2xx ACK gets // new branch } } else if (method.equals(Request.CANCEL)) { branch = lastResponseTopMostVia.getBranch(); // CANCEL uses same // branch } else return; try { via.setBranch(branch); } catch (ParseException e) { e.printStackTrace(); } } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#sendRequest(android.javax.sip.ClientTransaction) */ public void sendRequest(ClientTransaction clientTransactionId) throws TransactionDoesNotExistException, SipException { this.sendRequest(clientTransactionId, !this.isBackToBackUserAgent); } public void sendRequest(ClientTransaction clientTransaction, boolean allowInterleaving) throws TransactionDoesNotExistException, SipException { if (clientTransaction == null) throw new NullPointerException("null parameter"); if ((!allowInterleaving) && clientTransaction.getRequest().getMethod().equals( Request.INVITE)) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("SIPDialog::sendRequest " + this + " clientTransaction = " + clientTransaction); } sipStack.getReinviteExecutor().execute( (new ReInviteSender(clientTransaction))); return; } SIPRequest dialogRequest = ((SIPClientTransaction) clientTransaction) .getOriginalRequest(); this.proxyAuthorizationHeader = (ProxyAuthorizationHeader) dialogRequest .getHeader(ProxyAuthorizationHeader.NAME); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "SIPDialog::sendRequest:dialog.sendRequest " + " dialog = " + this + "\ndialogRequest = \n" + dialogRequest); if (dialogRequest.getMethod().equals(Request.ACK) || dialogRequest.getMethod().equals(Request.CANCEL)) throw new SipException("Bad Request Method. " + dialogRequest.getMethod()); // JvB: added, allow re-sending of BYE after challenge if (byeSent && isTerminatedOnBye() && !dialogRequest.getMethod().equals(Request.BYE)) { if (logger.isLoggingEnabled()) logger.logError( "BYE already sent for " + this); throw new SipException("Cannot send request; BYE already sent"); } if (dialogRequest.getTopmostVia() == null) { Via via = ((SIPClientTransaction) clientTransaction) .getOutgoingViaHeader(); dialogRequest.addHeader(via); } if (!this.getCallId().getCallId().equalsIgnoreCase( dialogRequest.getCallId().getCallId())) { if (logger.isLoggingEnabled()) { logger .logError("CallID " + this.getCallId()); logger.logError( "SIPDialog::sendRequest:RequestCallID = " + dialogRequest.getCallId().getCallId()); logger.logError("dialog = " + this); } throw new SipException("Bad call ID in request"); } // Set the dialog back pointer. ((SIPClientTransaction) clientTransaction).setDialog(this, this.dialogId); this.addTransaction((SIPTransaction) clientTransaction); // Enable the retransmission filter for the transaction ((SIPClientTransaction) clientTransaction).setTransactionMapped(true); From from = (From) dialogRequest.getFrom(); To to = (To) dialogRequest.getTo(); // Caller already did the tag assignment -- check to see if the // tag assignment is OK. if (this.getLocalTag() != null && from.getTag() != null && !from.getTag().equals(this.getLocalTag())) throw new SipException("From tag mismatch expecting " + this.getLocalTag()); if (this.getRemoteTag() != null && to.getTag() != null && !to.getTag().equals(this.getRemoteTag())) { if (logger.isLoggingEnabled()) this.logger.logWarning( "SIPDialog::sendRequest:To header tag mismatch expecting " + this.getRemoteTag()); } /* * The application is sending a NOTIFY before sending the response of * the dialog. */ if (this.getLocalTag() == null && dialogRequest.getMethod().equals(Request.NOTIFY)) { if (!this.getMethod().equals(Request.SUBSCRIBE)) throw new SipException( "Trying to send NOTIFY without SUBSCRIBE Dialog!"); this.setLocalTag(from.getTag()); } try { if (this.getLocalTag() != null) from.setTag(this.getLocalTag()); if (this.getRemoteTag() != null) to.setTag(this.getRemoteTag()); } catch (ParseException ex) { InternalErrorHandler.handleException(ex); } Hop hop = ((SIPClientTransaction) clientTransaction).getNextHop(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SIPDialog::sendRequest:Using hop = " + hop.getHost() + " : " + hop.getPort()); } try { MessageChannel messageChannel = sipStack.createRawMessageChannel( this.getSipProvider().getListeningPoint(hop.getTransport()) .getIPAddress(), this.firstTransactionPort, hop); MessageChannel oldChannel = ((SIPClientTransaction) clientTransaction) .getMessageChannel(); // Remove this from the connection cache if it is in the // connection // cache and is not yet active. oldChannel.uncache(); // Not configured to cache client connections. if (!sipStack.cacheClientConnections) { oldChannel.useCount--; if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "SIPDialog::sendRequest:oldChannel: useCount " + oldChannel.useCount); } if (messageChannel == null) { /* * At this point the procedures of 8.1.2 and 12.2.1.1 of RFC3261 * have been tried but the resulting next hop cannot be resolved * (recall that the exception thrown is caught and ignored in * SIPStack.createMessageChannel() so we end up here with a null * messageChannel instead of the exception handler below). All * else failing, try the outbound proxy in accordance with * 8.1.2, in particular: This ensures that outbound proxies that * do not add Record-Route header field values will drop out of * the path of subsequent requests. It allows endpoints that * cannot resolve the first Route URI to delegate that task to * an outbound proxy. * * if one considers the 'first Route URI' of a request * constructed according to 12.2.1.1 to be the request URI when * the route set is empty. */ if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Null message channel using outbound proxy !"); Hop outboundProxy = sipStack.getRouter(dialogRequest) .getOutboundProxy(); if (outboundProxy == null) throw new SipException("No route found! hop=" + hop); messageChannel = sipStack.createRawMessageChannel(this .getSipProvider().getListeningPoint( outboundProxy.getTransport()).getIPAddress(), this.firstTransactionPort, outboundProxy); if (messageChannel != null) ((SIPClientTransaction) clientTransaction) .setEncapsulatedChannel(messageChannel); } else { ((SIPClientTransaction) clientTransaction) .setEncapsulatedChannel(messageChannel); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SIPDialog::sendRequest:using message channel " + messageChannel); } } if (messageChannel != null) messageChannel.useCount++; // See if we need to release the previously mapped channel. if ((!sipStack.cacheClientConnections) && oldChannel != null && oldChannel.useCount <= 0) oldChannel.close(); } catch (Exception ex) { if (logger.isLoggingEnabled()) logger.logException(ex); throw new SipException("Could not create message channel", ex); } try { // Increment before setting!! long cseqNumber = dialogRequest.getCSeq() == null?getLocalSeqNumber():dialogRequest.getCSeq().getSeqNumber(); if(cseqNumber > getLocalSeqNumber()) { setLocalSequenceNumber(cseqNumber); } else { setLocalSequenceNumber(getLocalSeqNumber() + 1); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SIPDialog::sendRequest:setting Seq Number to " + getLocalSeqNumber()); } dialogRequest.getCSeq().setSeqNumber(getLocalSeqNumber()); } catch (InvalidArgumentException ex) { logger.logFatalError(ex.getMessage()); } try { ((SIPClientTransaction) clientTransaction) .sendMessage(dialogRequest); /* * Note that if the BYE is rejected then the Dialog should bo back * to the ESTABLISHED state so we only set state after successful * send. */ if (dialogRequest.getMethod().equals(Request.BYE)) { this.byeSent = true; /* * Dialog goes into TERMINATED state as soon as BYE is sent. * ISSUE 182. */ if (isTerminatedOnBye()) { this.setState(DialogState._TERMINATED); } } } catch (IOException ex) { throw new SipException("error sending message", ex); } } /** * Return yes if the last response is to be retransmitted. */ private boolean toRetransmitFinalResponse(int T2) { if (--retransmissionTicksLeft == 0) { if (2 * prevRetransmissionTicks <= T2) this.retransmissionTicksLeft = 2 * prevRetransmissionTicks; else this.retransmissionTicksLeft = prevRetransmissionTicks; this.prevRetransmissionTicks = retransmissionTicksLeft; return true; } else return false; } protected void setRetransmissionTicks() { this.retransmissionTicksLeft = 1; this.prevRetransmissionTicks = 1; } /** * Resend the last ack. */ public void resendAck() throws SipException { // Check for null. if (this.getLastAckSent() != null) { if (getLastAckSent().getHeader(TimeStampHeader.NAME) != null && sipStack.generateTimeStampHeader) { TimeStamp ts = new TimeStamp(); try { ts.setTimeStamp(System.currentTimeMillis()); getLastAckSent().setHeader(ts); } catch (InvalidArgumentException e) { } } this.sendAck(getLastAckSent(), false); } else { if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)){ logger.logDebug("SIPDialog::resendAck:lastAck sent is NULL hence not resending ACK"); } } } /** * Get the method of the request/response that resulted in the creation of * the Dialog. * * @return -- the method of the dialog. */ public String getMethod() { // Method of the request or response used to create this dialog return this.method; } /** * Start the dialog timer. * * @param transaction */ protected void startTimer(SIPServerTransaction transaction) { if (this.timerTask != null && timerTask.transaction == transaction) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Timer already running for " + getDialogId()); return; } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Starting dialog timer for " + getDialogId()); acquireTimerTaskSem(); try { if (this.timerTask != null) { this.timerTask.transaction = transaction; } else { this.timerTask = new DialogTimerTask(transaction); if ( sipStack.getTimer() != null && sipStack.getTimer().isStarted()) { sipStack.getTimer().scheduleWithFixedDelay(timerTask, transaction.getBaseTimerInterval(), transaction.getBaseTimerInterval()); } } } finally { releaseTimerTaskSem(); } this.setRetransmissionTicks(); } /** * Stop the dialog timer. This is called when the dialog is terminated. * */ protected void stopTimer() { try { acquireTimerTaskSem(); try { if (this.timerTask != null) { this.getStack().getTimer().cancel(timerTask); this.timerTask = null; } if (this.earlyStateTimerTask != null) { this.getStack().getTimer().cancel(this.earlyStateTimerTask); this.earlyStateTimerTask = null; } } finally { releaseTimerTaskSem(); } } catch (Exception ex) { } } /* * (non-Javadoc) Retransmissions of the reliable provisional response cease * when a matching PRACK is received by the UA core. PRACK is like any other * request within a dialog, and the UAS core processes it according to the * procedures of Sections 8.2 and 12.2.2 of RFC 3261. A matching PRACK is * defined as one within the same dialog as the response, and whose method, * CSeq-num, and response-num in the RAck header field match, respectively, * the method from the CSeq, the sequence number from the CSeq, and the * sequence number from the RSeq of the reliable provisional response. * * @see android.javax.sip.Dialog#createPrack(android.javax.sip.message.Response) */ public Request createPrack(Response relResponse) throws DialogDoesNotExistException, SipException { if (this.getState() == null || this.getState().equals(DialogState.TERMINATED)) throw new DialogDoesNotExistException( "Dialog not initialized or terminated"); if ((RSeq) relResponse.getHeader(RSeqHeader.NAME) == null) { throw new SipException("Missing RSeq Header"); } try { SIPResponse sipResponse = (SIPResponse) relResponse; SIPRequest sipRequest = this.createRequest(Request.PRACK, sipResponse.getTopmostVia().getTransport()); String toHeaderTag = sipResponse.getTo().getTag(); sipRequest.setToTag(toHeaderTag); RAck rack = new RAck(); RSeq rseq = (RSeq) relResponse.getHeader(RSeqHeader.NAME); rack.setMethod(sipResponse.getCSeq().getMethod()); rack.setCSequenceNumber((int) sipResponse.getCSeq().getSeqNumber()); rack.setRSequenceNumber(rseq.getSeqNumber()); sipRequest.setHeader(rack); if (this.proxyAuthorizationHeader != null) { sipRequest.addHeader(proxyAuthorizationHeader); } return (Request) sipRequest; } catch (Exception ex) { InternalErrorHandler.handleException(ex); return null; } } private void updateRequest(SIPRequest sipRequest) { RouteList rl = this.getRouteList(); if (rl.size() > 0) { sipRequest.setHeader(rl); } else { sipRequest.removeHeader(RouteHeader.NAME); } if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { sipRequest .setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); } /* * Update the request with Proxy auth header if one has been cached. */ if (this.proxyAuthorizationHeader != null && sipRequest.getHeader(ProxyAuthorizationHeader.NAME) == null) { sipRequest.setHeader(proxyAuthorizationHeader); } } /* * (non-Javadoc) The UAC core MUST generate an ACK request for each 2xx * received from the transaction layer. The header fields of the ACK are * constructed in the same way as for any request sent within a dialog (see * Section 12) with the exception of the CSeq and the header fields related * to authentication. The sequence number of the CSeq header field MUST be * the same as the INVITE being acknowledged, but the CSeq method MUST be * ACK. The ACK MUST contain the same credentials as the INVITE. If the 2xx * contains an offer (based on the rules above), the ACK MUST carry an * answer in its body. If the offer in the 2xx response is not acceptable, * the UAC core MUST generate a valid answer in the ACK and then send a BYE * immediately. * * Note that for the case of forked requests, you can create multiple * outgoing invites each with a different cseq and hence you need to supply * the invite. * * @see android.javax.sip.Dialog#createAck(long) */ public Request createAck(long cseqno) throws InvalidArgumentException, SipException { // JvB: strictly speaking it is allowed to start a dialog with // SUBSCRIBE, // then send INVITE+ACK later on if (!method.equals(Request.INVITE)) throw new SipException("Dialog was not created with an INVITE" + method); if (cseqno <= 0) throw new InvalidArgumentException("bad cseq <= 0 "); else if (cseqno > ((((long) 1) << 32) - 1)) throw new InvalidArgumentException("bad cseq > " + ((((long) 1) << 32) - 1)); if (this.getRemoteTarget() == null) { throw new SipException("Cannot create ACK - no remote Target!"); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "createAck " + this + " cseqno " + cseqno); } // MUST ack in the same order that the OKs were received. This traps // out of order ACK sending. Old ACKs seqno's can always be ACKed. if (lastInviteOkReceived < cseqno) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "WARNING : Attempt to crete ACK without OK " + this); logger.logDebug( "LAST RESPONSE = " + this.getLastResponseStatusCode()); } throw new SipException( "Dialog not yet established -- no OK response! lastInviteOkReceived=" + lastInviteOkReceived + " cseqno=" + cseqno); } try { // JvB: Transport from first entry in route set, or remote Contact // if none // Only used to find correct LP & create correct Via SipURI uri4transport = null; if (this.routeList != null && !this.routeList.isEmpty()) { Route r = (Route) this.routeList.getFirst(); uri4transport = ((SipURI) r.getAddress().getURI()); } else { // should be !=null, checked above uri4transport = ((SipURI) this.getRemoteTarget().getURI()); } String transport = uri4transport.getTransportParam(); ListeningPointImpl lp; if(uri4transport.isSecure()){ // Fix for https://java.net/jira/browse/JSIP-492 if(transport != null && transport.equalsIgnoreCase(ListeningPoint.UDP)){ throw new SipException("Cannot create ACK - impossible to use sips uri with transport UDP:"+uri4transport); } transport = ListeningPoint.TLS; } if (transport != null) { lp = (ListeningPointImpl) sipProvider .getListeningPoint(transport); } else { if (uri4transport.isSecure()) { // JvB fix: also support TLS lp = (ListeningPointImpl) sipProvider .getListeningPoint(ListeningPoint.TLS); } else { lp = (ListeningPointImpl) sipProvider .getListeningPoint(ListeningPoint.UDP); if (lp == null) { // Alex K fix: let's try to find TCP lp = (ListeningPointImpl) sipProvider .getListeningPoint(ListeningPoint.TCP); } } } if ( logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { logger.logDebug("uri4transport = " + uri4transport); } if (lp == null) { if ( ! uri4transport.isSecure()) { // If transport is not secure, and we cannot find an appropriate transport, try any supported transport to send out the ACK. if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { logger.logDebug( "No Listening point for " + uri4transport + " Using last response topmost" ); } // We are not on a secure connection and we don't support the transport required lp = (ListeningPointImpl) sipProvider.getListeningPoint(this.lastResponseTopMostVia.getTransport()); } if ( lp== null) { if (logger.isLoggingEnabled(LogLevels.TRACE_ERROR)) { logger.logError( "remoteTargetURI " + this.getRemoteTarget().getURI()); logger.logError( "uri4transport = " + uri4transport); logger.logError( "No LP found for transport=" + transport); } throw new SipException( "Cannot create ACK - no ListeningPoint for transport towards next hop found:" + transport); } } SIPRequest sipRequest = new SIPRequest(); sipRequest.setMethod(Request.ACK); sipRequest.setRequestURI((SipUri) getRemoteTarget().getURI() .clone()); sipRequest.setCallId(this.getCallId()); sipRequest.setCSeq(new CSeq(cseqno, Request.ACK)); List vias = new ArrayList(); // Via via = lp.getViaHeader(); // The user may have touched the sentby for the response. // so use the via header extracted from the response for the ACK => // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=205 // strip the params from the via of the response and use the params // from the // original request Via via = this.lastResponseTopMostVia; if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("lastResponseTopMostVia " + lastResponseTopMostVia); } via.removeParameters(); if (originalRequest != null && originalRequest.getTopmostVia() != null) { NameValueList originalRequestParameters = originalRequest .getTopmostVia().getParameters(); if (originalRequestParameters != null && originalRequestParameters.size() > 0) { via.setParameters((NameValueList) originalRequestParameters .clone()); } } via.setBranch(Utils.getInstance().generateBranchId()); // new branch vias.add(via); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Adding via to the ACK we are creating : " + via + " lastResponseTopMostVia " + lastResponseTopMostVia); } sipRequest.setVia(vias); From from = new From(); from.setAddress(this.getLocalParty()); from.setTag(this.myTag); sipRequest.setFrom(from); To to = new To(); to.setAddress(this.getRemoteParty()); if (hisTag != null) to.setTag(this.hisTag); sipRequest.setTo(to); sipRequest.setMaxForwards(new MaxForwards(70)); if (this.originalRequest != null) { Authorization authorization = this.originalRequest .getAuthorization(); if (authorization != null) sipRequest.setHeader(authorization); // jeand : setting back the original Request to null to avoid // keeping references around for too long // since it is used only in the dialog setup originalRequestRecordRouteHeaders = originalRequest .getRecordRouteHeaders(); originalRequest = null; } // ACKs for 2xx responses // use the Route values learned from the Record-Route of the 2xx // responses. this.updateRequest(sipRequest); return sipRequest; } catch (Exception ex) { InternalErrorHandler.handleException(ex); throw new SipException("unexpected exception ", ex); } } /** * Get the provider for this Dialog. * * SPEC_REVISION * * @return -- the SIP Provider associated with this transaction. */ public SipProviderImpl getSipProvider() { return this.sipProvider; } /** * @param sipProvider * the sipProvider to set */ public void setSipProvider(SipProviderImpl sipProvider) { this.sipProvider = sipProvider; } /** * Check the tags of the response against the tags of the Dialog. Return * true if the respnse matches the tags of the dialog. We do this check wehn * sending out a response. * * @param sipResponse * -- the response to check. * */ public void setResponseTags(SIPResponse sipResponse) { if (this.getLocalTag() != null || this.getRemoteTag() != null) { return; } String responseFromTag = sipResponse.getFromTag(); if (responseFromTag != null) { if (responseFromTag.equals(this.getLocalTag())) { sipResponse.setToTag(this.getRemoteTag()); } else if (responseFromTag.equals(this.getRemoteTag())) { sipResponse.setToTag(this.getLocalTag()); } } else { if (logger.isLoggingEnabled()) logger.logWarning( "No from tag in response! Not RFC 3261 compatible."); } } /** * Set the last response for this dialog. This method is called for updating * the dialog state when a response is either sent or received from within a * Dialog. * * @param transaction * -- the transaction associated with the response * @param sipResponse * -- the last response to set. */ public void setLastResponse(SIPTransaction transaction, SIPResponse sipResponse) { this.callIdHeader = sipResponse.getCallId(); final int statusCode = sipResponse.getStatusCode(); if (statusCode == 100) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger .logDebug( "Invalid status code - 100 in setLastResponse - ignoring"); return; } if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logStackTrace(); } // this.lastResponse = sipResponse; try { this.lastResponseStatusCode = Integer.valueOf(statusCode); // Issue 378 : http://java.net/jira/browse/JSIP-378 // Cloning the via header to avoid race condition and be modified this.lastResponseTopMostVia = (Via) sipResponse.getTopmostVia().clone(); String cseqMethod = sipResponse.getCSeqHeader().getMethod(); this.lastResponseMethod = cseqMethod; long responseCSeqNumber = sipResponse.getCSeq().getSeqNumber(); boolean is100ClassResponse = statusCode / 100 == 1; boolean is200ClassResponse = statusCode / 100 == 2; this.lastResponseCSeqNumber = responseCSeqNumber; if(Request.INVITE.equals(cseqMethod)) { this.lastInviteResponseCSeqNumber = responseCSeqNumber; this.lastInviteResponseCode = statusCode; } if (sipResponse.getToTag() != null ) { this.lastResponseToTag = sipResponse.getToTag(); } if ( sipResponse.getFromTag() != null ) { this.lastResponseFromTag = sipResponse.getFromTag(); } if (transaction != null) { this.lastResponseDialogId = sipResponse.getDialogId(transaction .isServerTransaction()); } this.setAssigned(); // Adjust state of the Dialog state machine. if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "sipDialog: setLastResponse:" + this + " lastResponse = " + this.lastResponseStatusCode + " response " + sipResponse.toString() + " topMostViaHeader " + lastResponseTopMostVia); } if (this.getState() == DialogState.TERMINATED) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug( "sipDialog: setLastResponse -- dialog is terminated - ignoring "); } // Capture the OK response for later use in createAck // This is handy for late arriving OK's that we want to ACK. if (cseqMethod.equals(Request.INVITE) && statusCode == 200) { this.lastInviteOkReceived = Math.max( responseCSeqNumber, this.lastInviteOkReceived); } return; } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logStackTrace(); logger.logDebug( "cseqMethod = " + cseqMethod); logger.logDebug( "dialogState = " + this.getState()); logger.logDebug( "method = " + this.getMethod()); logger .logDebug("statusCode = " + statusCode); logger.logDebug( "transaction = " + transaction); } // JvB: don't use "!this.isServer" here // note that the transaction can be null for forked // responses. if (transaction == null || transaction instanceof ClientTransaction) { if (SIPTransactionStack.isDialogCreated(cseqMethod)) { // Make a final tag assignment. if (getState() == null && is100ClassResponse) { /* * Guard aginst slipping back into early state from * confirmed state. */ // Was (sipResponse.getToTag() != null || // sipStack.rfc2543Supported) setState(SIPDialog.EARLY_STATE); if ((sipResponse.getToTag() != null || sipStack.rfc2543Supported) && this.getRemoteTag() == null) { setRemoteTag(sipResponse.getToTag()); this.setDialogId(sipResponse.getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); } } else if (getState() != null && getState().equals(DialogState.EARLY) && is100ClassResponse) { /* * This case occurs for forked dialog responses. The To * tag can change as a result of the forking. The remote * target can also change as a result of the forking. */ if (cseqMethod.equals(getMethod()) && transaction != null && (sipResponse.getToTag() != null || sipStack.rfc2543Supported)) { setRemoteTag(sipResponse.getToTag()); this.setDialogId(sipResponse.getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); } } else if (is200ClassResponse) { // This is a dialog creating method (such as INVITE). // 2xx response -- set the state to the confirmed // state. To tag is MANDATORY for the response. // Only do this if method equals initial request! if (logger.isLoggingEnabled( LogWriter.TRACE_DEBUG)) { logger .logDebug( "pendingRouteUpdateOn202Response : " + this.pendingRouteUpdateOn202Response); } if (cseqMethod.equals(getMethod()) && (sipResponse.getToTag() != null || sipStack.rfc2543Supported) && (this.getState() != DialogState.CONFIRMED || (this .getState() == DialogState.CONFIRMED && cseqMethod .equals(Request.SUBSCRIBE) && this.pendingRouteUpdateOn202Response && is200ClassResponse))) { if (this.getState() != DialogState.CONFIRMED) { setRemoteTag(sipResponse.getToTag()); this .setDialogId(sipResponse .getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); this.setState(CONFIRMED_STATE); } /* * Note: Subscribe NOTIFY processing. The route set * is computed only after we get the 202 response * but the NOTIFY may come in before we get the 202 * response. So we need to update the route set * after we see the 202 despite the fact that the * dialog is in the CONFIRMED state. We do this only * on the dialog forming SUBSCRIBE an not a * resubscribe. */ if (cseqMethod.equals(Request.SUBSCRIBE) && is200ClassResponse && this.pendingRouteUpdateOn202Response) { setRemoteTag(sipResponse.getToTag()); this.addRoute(sipResponse); this.pendingRouteUpdateOn202Response = false; } } // Capture the OK response for later use in createAck if (cseqMethod.equals(Request.INVITE)) { this.lastInviteOkReceived = Math.max(responseCSeqNumber, this.lastInviteOkReceived); if(getState() != null && getState().getValue() == SIPDialog.CONFIRMED_STATE && transaction != null) { // http://java.net/jira/browse/JSIP-444 Honor Target Refresh on Response // Contribution from francoisjoseph levee (Orange Labs) doTargetRefresh(sipResponse); } } } else if (statusCode >= 300 && statusCode <= 699 && (getState() == null || (cseqMethod .equals(getMethod()) && getState() .getValue() == SIPDialog.EARLY_STATE))) { /* * This case handles 3xx, 4xx, 5xx and 6xx responses. * RFC 3261 Section 12.3 - dialog termination. * Independent of the method, if a request outside of a * dialog generates a non-2xx final response, any early * dialogs created through provisional responses to that * request are terminated. */ setState(SIPDialog.TERMINATED_STATE); } /* * This code is in support of "proxy" servers that are * constructed as back to back user agents. This could be a * dialog in the middle of the call setup path somewhere. * Hence the incoming invite has record route headers in it. * The response will have additional record route headers. * However, for this dialog only the downstream record route * headers matter. Ideally proxy servers should not be * constructed as Back to Back User Agents. Remove all the * record routes that are present in the incoming INVITE so * you only have the downstream Route headers present in the * dialog. Note that for an endpoint - you will have no * record route headers present in the original request so * the loop will not execute. */ if (this.getState() != DialogState.CONFIRMED && this.getState() != DialogState.TERMINATED) { if (getOriginalRequestRecordRouteHeaders() != null) { ListIterator it = getOriginalRequestRecordRouteHeaders() .listIterator( getOriginalRequestRecordRouteHeaders() .size()); while (it.hasPrevious()) { RecordRoute rr = (RecordRoute) it.previous(); Route route = (Route) routeList.getFirst(); if (route != null && rr.getAddress().equals( route.getAddress())) { routeList.removeFirst(); } else break; } } } } else if (cseqMethod.equals(Request.NOTIFY) && (this.getMethod().equals(Request.SUBSCRIBE) || this .getMethod().equals(Request.REFER)) && is200ClassResponse && this.getState() == null) { // This is a notify response. this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); this.setState(SIPDialog.CONFIRMED_STATE); } else if (cseqMethod.equals(Request.BYE) && is200ClassResponse && isTerminatedOnBye()) { // Dialog will be terminated when the transction is // terminated. setState(SIPDialog.TERMINATED_STATE); } } else { // Processing Server Dialog. if (cseqMethod.equals(Request.BYE) && is200ClassResponse && this.isTerminatedOnBye()) { /* * Only transition to terminated state when 200 OK is * returned for the BYE. Other status codes just result in * leaving the state in COMPLETED state. */ this.setState(SIPDialog.TERMINATED_STATE); } else { boolean doPutDialog = false; if (getLocalTag() == null && sipResponse.getTo().getTag() != null && SIPTransactionStack.isDialogCreated(cseqMethod) && cseqMethod.equals(getMethod())) { setLocalTag(sipResponse.getTo().getTag()); doPutDialog = true; } if (!is200ClassResponse) { if (is100ClassResponse) { if (doPutDialog) { setState(SIPDialog.EARLY_STATE); this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); } } else { /* * RFC 3265 chapter 3.1.4.1 "Non-200 class final * responses indicate that no subscription or dialog * has been created, and no subsequent NOTIFY * message will be sent. All non-200 class" + * responses (with the exception of "489", described * herein) have the same meanings and handling as * described in SIP" */ // Bug Fix by Jens tinfors // see // https://jain-sip.dev.java.net/servlets/ReadMsg?list=users&msgNo=797 if (statusCode == 489 && (cseqMethod .equals(Request.NOTIFY) || cseqMethod .equals(Request.SUBSCRIBE))) { if (logger .isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger .logDebug( "RFC 3265 : Not setting dialog to TERMINATED for 489"); } else { // baranowb: simplest fix to // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=175 // application is responsible for terminating in // this case // see rfc 5057 for better explanation if (!this.isReInvite() && getState() != DialogState.CONFIRMED) { this.setState(SIPDialog.TERMINATED_STATE); } } } } else { /* * JvB: RFC4235 says that when sending 2xx on UAS side, * state should move to CONFIRMED */ if (this.dialogState <= SIPDialog.EARLY_STATE && (cseqMethod.equals(Request.INVITE) || cseqMethod .equals(Request.SUBSCRIBE) || cseqMethod .equals(Request.REFER))) { this.setState(SIPDialog.CONFIRMED_STATE); } if (doPutDialog) { this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); } } } } } finally { if (sipResponse.getCSeq().getMethod().equals(Request.INVITE) && transaction != null && transaction instanceof ClientTransaction && this.getState() != DialogState.TERMINATED) { this.acquireTimerTaskSem(); try { if (this.getState() == DialogState.EARLY) { if (this.earlyStateTimerTask != null) { sipStack.getTimer() .cancel(this.earlyStateTimerTask); } logger.logDebug( "EarlyStateTimerTask craeted " + this.earlyDialogTimeout * 1000); this.earlyStateTimerTask = new EarlyStateTimerTask(); if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { sipStack.getTimer().schedule(this.earlyStateTimerTask, this.earlyDialogTimeout * 1000); } } else { if (this.earlyStateTimerTask != null) { sipStack.getTimer() .cancel(this.earlyStateTimerTask); this.earlyStateTimerTask = null; } } } finally { this.releaseTimerTaskSem(); } } } } /** * Start the retransmit timer. * * @param sipServerTx * -- server transaction on which the response was sent * @param response * - response that was sent. */ public void startRetransmitTimer(SIPServerTransaction sipServerTx, Response response) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "startRetransmitTimer() " + response.getStatusCode() + " method " + sipServerTx.getMethod()); } if (sipServerTx.isInviteTransaction() && response.getStatusCode() / 100 == 2) { this.startTimer(sipServerTx); } } /** * @return -- the last response associated with the dialog. */ // public SIPResponse getLastResponse() { // // return lastResponse; // } /** * Do taget refresh dialog state updates. * * RFC 3261: Requests within a dialog MAY contain Record-Route and Contact * header fields. However, these requests do not cause the dialog's route * set to be modified, although they may modify the remote target URI. * Specifically, requests that are not target refresh requests do not modify * the dialog's remote target URI, and requests that are target refresh * requests do. For dialogs that have been established with an * * INVITE, the only target refresh request defined is re-INVITE (see Section * 14). Other extensions may define different target refresh requests for * dialogs established in other ways. */ private void doTargetRefresh(SIPMessage sipMessage) { ContactList contactList = sipMessage.getContactHeaders(); /* * INVITE is the target refresh for INVITE dialogs. SUBSCRIBE is the * target refresh for subscribe dialogs from the client side. This * modifies the remote target URI potentially */ if (contactList != null) { Contact contact = (Contact) contactList.getFirst(); this.setRemoteTarget(contact); } } private static final boolean optionPresent(ListIterator l, String option) { while (l.hasNext()) { OptionTag opt = (OptionTag) l.next(); if (opt != null && option.equalsIgnoreCase(opt.getOptionTag())) return true; } return false; } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#createReliableProvisionalResponse(int) */ public Response createReliableProvisionalResponse(int statusCode) throws InvalidArgumentException, SipException { if (!(firstTransactionIsServerTransaction)) { throw new SipException("Not a Server Dialog!"); } /* * A UAS MUST NOT attempt to send a 100 (Trying) response reliably. Only * provisional responses numbered 101 to 199 may be sent reliably. If * the request did not include either a Supported or Require header * field indicating this feature, the UAS MUST NOT send the provisional * response reliably. */ if (statusCode <= 100 || statusCode > 199) throw new InvalidArgumentException("Bad status code "); SIPRequest request = this.originalRequest; if (!request.getMethod().equals(Request.INVITE)) throw new SipException("Bad method"); ListIterator list = request.getHeaders(SupportedHeader.NAME); if (list == null || !optionPresent(list, "100rel")) { list = request.getHeaders(RequireHeader.NAME); if (list == null || !optionPresent(list, "100rel")) { throw new SipException( "No Supported/Require 100rel header in the request"); } } SIPResponse response = request.createResponse(statusCode); /* * The provisional response to be sent reliably is constructed by the * UAS core according to the procedures of Section 8.2.6 of RFC 3261. In * addition, it MUST contain a Require header field containing the * option tag 100rel, and MUST include an RSeq header field. The value * of the header field for the first reliable provisional response in a * transaction MUST be between 1 and 231 - 1. It is RECOMMENDED that it * be chosen uniformly in this range. The RSeq numbering space is within * a single transaction. This means that provisional responses for * different requests MAY use the same values for the RSeq number. */ Require require = new Require(); try { require.setOptionTag("100rel"); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } response.addHeader(require); RSeq rseq = new RSeq(); /* * set an arbitrary sequence number. This is actually set when the * response is sent out */ rseq.setSeqNumber(1L); /* * Copy the record route headers from the request to the response ( * Issue 160 ). Note that other 1xx headers do not get their Record * Route headers copied over but reliable provisional responses do. See * RFC 3262 Table 2. */ RecordRouteList rrl = request.getRecordRouteHeaders(); if (rrl != null) { RecordRouteList rrlclone = (RecordRouteList) rrl.clone(); response.setHeader(rrlclone); } return response; } /** * Do the processing necessary for the PRACK * * @param prackRequest * @return true if this is the first time the tx has seen the prack ( and * hence needs to be passed up to the TU) */ public boolean handlePrack(SIPRequest prackRequest) { /* * The RAck header is sent in a PRACK request to support reliability of * provisional responses. It contains two numbers and a method tag. The * first number is the value from the RSeq header in the provisional * response that is being acknowledged. The next number, and the method, * are copied from the CSeq in the response that is being acknowledged. * The method name in the RAck header is case sensitive. */ if (!this.isServer()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- not a server Dialog"); return false; } SIPServerTransaction sipServerTransaction = (SIPServerTransaction) this .getFirstTransactionInt(); byte[] sipResponse = sipServerTransaction .getReliableProvisionalResponse(); if (sipResponse == null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- ReliableResponse not found"); return false; } RAck rack = (RAck) prackRequest.getHeader(RAckHeader.NAME); if (rack == null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- rack header not found"); return false; } if (!rack.getMethod().equals( sipServerTransaction.getPendingReliableResponseMethod())) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- CSeq Header does not match PRACK"); return false; } if (rack.getCSeqNumberLong() != sipServerTransaction .getPendingReliableCSeqNumber()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- CSeq Header does not match PRACK"); return false; } if (rack.getRSequenceNumber() != sipServerTransaction .getPendingReliableRSeqNumber()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "Dropping Prack -- RSeq Header does not match PRACK"); return false; } return sipServerTransaction.prackRecieved(); } /* * (non-Javadoc) * * @see * android.javax.sip.Dialog#sendReliableProvisionalResponse(android.javax.sip.message.Response * ) */ public void sendReliableProvisionalResponse(Response relResponse) throws SipException { if (!this.isServer()) { throw new SipException("Not a Server Dialog"); } SIPResponse sipResponse = (SIPResponse) relResponse; if (relResponse.getStatusCode() == 100) throw new SipException( "Cannot send 100 as a reliable provisional response"); if (relResponse.getStatusCode() / 100 > 2) throw new SipException( "Response code is not a 1xx response - should be in the range 101 to 199 "); /* * Do a little checking on the outgoing response. */ if (sipResponse.getToTag() == null) { throw new SipException( "Badly formatted response -- To tag mandatory for Reliable Provisional Response"); } ListIterator requireList = (ListIterator) relResponse .getHeaders(RequireHeader.NAME); boolean found = false; if (requireList != null) { while (requireList.hasNext() && !found) { RequireHeader rh = (RequireHeader) requireList.next(); if (rh.getOptionTag().equalsIgnoreCase("100rel")) { found = true; } } } if (!found) { Require require = new Require("100rel"); relResponse.addHeader(require); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug( "Require header with optionTag 100rel is needed -- adding one"); } } SIPServerTransaction serverTransaction = (SIPServerTransaction) this .getFirstTransactionInt(); /* * put into the dialog table before sending the response so as to avoid * race condition with PRACK */ this.setLastResponse(serverTransaction, sipResponse); this.setDialogId(sipResponse.getDialogId(true)); serverTransaction.sendReliableProvisionalResponse(relResponse); this.startRetransmitTimer(serverTransaction, relResponse); } /* * (non-Javadoc) * * @see android.javax.sip.Dialog#terminateOnBye(boolean) */ public void terminateOnBye(boolean terminateFlag) throws SipException { this.terminateOnBye = terminateFlag; } /** * Set the "assigned" flag to true. We do this when inserting the dialog * into the dialog table of the stack. * */ public void setAssigned() { this.isAssigned = true; } /** * Return true if the dialog has already been mapped to a transaction. * */ public boolean isAssigned() { return this.isAssigned; } /** * Get the contact header that the owner of this dialog assigned. Subsequent * Requests are considered to belong to the dialog if the dialog identifier * matches and the contact header matches the ip address and port on which * the request is received. * * @return contact header belonging to the dialog. */ public Contact getMyContactHeader() { if (contactHeader == null && contactHeaderStringified != null) { try { this.contactHeader = (Contact) new ContactParser( contactHeaderStringified).parse(); } catch (ParseException e) { logger.logError( "error reparsing the contact header", e); } } return contactHeader; } /** * Do the necessary processing to handle an ACK directed at this Dialog. * * @param ackTransaction * -- the ACK transaction that was directed at this dialog. * @return -- true if the ACK was successfully consumed by the Dialog and * resulted in the dialog state being changed. */ public boolean handleAck(SIPServerTransaction ackTransaction) { // SIPRequest sipRequest = ackTransaction.getOriginalRequest(); if (isAckSeen() && getRemoteSeqNumber() == ackTransaction.getCSeq()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "SIPDialog::handleAck: ACK already seen by dialog -- dropping Ack" + " retransmission"); } acquireTimerTaskSem(); try { if (this.timerTask != null) { this.getStack().getTimer().cancel(timerTask); this.timerTask = null; } } finally { releaseTimerTaskSem(); } return false; } else if (this.getState() == DialogState.TERMINATED) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "SIPDialog::handleAck: Dialog is terminated -- dropping ACK"); return false; } else { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("SIPDialog::handleAck: lastResponseCSeqNumber = " + lastInviteOkReceived + " ackTxCSeq " + ackTransaction.getCSeq()); } if (lastResponseStatusCode != null && this.lastInviteResponseCode / 100 == 2 && lastInviteResponseCSeqNumber == ackTransaction.getCSeq()) { ackTransaction.setDialog(this, lastResponseDialogId); /* * record that we already saw an ACK for this dialog. */ ackReceived(ackTransaction.getCSeq()); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( "SIPDialog::handleACK: ACK for 2XX response --- sending to TU "); return true; } else { /* * This happens when the ACK is re-transmitted and arrives too * late to be processed. */ if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug( " INVITE transaction not found"); if ( this.isBackToBackUserAgent() ) { this.releaseAckSem(); } return false; } } } String getEarlyDialogId() { return earlyDialogId; } /** * Release the semaphore for ACK processing so the next re-INVITE may * proceed. */ void releaseAckSem() { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug("releaseAckSem-enter]]" + this + " sem=" + this.ackSem + " b2bua=" + this.isBackToBackUserAgent); logger.logStackTrace(); } if (this.isBackToBackUserAgent) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug("releaseAckSem]]" + this + " sem=" + this.ackSem); logger.logStackTrace(); } if (this.ackSem.availablePermits() == 0 ) { this.ackSem.release(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug("releaseAckSem]]" + this + " sem=" + this.ackSem); } } } } boolean isBlockedForReInvite() { return this.ackSem.availablePermits() == 0; } boolean takeAckSem() { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("[takeAckSem " + this + " sem=" + this.ackSem); } try { if (!this.ackSem.tryAcquire(2, TimeUnit.SECONDS)) { if (logger.isLoggingEnabled()) { logger.logError( "Cannot aquire ACK semaphore "); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "Semaphore previously acquired at " + this.stackTrace + " sem=" + this.ackSem); logger.logStackTrace(); } return false; } if (logger.isLoggingEnabled( StackLogger.TRACE_DEBUG)) { this.recordStackTrace(); } } catch (InterruptedException ex) { logger.logError("Cannot aquire ACK semaphore"); return false; } return true; } /** * @param lastAckSent * the lastAckSent to set */ private void setLastAckSent(SIPRequest lastAckSent) { this.lastAckSent = lastAckSent; this.lastAckSent.setTransaction(null); // null out the associated Tx (release memory) } /** * @return true if an ack was ever sent for this Dialog */ public boolean isAtleastOneAckSent() { return this.isAcknowledged; } public boolean isBackToBackUserAgent() { return this.isBackToBackUserAgent; } public synchronized void doDeferredDeleteIfNoAckSent(long seqno) { if (sipStack.getTimer() == null) { this.setState(TERMINATED_STATE); } else if (dialogDeleteIfNoAckSentTask == null) { // Delete the transaction after the max ack timeout. dialogDeleteIfNoAckSentTask = new DialogDeleteIfNoAckSentTask(seqno); if (sipStack.getTimer() != null && sipStack.getTimer().isStarted()) { int delay = SIPTransactionStack.BASE_TIMER_INTERVAL; if(lastTransaction != null) { delay = lastTransaction.getBaseTimerInterval(); } sipStack.getTimer().schedule( dialogDeleteIfNoAckSentTask, sipStack.getAckTimeoutFactor() * delay); } } } /* * (non-Javadoc) * * @see android.gov.nist.javax.sip.DialogExt#setBackToBackUserAgent(boolean) */ public void setBackToBackUserAgent() { this.isBackToBackUserAgent = true; } /** * @return the eventHeader */ EventHeader getEventHeader() { return eventHeader; } /** * @param eventHeader * the eventHeader to set */ void setEventHeader(EventHeader eventHeader) { this.eventHeader = eventHeader; } /** * @param serverTransactionFlag * the serverTransactionFlag to set */ void setServerTransactionFlag(boolean serverTransactionFlag) { this.serverTransactionFlag = serverTransactionFlag; } /** * @param reInviteFlag * the reinviteFlag to set */ protected void setReInviteFlag(boolean reInviteFlag) { this.reInviteFlag = reInviteFlag; } public boolean isSequenceNumberValidation() { return this.sequenceNumberValidation; } public void disableSequenceNumberValidation() { this.sequenceNumberValidation = false; } public void acquireTimerTaskSem() { boolean acquired = false; try { acquired = this.timerTaskLock.tryAcquire(10, TimeUnit.SECONDS); } catch (InterruptedException ex) { acquired = false; } if (!acquired) { throw new IllegalStateException( "Impossible to acquire the dialog timer task lock"); } } public void releaseTimerTaskSem() { this.timerTaskLock.release(); } public String getMergeId() { return this.firstTransactionMergeId; } public void setPendingRouteUpdateOn202Response(SIPRequest sipRequest) { this.pendingRouteUpdateOn202Response = true; // Issue 374 : patch from ivan dubrov : get the from tag instead of to tag String fromTag = sipRequest.getFromHeader().getTag(); if (fromTag != null) { this.setRemoteTag(fromTag); } } public String getLastResponseMethod() { return lastResponseMethod; } public Integer getLastResponseStatusCode() { return lastResponseStatusCode; } public long getLastResponseCSeqNumber() { return lastResponseCSeqNumber; } // jeand cleanup the dialog from the data not needed anymore upon receiving // or sending an ACK // to save on mem protected void cleanUpOnAck() { if (getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "cleanupOnAck : " + getDialogId()); } if (originalRequest != null) { if (originalRequestRecordRouteHeaders != null) { originalRequestRecordRouteHeadersString = originalRequestRecordRouteHeaders .toString(); } originalRequestRecordRouteHeaders = null; originalRequest = null; } if (firstTransaction != null) { if (firstTransaction.getOriginalRequest() != null) { firstTransaction.getOriginalRequest().cleanUp(); } firstTransaction = null; } if (lastTransaction != null) { if (lastTransaction.getOriginalRequest() != null) { lastTransaction.getOriginalRequest().cleanUp(); } lastTransaction = null; } if (callIdHeader != null) { callIdHeaderString = callIdHeader.toString(); callIdHeader = null; } if (contactHeader != null) { contactHeaderStringified = contactHeader.toString(); contactHeader = null; } if (remoteTarget != null) { remoteTargetStringified = remoteTarget.toString(); remoteTarget = null; } if (remoteParty != null) { remotePartyStringified = remoteParty.toString(); remoteParty = null; } if (localParty != null) { localPartyStringified = localParty.toString(); localParty = null; } } } /** * Release references so the GC can clean up dialog state. * */ protected void cleanUp() { if (getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { cleanUpOnAck(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger .logDebug("dialog cleanup : " + getDialogId()); } if (eventListeners != null) { eventListeners.clear(); } timerTaskLock = null; ackSem = null; contactHeader = null; eventHeader = null; firstTransactionId = null; firstTransactionMethod = null; // Cannot clear up the last Ack Sent. until DIALOG is terminated. // lastAckReceivedCSeqNumber = null; lastResponseDialogId = null; lastResponseMethod = null; lastResponseTopMostVia = null; if (originalRequestRecordRouteHeaders != null) { originalRequestRecordRouteHeaders.clear(); originalRequestRecordRouteHeaders = null; originalRequestRecordRouteHeadersString = null; } if (routeList != null) { routeList.clear(); routeList = null; } responsesReceivedInForkingCase.clear(); } } protected RecordRouteList getOriginalRequestRecordRouteHeaders() { if (originalRequestRecordRouteHeaders == null && originalRequestRecordRouteHeadersString != null) { try { originalRequestRecordRouteHeaders = (RecordRouteList) new RecordRouteParser( originalRequestRecordRouteHeadersString).parse(); } catch (ParseException e) { logger .logError( "error reparsing the originalRequest RecordRoute Headers", e); } originalRequestRecordRouteHeadersString = null; } return originalRequestRecordRouteHeaders; } /** * @return the lastResponseTopMostVia */ public Via getLastResponseTopMostVia() { return lastResponseTopMostVia; } /* * (non-Javadoc) * * @see android.gov.nist.javax.sip.DialogExt#isReleaseReferences() */ public ReleaseReferencesStrategy getReleaseReferencesStrategy() { return releaseReferencesStrategy; } /* * (non-Javadoc) * * @see android.gov.nist.javax.sip.DialogExt#setReleaseReferences(boolean) */ public void setReleaseReferencesStrategy(ReleaseReferencesStrategy releaseReferencesStrategy) { this.releaseReferencesStrategy = releaseReferencesStrategy; } public void setEarlyDialogTimeoutSeconds(int seconds) { if (seconds <= 0) { throw new IllegalArgumentException("Invalid value " + seconds); } this.earlyDialogTimeout = seconds; } public void checkRetransmissionForForking(SIPResponse response) { final int statusCode = response.getStatusCode(); final String responseMethod = response.getCSeqHeader().getMethod(); final long responseCSeqNumber = response.getCSeq().getSeqNumber(); boolean isRetransmission = !responsesReceivedInForkingCase.add(statusCode + "/" + responseCSeqNumber + "/" + responseMethod); response.setRetransmission(isRetransmission); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "marking response as retransmission " + isRetransmission + " for dialog " + this); } } @Override public int hashCode() { if ( (callIdHeader == null) && // https://java.net/jira/browse/JSIP-493 (callIdHeaderString == null)) { return 0; } else { return getCallId().getCallId().hashCode(); } } /** * In case of forking scenarios, set the original dialog that had been forked * @param defaultDialog */ public void setOriginalDialog(SIPDialog originalDialog) { this.originalDialog = originalDialog; } @Override public boolean isForked() { return originalDialog != null; } @Override public Dialog getOriginalDialog() { return originalDialog; } /** * Set the ack sending strategy to be used by this dialog * @param ackSendingStrategy */ public void setAckSendingStrategy(AckSendingStrategy ackSendingStrategy) { this.ackSendingStrategy = ackSendingStrategy; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy