content: /*
   * 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
   *
   * .
   *
   */
  package gov2.nist.javax2.sip.stack;
  
  import android.os.SystemClock;
  
  import gov2.nist.core.InternalErrorHandler;
  import gov2.nist.javax2.sip.SIPConstants;
  import gov2.nist.javax2.sip.SipProviderImpl;
  import gov2.nist.javax2.sip.header.CallID;
  import gov2.nist.javax2.sip.header.Event;
  import gov2.nist.javax2.sip.header.From;
  import gov2.nist.javax2.sip.header.To;
  import gov2.nist.javax2.sip.header.Via;
  import gov2.nist.javax2.sip.header.ViaList;
  import gov2.nist.javax2.sip.message.SIPMessage;
  import gov2.nist.javax2.sip.message.SIPRequest;
  import gov2.nist.javax2.sip.message.SIPResponse;
  
  import java.io.IOException;
  import java.net.InetAddress;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Set;
  import java.util.concurrent.Semaphore;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  import javax.net.ssl.SSLPeerUnverifiedException;
  import javax2.sip.Dialog;
  import javax2.sip.IOExceptionEvent;
  import javax2.sip.ServerTransaction;
  import javax2.sip.TransactionState;
  import javax2.sip.message.Request;
  import javax2.sip.message.Response;
  
  /*
   * Modifications for TLS Support added by Daniel J. Martinez Manzano
   * <dani@dif.um.es> Bug fixes by Jeroen van Bemmel (JvB) and others.
   */
  
  /**
   * Abstract class to support both client and server transactions. Provides an
   * encapsulation of a message channel, handles timer events, and creation of the
   * Via header for a message.
   *
   * @author Jeff Keyser
   * @author M. Ranganathan
   *
   *
   * @version 1.2 $Revision: 1.71 $ $Date: 2009/11/29 04:31:29 $
   */
  public abstract class SIPTransaction extends MessageChannel implements
          javax2.sip.Transaction, gov2.nist.javax2.sip.TransactionExt {
  
      /**
  	 * 
  	 */
  	private static final long serialVersionUID = 1L;
  
  	protected boolean toListener; // Flag to indicate that the listener gets
  
      // to see the event.
  
      protected int BASE_TIMER_INTERVAL = SIPTransactionStack.BASE_TIMER_INTERVAL;
      /**
       * 5 sec Maximum duration a message will remain in the network
       */
      protected int T4 = 5000 / BASE_TIMER_INTERVAL;
  
      /**
       * The maximum retransmit interval for non-INVITE requests and INVITE
       * responses
       */
      protected int T2 = 4000 / BASE_TIMER_INTERVAL;
      protected int TIMER_I = T4;
  
      protected int TIMER_K = T4;
  
      protected int TIMER_D = 32000 / BASE_TIMER_INTERVAL;
  
      // protected static final int TIMER_C = 3 * 60 * 1000 / BASE_TIMER_INTERVAL;
  
      /**
       * One timer tick.
       */
      protected static final int T1 = 1;
  
      /**
       * INVITE request retransmit interval, for UDP only
       */
      protected static final int TIMER_A = 1;
  
      /**
       * INVITE transaction timeout timer
       */
      protected static final int TIMER_B = 64;
  
      protected static final int TIMER_J = 64;
  
      protected static final int TIMER_F = 64;
  
      protected static final int TIMER_H = 64;
  
      // Proposed feature for next release.
      protected transient Object applicationData;
  
      protected SIPResponse lastResponse;
  
      // private SIPDialog dialog;
  
      protected boolean isMapped;
  
      private Semaphore semaphore;
  
      protected boolean isSemaphoreAquired;
  
      // protected boolean eventPending; // indicate that an event is pending
      // here.
  
      protected String transactionId; // Transaction Id.
  
      // Audit tag used by the SIP Stack audit
      public long auditTag = 0;
  
      /**
       * Initialized but no state assigned.
       */
      public static final TransactionState INITIAL_STATE = null;
  
      /**
       * Trying state.
       */
      public static final TransactionState TRYING_STATE = TransactionState.TRYING;
  
      /**
       * CALLING State.
       */
      public static final TransactionState CALLING_STATE = TransactionState.CALLING;
  
      /**
       * Proceeding state.
       */
      public static final TransactionState PROCEEDING_STATE = TransactionState.PROCEEDING;
  
      /**
       * Completed state.
       */
      public static final TransactionState COMPLETED_STATE = TransactionState.COMPLETED;
  
      /**
       * Confirmed state.
       */
      public static final TransactionState CONFIRMED_STATE = TransactionState.CONFIRMED;
  
      /**
       * Terminated state.
       */
      public static final TransactionState TERMINATED_STATE = TransactionState.TERMINATED;
  
      /**
       * Maximum number of ticks between retransmissions.
       */
      protected static final int MAXIMUM_RETRANSMISSION_TICK_COUNT = 8;
  
      // Parent stack for this transaction
      protected transient SIPTransactionStack sipStack;
  
      // Original request that is being handled by this transaction
      protected SIPRequest originalRequest;
  
      // Underlying channel being used to send messages for this transaction
      private transient MessageChannel encapsulatedChannel;
  
      // Port of peer
      protected int peerPort;
  
      // Address of peer
      protected InetAddress peerInetAddress;
  
      // Address of peer as a string
      protected String peerAddress;
  
      // Protocol of peer
      protected String peerProtocol;
  
      // @@@ hagai - NAT changes
      // Source port extracted from peer packet
      protected int peerPacketSourcePort;
  
      protected InetAddress peerPacketSourceAddress;
  
      protected AtomicBoolean transactionTimerStarted = new AtomicBoolean(false);
  
      // Transaction branch ID
      private String branch;
  
      // Method of the Request used to create the transaction.
      private String method;
  
      // Sequence number of request used to create the transaction
      private long cSeq;
  
      // Current transaction state
      private TransactionState currentState;
  
      // Number of ticks the retransmission timer was set to last
      private transient int retransmissionTimerLastTickCount;
  
      // Number of ticks before the message is retransmitted
      private transient int retransmissionTimerTicksLeft;
  
      // Number of ticks before the transaction times out
      protected int timeoutTimerTicksLeft;
  
      // List of event listeners for this transaction
      private transient Set<SIPTransactionEventListener> eventListeners;
  
      // Hang on to these - we clear out the request URI after
      // transaction goes to final state. Pointers to these are kept around
      // for transaction matching as long as the transaction is in
      // the transaction table.
      protected From from;
  
      protected To to;
  
      protected Event event;
  
      protected CallID callId;
  
      // Back ptr to the JAIN layer.
      // private Object wrapper;
  
      // Counter for caching of connections.
      // Connection lingers for collectionTime
      // after the Transaction goes to terminated state.
      protected int collectionTime;
  
      protected String toTag;
  
      protected String fromTag;
  
      private boolean terminatedEventDelivered;
  
      // Timestamps by when timer tasks and retransmission tasks are considered outdated
      // Rational: As the timer tick mechanism may not work as timely as expected if the device is sleeping
      // timer tasks may be outdated even though ticks are still left
      private long timerOutdatedTime;
      private long retransmissionOutdatedTime;
  
      public String getBranchId() {
          return this.branch;
      }
  
      /**
       * The linger timer is used to remove the transaction from the transaction
       * table after it goes into terminated state. This allows connection caching
       * and also takes care of race conditins.
       *
       *
       */
      class LingerTimer extends SIPStackTimerTask {
  
          public LingerTimer() {
              SIPTransaction sipTransaction = SIPTransaction.this;
              if (sipStack.isLoggingEnabled()) {
                  sipStack.getStackLogger().logDebug("LingerTimer : "
                          + sipTransaction.getTransactionId());
              }
  
          }
  
          protected void runTask() {
              SIPTransaction transaction = SIPTransaction.this;
              // release the connection associated with this transaction.
              SIPTransactionStack sipStack = transaction.getSIPStack();
  
              if (sipStack.isLoggingEnabled()) {
                  sipStack.getStackLogger().logDebug("LingerTimer: run() : "
                          + getTransactionId());
              }
  
              if (transaction instanceof SIPClientTransaction) {
                  sipStack.removeTransaction(transaction);
                  transaction.close();
  
              } else if (transaction instanceof ServerTransaction) {
                  // Remove it from the set
                  if (sipStack.isLoggingEnabled())
                      sipStack.getStackLogger().logDebug("removing" + transaction);
                  sipStack.removeTransaction(transaction);
                  if ((!sipStack.cacheServerConnections)
                          && --transaction.encapsulatedChannel.useCount <= 0) {
                      // Close the encapsulated socket if stack is configured
                      transaction.close(); 
                  } else {
                      if (sipStack.isLoggingEnabled()
                              && (!sipStack.cacheServerConnections)
                              && transaction.isReliable()) {
                          int useCount = transaction.encapsulatedChannel.useCount;
                          sipStack.getStackLogger().logDebug("Use Count = " + useCount);
                      }
                  }
              }
  
          }
      }
  
      /**
       * Transaction constructor.
       *
       * @param newParentStack
       *            Parent stack for this transaction.
       * @param newEncapsulatedChannel
       *            Underlying channel for this transaction.
       */
      protected SIPTransaction(SIPTransactionStack newParentStack,
              MessageChannel newEncapsulatedChannel) {
  
          sipStack = newParentStack;
          this.semaphore = new Semaphore(1,true);
  
          encapsulatedChannel = newEncapsulatedChannel;
          // Record this to check if the address has changed before sending
          // message to avoid possible race condition.
          this.peerPort = newEncapsulatedChannel.getPeerPort();
          this.peerAddress = newEncapsulatedChannel.getPeerAddress();
          this.peerInetAddress = newEncapsulatedChannel.getPeerInetAddress();
          // @@@ hagai
          this.peerPacketSourcePort = newEncapsulatedChannel
                  .getPeerPacketSourcePort();
          this.peerPacketSourceAddress = newEncapsulatedChannel
                  .getPeerPacketSourceAddress();
          this.peerProtocol = newEncapsulatedChannel.getPeerProtocol();
          if (this.isReliable()) {            
                  encapsulatedChannel.useCount++;
                  if (sipStack.isLoggingEnabled())
                      sipStack.getStackLogger()
                              .logDebug("use count for encapsulated channel"
                                      + this
                                      + " "
                                      + encapsulatedChannel.useCount );
          }
  
          this.currentState = null;
  
          disableRetransmissionTimer();
          disableTimeoutTimer();
          eventListeners = Collections.synchronizedSet(new HashSet<SIPTransactionEventListener>());
  
          // Always add the parent stack as a listener
          // of this transaction
          addEventListener(newParentStack);
  
      }
  
      /**
       * Sets the request message that this transaction handles.
       *
       * @param newOriginalRequest
       *            Request being handled.
       */
      public void setOriginalRequest(SIPRequest newOriginalRequest) {
  
          // Branch value of topmost Via header
          String newBranch;
  
          if (this.originalRequest != null
                  && (!this.originalRequest.getTransactionId().equals(
                          newOriginalRequest.getTransactionId()))) {
              sipStack.removeTransactionHash(this);
          }
          // This will be cleared later.
  
          this.originalRequest = newOriginalRequest;
  
          // just cache the control information so the
          // original request can be released later.
          this.method = newOriginalRequest.getMethod();
          this.from = (From) newOriginalRequest.getFrom();
          this.to = (To) newOriginalRequest.getTo();
          // Save these to avoid concurrent modification exceptions!
          this.toTag = this.to.getTag();
          this.fromTag = this.from.getTag();
          this.callId = (CallID) newOriginalRequest.getCallId();
          this.cSeq = newOriginalRequest.getCSeq().getSeqNumber();
          this.event = (Event) newOriginalRequest.getHeader("Event");
          this.transactionId = newOriginalRequest.getTransactionId();
  
          originalRequest.setTransaction(this);
  
          // If the message has an explicit branch value set,
          newBranch = ((Via) newOriginalRequest.getViaHeaders().getFirst())
                  .getBranch();
          if (newBranch != null) {
              if (sipStack.isLoggingEnabled())
                  sipStack.getStackLogger().logDebug("Setting Branch id : " + newBranch);
  
              // Override the default branch with the one
              // set by the message
              setBranch(newBranch);
  
          } else {
              if (sipStack.isLoggingEnabled())
                  sipStack.getStackLogger().logDebug("Branch id is null - compute TID!"
                          + newOriginalRequest.encode());
              setBranch(newOriginalRequest.getTransactionId());
          }
      }
  
      /**
       * Gets the request being handled by this transaction.
       *
       * @return -- the original Request associated with this transaction.
       */
      public SIPRequest getOriginalRequest() {
          return originalRequest;
      }
  
      /**
       * Get the original request but cast to a Request structure.
       *
       * @return the request that generated this transaction.
       */
      public Request getRequest() {
          return (Request) originalRequest;
      }
  
      /**
       * Returns a flag stating whether this transaction is for an INVITE request
       * or not.
       *
       * @return -- true if this is an INVITE request, false if not.
       */
      public final boolean isInviteTransaction() {
          return getMethod().equals(Request.INVITE);
      }
  
      /**
       * Return true if the transaction corresponds to a CANCEL message.
       *
       * @return -- true if the transaciton is a CANCEL transaction.
       */
      public final boolean isCancelTransaction() {
          return getMethod().equals(Request.CANCEL);
      }
  
      /**
       * Return a flag that states if this is a BYE transaction.
       *
       * @return true if the transaciton is a BYE transaction.
       */
      public final boolean isByeTransaction() {
          return getMethod().equals(Request.BYE);
      }
  
      /**
       * Returns the message channel used for transmitting/receiving messages for
       * this transaction. Made public in support of JAIN dual transaction model.
       *
       * @return Encapsulated MessageChannel.
       *
       */
      public MessageChannel getMessageChannel() {
          return encapsulatedChannel;
      }
  
      /**
       * Sets the Via header branch parameter used to identify this transaction.
       *
       * @param newBranch
       *            New string used as the branch for this transaction.
       */
      public final void setBranch(String newBranch) {
          branch = newBranch;
      }
  
      /**
       * Gets the current setting for the branch parameter of this transaction.
       *
       * @return Branch parameter for this transaction.
       */
      public final String getBranch() {
          if (this.branch == null) {
              this.branch = getOriginalRequest().getTopmostVia().getBranch();
          }
          return branch;
      }
  
      /**
       * Get the method of the request used to create this transaction.
       *
       * @return the method of the request for the transaction.
       */
      public final String getMethod() {
          return this.method;
      }
  
      /**
       * Get the Sequence number of the request used to create the transaction.
       *
       * @return the cseq of the request used to create the transaction.
       */
      public final long getCSeq() {
          return this.cSeq;
      }
  
      /**
       * Changes the state of this transaction.
       *
       * @param newState
       *            New state of this transaction.
       */
      public void setState(TransactionState newState) {
          // PATCH submitted by sribeyron
          if (currentState == TransactionState.COMPLETED) {
              if (newState != TransactionState.TERMINATED
                      && newState != TransactionState.CONFIRMED)
                  newState = TransactionState.COMPLETED;
          }
          if (currentState == TransactionState.CONFIRMED) {
              if (newState != TransactionState.TERMINATED)
                  newState = TransactionState.CONFIRMED;
          }
          if (currentState != TransactionState.TERMINATED)
              currentState = newState;
          else
              newState = currentState;
          // END OF PATCH
          if (sipStack.isLoggingEnabled()) {
              sipStack.getStackLogger().logDebug("Transaction:setState " + newState
                      + " " + this + " branchID = " + this.getBranch()
                      + " isClient = " + (this instanceof SIPClientTransaction));
              sipStack.getStackLogger().logStackTrace();
          }
      }
  
      /**
       * Gets the current state of this transaction.
       *
       * @return Current state of this transaction.
       */
      public TransactionState getState() {
          return this.currentState;
      }
  
      /**
       * Enables retransmission timer events for this transaction to begin in one
       * tick.
       */
      protected final void enableRetransmissionTimer() {
          enableRetransmissionTimer(1);
      }
  
      /**
       * Enables retransmission timer events for this transaction to begin after
       * the number of ticks passed to this routine.
       *
       * @param tickCount
       *            Number of ticks before the next retransmission timer event
       *            occurs.
       */
      protected final void enableRetransmissionTimer(int tickCount) {
          // For INVITE Client transactions, double interval each time
          if (isInviteTransaction() && (this instanceof SIPClientTransaction)) {
              retransmissionTimerTicksLeft = tickCount;
          } else {
              // non-INVITE transactions and 3xx-6xx responses are capped at T2
              retransmissionTimerTicksLeft = Math.min(tickCount,
                      MAXIMUM_RETRANSMISSION_TICK_COUNT);
          }
          retransmissionTimerLastTickCount = retransmissionTimerTicksLeft;
  
          // set the timestamp by when the retransmission timer is considered outdated no matter
          // how many ticks are still left
          retransmissionOutdatedTime = SystemClock.elapsedRealtime() + retransmissionTimerTicksLeft * BASE_TIMER_INTERVAL;
      }
  
      /**
       * Turns off retransmission events for this transaction.
       */
      protected final void disableRetransmissionTimer() {
          retransmissionTimerTicksLeft = -1;
      }
  
      /**
       * Enables a timeout event to occur for this transaction after the number of
       * ticks passed to this method.
       *
       * @param tickCount
       *            Number of ticks before this transaction times out.
       */
      protected final void enableTimeoutTimer(int tickCount) {
          if (sipStack.isLoggingEnabled())
              sipStack.getStackLogger().logDebug("enableTimeoutTimer " + this
                      + " tickCount " + tickCount + " currentTickCount = "
                      + timeoutTimerTicksLeft);
  
          timeoutTimerTicksLeft = tickCount;
  
          // set the timestamp by when the timer is considered outdated no matter how many
          // ticks are still left
          timerOutdatedTime = SystemClock.elapsedRealtime() + tickCount * BASE_TIMER_INTERVAL;
      }
  
      /**
       * Disabled the timeout timer.
       */
      protected final void disableTimeoutTimer() {
          timeoutTimerTicksLeft = -1;
      }
  
      /**
       * Fired after each timer tick. Checks the retransmission and timeout timers
       * of this transaction, and fired these events if necessary.
       */
      final void fireTimer() {
          // If the timeout timer is enabled,
  
          if (timeoutTimerTicksLeft != -1) {
              // Count down the timer, and if it has run out,
              // (or time limit has exceeded (e.g. due to timer thread was sleeping too long))
              if (--timeoutTimerTicksLeft == 0 || SystemClock.elapsedRealtime() > timerOutdatedTime) {
                  // Fire the timeout timer
                  fireTimeoutTimer();
              }
          }
  
          // If the retransmission timer is enabled,
          if (retransmissionTimerTicksLeft != -1) {
              // Count down the timer, and if it has run out,
              // (or time limit has exceeded (e.g. due to timer thread was sleeping too long))
              if (--retransmissionTimerTicksLeft == 0 || SystemClock.elapsedRealtime() > retransmissionOutdatedTime) {
                  // Enable this timer to fire again after
                  // twice the original time
                  enableRetransmissionTimer(retransmissionTimerLastTickCount * 2);
                  // Fire the timeout timer
                  fireRetransmissionTimer();
              }
          } 
      }
  
      /**
       * Tests if this transaction has terminated.
       *
       * @return Trus if this transaction is terminated, false if not.
       */
      public final boolean isTerminated() {
          return getState() == TERMINATED_STATE;
      }
  
      public String getHost() {
          return encapsulatedChannel.getHost();
      }
  
      public String getKey() {
          return encapsulatedChannel.getKey();
      }
  
      public int getPort() {
          return encapsulatedChannel.getPort();
      }
  
      public SIPTransactionStack getSIPStack() {
          return (SIPTransactionStack) sipStack;
      }
  
      public String getPeerAddress() {
          return this.peerAddress;
      }
  
      public int getPeerPort() {
          return this.peerPort;
      }
  
      // @@@ hagai
      public int getPeerPacketSourcePort() {
          return this.peerPacketSourcePort;
      }
  
      public InetAddress getPeerPacketSourceAddress() {
          return this.peerPacketSourceAddress;
      }
  
      protected InetAddress getPeerInetAddress() {
          return this.peerInetAddress;
      }
  
      protected String getPeerProtocol() {
          return this.peerProtocol;
      }
  
      public String getTransport() {
          return encapsulatedChannel.getTransport();
      }
  
      public boolean isReliable() {
          return encapsulatedChannel.isReliable();
      }
  
      /**
       * Returns the Via header for this channel. Gets the Via header of the
       * underlying message channel, and adds a branch parameter to it for this
       * transaction.
       */
      public Via getViaHeader() {
          // Via header of the encapulated channel
          Via channelViaHeader;
  
          // Add the branch parameter to the underlying
          // channel's Via header
          channelViaHeader = super.getViaHeader();
          try {
              channelViaHeader.setBranch(branch);
          } catch (java.text.ParseException ex) {
          }
          return channelViaHeader;
  
      }
  
      /**
       * Process the message through the transaction and sends it to the SIP peer.
       *
       * @param messageToSend
       *            Message to send to the SIP peer.
       */
      public void sendMessage(SIPMessage messageToSend) throws IOException {
          // Use the peer address, port and transport
          // that was specified when the transaction was
          // created. Bug was noted by Bruce Evangelder
          // soleo communications.
          try {
              encapsulatedChannel.sendMessage(messageToSend,
                      this.peerInetAddress, this.peerPort);
          } finally {
              this.startTransactionTimer();
          }
      }
  
      /**
       * Parse the byte array as a message, process it through the transaction,
       * and send it to the SIP peer. This is just a placeholder method -- calling
       * it will result in an IO exception.
       *
       * @param message
       *            the message to send.
       * @param receiverAddress
       *            Address of the target peer.
       * @param receiverPort
       *            Network port of the target peer.
       *
       * @throws IOException
       *             If called.
       */
      protected void sendMessage(SIPMessage message,
              InetAddress receiverAddress, int receiverPort, boolean retry)
              throws IOException {
          throw new IOException(
                  "Cannot send unparsed message through Transaction Channel!");
      }
  
      /**
       * Adds a new event listener to this transaction.
       *
       * @param newListener
       *            Listener to add.
       */
      public void addEventListener(SIPTransactionEventListener newListener) {
          eventListeners.add(newListener);
      }
  
      /**
       * Removed an event listener from this transaction.
       *
       * @param oldListener
       *            Listener to remove.
       */
      public void removeEventListener(SIPTransactionEventListener oldListener) {
          eventListeners.remove(oldListener);
      }
  
      /**
       * Creates a SIPTransactionErrorEvent and sends it to all of the listeners
       * of this transaction. This method also flags the transaction as
       * terminated.
       *
       * @param errorEventID
       *            ID of the error to raise.
       */
      protected void raiseErrorEvent(int errorEventID) {
  
          // Error event to send to all listeners
          SIPTransactionErrorEvent newErrorEvent;
          // Iterator through the list of listeners
          Iterator<SIPTransactionEventListener> listenerIterator;
          // Next listener in the list
          SIPTransactionEventListener nextListener;
  
          // Create the error event
          newErrorEvent = new SIPTransactionErrorEvent(this, errorEventID);
  
          // Loop through all listeners of this transaction
          synchronized (eventListeners) {
              listenerIterator = eventListeners.iterator();
              while (listenerIterator.hasNext()) {
                  // Send the event to the next listener
                  nextListener = (SIPTransactionEventListener) listenerIterator
                          .next();
                  nextListener.transactionErrorEvent(newErrorEvent);
              }
          }
          // Clear the event listeners after propagating the error.
          // Retransmit notifications are just an alert to the
          // application (they are not an error).
          if (errorEventID != SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) {
              eventListeners.clear();
  
              // Errors always terminate a transaction
              this.setState(TransactionState.TERMINATED);
  
              if (this instanceof SIPServerTransaction && this.isByeTransaction()
                      && this.getDialog() != null)
                  ((SIPDialog) this.getDialog())
                          .setState(SIPDialog.TERMINATED_STATE);
          }
      }
  
      /**
       * A shortcut way of telling if we are a server transaction.
       */
      protected boolean isServerTransaction() {
          return this instanceof SIPServerTransaction;
      }
  
      /**
       * Gets the dialog object of this Transaction object. This object returns
       * null if no dialog exists. A dialog only exists for a transaction when a
       * session is setup between a User Agent Client and a User Agent Server,
       * either by a 1xx Provisional Response for an early dialog or a 200OK
       * Response for a committed dialog.
       *
       * @return the Dialog Object of this Transaction object.
       * @see Dialog
       */
      public abstract Dialog getDialog();
  
      /**
       * set the dialog object.
       *
       * @param sipDialog --
       *            the dialog to set.
       * @param dialogId --
       *            the dialog id ot associate with the dialog.s
       */
      public abstract void setDialog(SIPDialog sipDialog, String dialogId);
  
      /**
       * Returns the current value of the retransmit timer in milliseconds used to
       * retransmit messages over unreliable transports.
       *
       * @return the integer value of the retransmit timer in milliseconds.
       */
      public int getRetransmitTimer() {
          return SIPTransactionStack.BASE_TIMER_INTERVAL;
      }
  
      /**
       * Get the host to assign for an outgoing Request via header.
       */
      public String getViaHost() {
          return this.getViaHeader().getHost();
  
      }
  
      /**
       * Get the last response. This is used internally by the implementation.
       * Dont rely on it.
       *
       * @return the last response received (for client transactions) or sent (for
       *         server transactions).
       */
      public SIPResponse getLastResponse() {
          return this.lastResponse;
      }
  
      /**
       * Get the JAIN interface response
       */
      public Response getResponse() {
          return (Response) this.lastResponse;
      }
  
      /**
       * Get the transaction Id.
       */
      public String getTransactionId() {
          return this.transactionId;
      }
  
      /**
       * Hashcode method for fast hashtable lookup.
       */
      public int hashCode() {
          if (this.transactionId == null)
              return -1;
          else
              return this.transactionId.hashCode();
      }
  
      /**
       * Get the port to assign for the via header of an outgoing message.
       */
      public int getViaPort() {
          return this.getViaHeader().getPort();
      }
  
      /**
       * A method that can be used to test if an incoming request belongs to this
       * transction. This does not take the transaction state into account when
       * doing the check otherwise it is identical to isMessagePartOfTransaction.
       * This is useful for checking if a CANCEL belongs to this transaction.
       *
       * @param requestToTest
       *            is the request to test.
       * @return true if the the request belongs to the transaction.
       *
       */
      public boolean doesCancelMatchTransaction(SIPRequest requestToTest) {
  
          // List of Via headers in the message to test
          ViaList viaHeaders;
          // Topmost Via header in the list
          Via topViaHeader;
          // Branch code in the topmost Via header
          String messageBranch;
          // Flags whether the select message is part of this transaction
          boolean transactionMatches;
  
          transactionMatches = false;
  
          if (this.getOriginalRequest() == null
                  || this.getOriginalRequest().getMethod().equals(Request.CANCEL))
              return false;
          // Get the topmost Via header and its branch parameter
          viaHeaders = requestToTest.getViaHeaders();
          if (viaHeaders != null) {
  
              topViaHeader = (Via) viaHeaders.getFirst();
              messageBranch = topViaHeader.getBranch();
              if (messageBranch != null) {
  
                  // If the branch parameter exists but
                  // does not start with the magic cookie,
                  if (!messageBranch.toLowerCase().startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
  
                      // Flags this as old
                      // (RFC2543-compatible) client
                      // version
                      messageBranch = null;
  
                  }
  
              }
  
              // If a new branch parameter exists,
              if (messageBranch != null && this.getBranch() != null) {
  
                  // If the branch equals the branch in
                  // this message,
                  if (getBranch().equalsIgnoreCase(messageBranch)
                          && topViaHeader.getSentBy().equals(
                                  ((Via) getOriginalRequest().getViaHeaders()
                                          .getFirst()).getSentBy())) {
                      transactionMatches = true;
                      if (sipStack.isLoggingEnabled())
                          sipStack.getStackLogger().logDebug("returning  true");
                  }
  
              } else {
                  // If this is an RFC2543-compliant message,
                  // If RequestURI, To tag, From tag,
                  // CallID, CSeq number, and top Via
                  // headers are the same,
                  if (sipStack.isLoggingEnabled())
                      sipStack.getStackLogger().logDebug("testing against "
                              + getOriginalRequest());
  
                  if (getOriginalRequest().getRequestURI().equals(
                          requestToTest.getRequestURI())
                          && getOriginalRequest().getTo().equals(
                                  requestToTest.getTo())
                          && getOriginalRequest().getFrom().equals(
                                  requestToTest.getFrom())
                          && getOriginalRequest().getCallId().getCallId().equals(
                                  requestToTest.getCallId().getCallId())
                          && getOriginalRequest().getCSeq().getSeqNumber() == requestToTest
                                  .getCSeq().getSeqNumber()
                          && topViaHeader.equals(getOriginalRequest()
                                  .getViaHeaders().getFirst())) {
  
                      transactionMatches = true;
                  }
  
              }
  
          }
  
          // JvB: Need to pass the CANCEL to the listener! Retransmitted INVITEs
          // set it to false
          if (transactionMatches) {
              this.setPassToListener();
          }
          return transactionMatches;
      }
  
      /**
       * Sets the value of the retransmit timer to the newly supplied timer value.
       * The retransmit timer is expressed in milliseconds and its default value
       * is 500ms. This method allows the application to change the transaction
       * retransmit behavior for different networks. Take the gateway proxy as an
       * example. The internal intranet is likely to be reatively uncongested and
       * the endpoints will be relatively close. The external network is the
       * general Internet. This functionality allows different retransmit times
       * for either side.
       *
       * @param retransmitTimer -
       *            the new integer value of the retransmit timer in milliseconds.
       */
      public void setRetransmitTimer(int retransmitTimer) {
  
          if (retransmitTimer <= 0)
              throw new IllegalArgumentException(
                      "Retransmit timer must be positive!");
          if (this.transactionTimerStarted.get())
              throw new IllegalStateException(
                      "Transaction timer is already started");
          BASE_TIMER_INTERVAL = retransmitTimer;
          T4 = 5000 / BASE_TIMER_INTERVAL;
  
          T2 = 4000 / BASE_TIMER_INTERVAL;
          TIMER_I = T4;
  
          TIMER_K = T4;
  
          TIMER_D = 32000 / BASE_TIMER_INTERVAL;
  
      }
  
      /**
       * Sets timers value. Bug fix OrangeLabs, JOGUET Benoit.
       *
       * @param timer_T1
       * @param timer_T2
       * @param timer_T4
       */
      public void setRetransmitTimers(int timer_T1, int timer_T2, int timer_T4) {
          if ((timer_T1 <= 0) || (timer_T2 <= 0) || (timer_T4 <= 0)) {
              throw new IllegalArgumentException("Retransmit timers must be positives!");
          }
          if (this.transactionTimerStarted.get()) {
              throw new IllegalStateException("Transaction timer is already started");
          }
          BASE_TIMER_INTERVAL = timer_T1;
          T4 = timer_T4 / BASE_TIMER_INTERVAL;
          T2 = timer_T2 / BASE_TIMER_INTERVAL;
          TIMER_I = T4;
          TIMER_K = T4;
          TIMER_D = 32000 / BASE_TIMER_INTERVAL;
      }
  
      /**
       * Close the encapsulated channel.
       */
      public void close() {
          this.encapsulatedChannel.close();
          if (sipStack.isLoggingEnabled())
              sipStack.getStackLogger().logDebug("Closing " + this.encapsulatedChannel);
  
      }
  
      public boolean isSecure() {
          return encapsulatedChannel.isSecure();
      }
  
      public MessageProcessor getMessageProcessor() {
          return this.encapsulatedChannel.getMessageProcessor();
      }
  
      /**
       * Set the application data pointer. This is un-interpreted by the stack.
       * This is provided as a conveniant way of keeping book-keeping data for
       * applications. Note that null clears the application data pointer
       * (releases it).
       *
       * @param applicationData --
       *            application data pointer to set. null clears the applicationd
       *            data pointer.
       *
       */
  
      public void setApplicationData(Object applicationData) {
          this.applicationData = applicationData;
      }
  
      /**
       * Get the application data associated with this transaction.
       *
       * @return stored application data.
       */
      public Object getApplicationData() {
          return this.applicationData;
      }
  
      /**
       * Set the encapsuated channel. The peer inet address and port are set equal
       * to the message channel.
       */
      public void setEncapsulatedChannel(MessageChannel messageChannel) {
          this.encapsulatedChannel = messageChannel;
          this.peerInetAddress = messageChannel.getPeerInetAddress();
          this.peerPort = messageChannel.getPeerPort();
      }
  
      /**
       * Return the SipProvider for which the transaction is assigned.
       *
       * @return the SipProvider for the transaction.
       */
      public SipProviderImpl getSipProvider() {
  
          return this.getMessageProcessor().getListeningPoint().getProvider();
      }
  
      /**
       * Raise an IO Exception event - this is used for reporting asynchronous IO
       * Exceptions that are attributable to this transaction.
       *
       */
      public void raiseIOExceptionEvent() {
          setState(TransactionState.TERMINATED);
          String host = getPeerAddress();
          int port = getPeerPort();
          String transport = getTransport();
          IOExceptionEvent exceptionEvent = new IOExceptionEvent(this, host,
                  port, transport);
          getSipProvider().handleEvent(exceptionEvent, this);
      }
  
      /**
       * A given tx can process only a single outstanding event at a time. This
       * semaphore gaurds re-entrancy to the transaction.
       *
       */
      public boolean acquireSem() {
          boolean retval = false;
          try {
              if (sipStack.getStackLogger().isLoggingEnabled()) {
                  sipStack.getStackLogger().logDebug("acquireSem [[[[" + this);
                  sipStack.getStackLogger().logStackTrace();
              }
              retval = this.semaphore.tryAcquire(1000, TimeUnit.MILLISECONDS);
              if ( sipStack.isLoggingEnabled())
                  sipStack.getStackLogger().logDebug(
                      "acquireSem() returning : " + retval);
              return retval;
          } catch (Exception ex) {
              if (sipStack.isLoggingEnabled())
              	sipStack.getStackLogger().logError("Unexpected exception acquiring sem",
                      ex);
              InternalErrorHandler.handleException(ex);
              return false;
          } finally {
              this.isSemaphoreAquired = retval;
          }
  
      }
  
      /**
       * Release the transaction semaphore.
       *
       */
      public void releaseSem() {
          try {
  
              this.toListener = false;
              this.semRelease();
  
          } catch (Exception ex) {
              if (sipStack.isLoggingEnabled())
              	sipStack.getStackLogger().logError("Unexpected exception releasing sem",
                      ex);
  
          }
  
      }
  
      protected void semRelease() {
          try {
              if (sipStack.isLoggingEnabled()) {
                  sipStack.getStackLogger().logDebug("semRelease ]]]]" + this);
                  sipStack.getStackLogger().logStackTrace();
              }
              this.isSemaphoreAquired = false;
              this.semaphore.release();
  
          } catch (Exception ex) {
              if (sipStack.isLoggingEnabled())
              	sipStack.getStackLogger().logError("Unexpected exception releasing sem",
                      ex);
  
          }
      }
  
      /**
       * Set true to pass the request up to the listener. False otherwise.
       *
       */
  
      public boolean passToListener() {
          return toListener;
      }
  
      /**
       * Set the passToListener flag to true.
       */
      public void setPassToListener() {
          if (sipStack.isLoggingEnabled()) {
              sipStack.getStackLogger().logDebug("setPassToListener()");
          }
          this.toListener = true;
  
      }
  
      /**
       * Flag to test if the terminated event is delivered.
       *
       * @return
       */
      protected synchronized boolean testAndSetTransactionTerminatedEvent() {
          boolean retval = !this.terminatedEventDelivered;
          this.terminatedEventDelivered = true;
          return retval;
      }
      
      public String getCipherSuite() throws UnsupportedOperationException {
          if (this.getMessageChannel() instanceof TLSMessageChannel ) {
              if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) 
                  return null;
              else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                  return null;
              else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getCipherSuite();
          } else throw new UnsupportedOperationException("Not a TLS channel");
  
      }
  
      
      public java.security.cert.Certificate[] getLocalCertificates() throws UnsupportedOperationException {
           if (this.getMessageChannel() instanceof TLSMessageChannel ) {
              if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) 
                  return null;
              else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                  return null;
              else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getLocalCertificates();
          } else throw new UnsupportedOperationException("Not a TLS channel");
      }
  
      
      public java.security.cert.Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
          if (this.getMessageChannel() instanceof TLSMessageChannel ) {
              if (  ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) 
                  return null;
              else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null)
                  return null;
              else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getPeerCertificates();
          } else throw new UnsupportedOperationException("Not a TLS channel");
  
      }
  
  
      /**
       * Start the timer that runs the transaction state machine.
       *
       */
  
      protected abstract void startTransactionTimer();
  
      /**
       * Tests a message to see if it is part of this transaction.
       *
       * @return True if the message is part of this transaction, false if not.
       */
      public abstract boolean isMessagePartOfTransaction(SIPMessage messageToTest);
  
      /**
       * This method is called when this transaction's retransmission timer has
       * fired.
       */
      protected abstract void fireRetransmissionTimer();
  
      /**
       * This method is called when this transaction's timeout timer has fired.
       */
      protected abstract void fireTimeoutTimer();    
  
  }
  