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 gov2.nist.core.Host;
  import gov2.nist.core.HostPort;
  import gov2.nist.core.ServerLogger;
  import gov2.nist.core.StackLogger;
  import gov2.nist.core.ThreadAuditor;
  import gov2.nist.core.net.AddressResolver;
  import gov2.nist.core.net.DefaultNetworkLayer;
  import gov2.nist.core.net.NetworkLayer;
  import gov2.nist.javax2.sip.DefaultAddressResolver;
  import gov2.nist.javax2.sip.ListeningPointImpl;
  import gov2.nist.javax2.sip.LogRecordFactory;
  import gov2.nist.javax2.sip.SIPConstants;
  import gov2.nist.javax2.sip.SipListenerExt;
  import gov2.nist.javax2.sip.SipProviderImpl;
  import gov2.nist.javax2.sip.SipStackImpl;
  import gov2.nist.javax2.sip.header.Event;
  import gov2.nist.javax2.sip.header.Via;
  import gov2.nist.javax2.sip.header.extensions.JoinHeader;
  import gov2.nist.javax2.sip.header.extensions.ReplacesHeader;
  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.net.NetworkInterface;
  import java.net.SocketAddress;
  import java.net.UnknownHostException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.Set;
  import java.util.Timer;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import javax2.sip.ClientTransaction;
  import javax2.sip.Dialog;
  import javax2.sip.DialogState;
  import javax2.sip.DialogTerminatedEvent;
  import javax2.sip.ServerTransaction;
  import javax2.sip.SipException;
  import javax2.sip.SipListener;
  import javax2.sip.TransactionState;
  import javax2.sip.TransactionTerminatedEvent;
  import javax2.sip.address.Hop;
  import javax2.sip.address.Router;
  import javax2.sip.header.CallIdHeader;
  import javax2.sip.header.EventHeader;
  import javax2.sip.message.Request;
  import javax2.sip.message.Response;
  
  /*
   * Jeff Keyser : architectural suggestions and contributions. Pierre De Rop and Thomas Froment :
   * Bug reports. Jeyashankher < jai@lucent.com > : bug reports. Jeroen van Bemmel : Bug fixes.
   *
   *
   */
  
  /**
   *
   * This is the sip stack. It is essentially a management interface. It manages the resources for
   * the JAIN-SIP implementation. This is the structure that is wrapped by the SipStackImpl.
   *
   * @see gov2.nist.javax2.sip.SipStackImpl
   *
   * @author M. Ranganathan <br/>
   *
   * @version 1.2 $Revision: 1.141 $ $Date: 2009/12/17 23:38:27 $
   */
  public abstract class SIPTransactionStack implements SIPTransactionEventListener, SIPDialogEventListener {
  
      /*
       * Number of milliseconds between timer ticks (500).
       */
      public static final int BASE_TIMER_INTERVAL = 500;
  
      /*
       * Connection linger time (seconds) this is the time (in seconds) for which we linger the TCP
       * connection before closing it.
       */
      public static final int CONNECTION_LINGER_TIME = 8;
  
      /*
       * Table of retransmission Alert timers.
       */
      protected ConcurrentHashMap<String, SIPServerTransaction> retransmissionAlertTransactions;
  
      // Table of early dialogs ( to keep identity mapping )
      protected ConcurrentHashMap<String, SIPDialog> earlyDialogTable;
  
      // Table of dialogs.
      protected ConcurrentHashMap<String, SIPDialog> dialogTable;
  
      // A set of methods that result in dialog creations.
      protected static final Set<String> dialogCreatingMethods = new HashSet<String>();
  
      // Global timer. Use this for all timer tasks.
  
      private Timer timer;
  
      // List of pending server transactions
      private ConcurrentHashMap<String, SIPServerTransaction> pendingTransactions;
  
      // hashtable for fast lookup
      private ConcurrentHashMap<String, SIPClientTransaction> clientTransactionTable;
  
      // Set to false if you want hiwat and lowat to be consulted.
      protected boolean unlimitedServerTransactionTableSize = true;
  
      // Set to false if you want unlimited size of client trnansactin table.
      protected boolean unlimitedClientTransactionTableSize = true;
  
      // High water mark for ServerTransaction Table
      // after which requests are dropped.
      protected int serverTransactionTableHighwaterMark = 5000;
  
      // Low water mark for Server Tx table size after which
      // requests are selectively dropped
      protected int serverTransactionTableLowaterMark = 4000;
  
      // Hiwater mark for client transaction table. These defaults can be
      // overriden by stack
      // configuration.
      protected int clientTransactionTableHiwaterMark = 1000;
  
      // Low water mark for client tx table.
      protected int clientTransactionTableLowaterMark = 800;
  
      private AtomicInteger activeClientTransactionCount = new AtomicInteger(0);
  
      // Hashtable for server transactions.
      private ConcurrentHashMap<String, SIPServerTransaction> serverTransactionTable;
  
      // A table of ongoing transactions indexed by mergeId ( for detecting merged
      // requests.
      private ConcurrentHashMap<String, SIPServerTransaction> mergeTable;
      
      private ConcurrentHashMap<String,SIPServerTransaction> terminatedServerTransactionsPendingAck;
      
      private ConcurrentHashMap<String,SIPClientTransaction> forkedClientTransactionTable;
  
      /*
       * A wrapper around differnt logging implementations (log4j, commons logging, slf4j, ...) to help log debug.
       */
      private StackLogger stackLogger;
  
      /*
       * ServerLog is used just for logging stack message tracecs.
       */
      protected ServerLogger serverLogger;
  
      /*
       * We support UDP on this stack.
       */
      boolean udpFlag;
  
      /*
       * Internal router. Use this for all sip: request routing.
       *
       */
      protected DefaultRouter defaultRouter;
  
      /*
       * Global flag that turns logging off
       */
      protected boolean needsLogging;
  
      /*
       * Flag used for testing TI, bypasses filtering of ACK to non-2xx
       */
      private boolean non2XXAckPassedToListener;
  
      /*
       * Class that handles caching of TCP/TLS connections.
       */
      protected IOHandler ioHandler;
  
      /*
       * Flag that indicates that the stack is active.
       */
      protected boolean toExit;
  
      /*
       * Name of the stack.
       */
      protected String stackName;
  
      /*
       * IP address of stack -- this can be re-written by stun.
       *
       * @deprecated
       */
      protected String stackAddress;
  
      /*
       * INET address of stack (cached to avoid repeated lookup)
       *
       * @deprecated
       */
      protected InetAddress stackInetAddress;
  
      // Changed by Deutsche Telekom
      /**
       * Default SIP port
       */
      public final static int DEFAULT_MTU_SIZE = 1500;
  
      // Changed by Deutsche Telekom
      /**
       * MTU size
       */
      private int mtuSize = DEFAULT_MTU_SIZE;
  
      /*
       * Request factory interface (to be provided by the application)
       */
      protected StackMessageFactory sipMessageFactory;
  
      /*
       * Router to determine where to forward the request.
       */
      protected javax2.sip.address.Router router;
  
      /*
       * Number of pre-allocated threads for processing udp messages. -1 means no preallocated
       * threads ( dynamically allocated threads).
       */
      protected int threadPoolSize;
  
      /*
       * max number of simultaneous connections.
       */
      protected int maxConnections;
  
      /*
       * Close accept socket on completion.
       */
      protected boolean cacheServerConnections;
  
      /*
       * Close connect socket on Tx termination.
       */
      protected boolean cacheClientConnections;
  
      /*
       * Use the user supplied router for all out of dialog requests.
       */
      protected boolean useRouterForAll;
  
      /*
       * Max size of message that can be read from a TCP connection.
       */
      protected int maxContentLength;
  
      /*
       * Max # of headers that a SIP message can contain.
       */
      protected int maxMessageSize;
  
      /*
       * A collection of message processors.
       */
      private Collection<MessageProcessor> messageProcessors;
  
      /*
       * Read timeout on TCP incoming sockets -- defines the time between reads for after delivery
       * of first byte of message.
       */
      protected int readTimeout;
  
      /*
       * The socket factory. Can be overriden by applications that want direct access to the
       * underlying socket.
       */
  
      protected NetworkLayer networkLayer;
  
      /*
       * Outbound proxy String ( to be handed to the outbound proxy class on creation).
       */
      protected String outboundProxy;
  
      protected String routerPath;
  
      // Flag to indicate whether the stack will provide dialog
      // support.
      protected boolean isAutomaticDialogSupportEnabled;
  
      // The set of events for which subscriptions can be forked.
  
      protected HashSet<String> forkedEvents;
  
      // Generate a timestamp header for retransmitted requests.
      protected boolean generateTimeStampHeader;
  
      protected AddressResolver addressResolver;
  
      // Max time that the listener is allowed to take to respond to a
      // request. Default is "infinity". This property allows
      // containers to defend against buggy clients (that do not
      // want to respond to requests).
      protected int maxListenerResponseTime;
  
    
      // A flag that indicates whether or not RFC 2543 clients are fully supported.
      // If this is set to true, then To tag checking on the Dialog layer is
      // disabled in a few places - resulting in possible breakage of forked dialogs.
      protected boolean rfc2543Supported = true;
  
      // / Provides a mechanism for applications to check the health of threads in
      // the stack
      protected ThreadAuditor threadAuditor = new ThreadAuditor();
  
      protected LogRecordFactory logRecordFactory;
  
      // Set to true if the client CANCEL transaction should be checked before sending
      // it out.
      protected boolean cancelClientTransactionChecked = true;
  
      // Is to tag reassignment allowed.
      protected boolean remoteTagReassignmentAllowed = true;
  
      protected boolean logStackTraceOnMessageSend = true;
     
      // Receive UDP buffer size
      protected int receiveUdpBufferSize;
      
      // Send UDP buffer size
      protected int sendUdpBufferSize;
  
      protected boolean stackDoesCongestionControl = true;
  
      protected boolean isBackToBackUserAgent = false;
  
      protected boolean checkBranchId;
  
  	protected boolean isAutomaticDialogErrorHandlingEnabled = true;
  	
  	protected boolean isDialogTerminatedEventDeliveredForNullDialog = false;
  	
  	// Max time for a forked response to arrive. After this time, the original dialog
  	// is not tracked. If you want to track the original transaction you need to specify
  	// the max fork time with a stack init property.
  	protected int maxForkTime = 0;
  
     
      // / Timer to regularly ping the thread auditor (on behalf of the timer
      // thread)
      class PingTimer extends SIPStackTimerTask {
          // / Timer thread handle
          ThreadAuditor.ThreadHandle threadHandle;
  
          // / Constructor
          public PingTimer(ThreadAuditor.ThreadHandle a_oThreadHandle) {
              threadHandle = a_oThreadHandle;
          }
  
          protected void runTask() {
              // Check if we still have a timer (it may be null after shutdown)
              if (getTimer() != null) {
                  // Register the timer task if we haven't done so
                  if (threadHandle == null) {
                      // This happens only once since the thread handle is passed
                      // to the next scheduled ping timer
                      threadHandle = getThreadAuditor().addCurrentThread();
                  }
  
                  // Let the thread auditor know that the timer task is alive
                  threadHandle.ping();
  
                  // Schedule the next ping
                  getTimer().schedule(new PingTimer(threadHandle),
                          threadHandle.getPingIntervalInMillisecs());
              }
          }
  
      }
      
      
      class RemoveForkedTransactionTimerTask extends SIPStackTimerTask {
          
          private SIPClientTransaction clientTransaction;
  
          public RemoveForkedTransactionTimerTask(SIPClientTransaction sipClientTransaction ) {
              this.clientTransaction = sipClientTransaction;
          }
  
          @Override
          protected void runTask() {
             forkedClientTransactionTable.remove(clientTransaction.getTransactionId()); 
          }
          
      }
  
      static {
      	// Standard set of methods that create dialogs.
      	dialogCreatingMethods.add(Request.REFER);
          dialogCreatingMethods.add(Request.INVITE);
          dialogCreatingMethods.add(Request.SUBSCRIBE);
      }
      
      /**
       * Default constructor.
       */
      protected SIPTransactionStack() {
          this.toExit = false;
          this.forkedEvents = new HashSet<String>();
          // set of events for which subscriptions can be forked.
          // Set an infinite thread pool size.
          this.threadPoolSize = -1;
          // Close response socket after infinte time.
          // for max performance
          this.cacheServerConnections = true;
          // Close the request socket after infinite time.
          // for max performance
          this.cacheClientConnections = true;
          // Max number of simultaneous connections.
          this.maxConnections = -1;
          // Array of message processors.
          messageProcessors = new ArrayList<MessageProcessor>();
          // Handle IO for this process.
          this.ioHandler = new IOHandler(this);
  
          // The read time out is infinite.
          this.readTimeout = -1;
  
          this.maxListenerResponseTime = -1;
  
          // The default (identity) address lookup scheme
  
          this.addressResolver = new DefaultAddressResolver();
  
          // Notify may or may not create a dialog. This is handled in
          // the code.
          // Create the transaction collections
  
          // Dialog dable.
          this.dialogTable = new ConcurrentHashMap<String, SIPDialog>();
          this.earlyDialogTable = new ConcurrentHashMap<String, SIPDialog>();
  
          clientTransactionTable = new ConcurrentHashMap<String, SIPClientTransaction>();
          serverTransactionTable = new ConcurrentHashMap<String, SIPServerTransaction>();
          this.terminatedServerTransactionsPendingAck = new ConcurrentHashMap<String, SIPServerTransaction>();
          mergeTable = new ConcurrentHashMap<String, SIPServerTransaction>();
          retransmissionAlertTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
  
          // Start the timer event thread.
  
          this.timer = new Timer();
          this.pendingTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
          
          
          this.forkedClientTransactionTable = new ConcurrentHashMap<String,SIPClientTransaction>();
  
          if (getThreadAuditor().isEnabled()) {
              // Start monitoring the timer thread
              timer.schedule(new PingTimer(null), 0);
          }
      }
  
      /**
       * Re Initialize the stack instance.
       */
      protected void reInit() {
          if (isLoggingEnabled())
              stackLogger.logDebug("Re-initializing !");
  
          // Array of message processors.
          messageProcessors = new ArrayList<MessageProcessor>();
          // Handle IO for this process.
          this.ioHandler = new IOHandler(this);
          // clientTransactions = new ConcurrentLinkedQueue();
          // serverTransactions = new ConcurrentLinkedQueue();
          pendingTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
          clientTransactionTable = new ConcurrentHashMap<String, SIPClientTransaction>();
          serverTransactionTable = new ConcurrentHashMap<String, SIPServerTransaction>();
          retransmissionAlertTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
          mergeTable = new ConcurrentHashMap<String, SIPServerTransaction>();
          // Dialog dable.
          this.dialogTable = new ConcurrentHashMap<String, SIPDialog>();
          this.earlyDialogTable = new ConcurrentHashMap<String, SIPDialog>();
          this.terminatedServerTransactionsPendingAck = new ConcurrentHashMap<String,SIPServerTransaction>();
          this.forkedClientTransactionTable = new ConcurrentHashMap<String,SIPClientTransaction>();
  
          this.timer = new Timer();
  
          this.activeClientTransactionCount = new AtomicInteger(0);
  
      }
  
      /**
       * Creates and binds, if necessary, a socket connected to the specified
       * destination address and port and then returns its local address.
       *
       * @param dst the destination address that the socket would need to connect
       *            to.
       * @param dstPort the port number that the connection would be established
       * with.
       * @param localAddress the address that we would like to bind on
       * (null for the "any" address).
       * @param localPort the port that we'd like our socket to bind to (0 for a
       * random port).
       *
       * @return the SocketAddress that this handler would use when connecting to
       * the specified destination address and port.
       *
       * @throws IOException
       */
      public SocketAddress obtainLocalAddress(InetAddress dst, int dstPort,
                      InetAddress localAddress, int localPort)
          throws IOException
      {
          return this.ioHandler.obtainLocalAddress(
                          dst, dstPort, localAddress, localPort);
  
      }
  
      /**
       * For debugging -- allows you to disable logging or enable logging selectively.
       *
       *
       */
      public void disableLogging() {
          getStackLogger().disableLogging();
      }
  
      /**
       * Globally enable message logging ( for debugging)
       *
       */
      public void enableLogging() {
          getStackLogger().enableLogging();
      }
  
      /**
       * Print the dialog table.
       *
       */
      public void printDialogTable() {
          if (isLoggingEnabled()) {
              getStackLogger().logDebug("dialog table  = " + this.dialogTable);
              System.out.println("dialog table = " + this.dialogTable);
          }
      }
  
      /**
       * Retrieve a transaction from our table of transactions with pending retransmission alerts.
       *
       * @param dialogId
       * @return -- the RetransmissionAlert enabled transaction corresponding to the given dialog
       *         ID.
       */
      public SIPServerTransaction getRetransmissionAlertTransaction(String dialogId) {
          return (SIPServerTransaction) this.retransmissionAlertTransactions.get(dialogId);
      }
  
      /**
       * Return true if extension is supported.
       *
       * @return true if extension is supported and false otherwise.
       */
      public static boolean isDialogCreated(String method) {
      	return dialogCreatingMethods.contains(method);
      }
  
      /**
       * Add an extension method.
       *
       * @param extensionMethod -- extension method to support for dialog creation
       */
      public void addExtensionMethod(String extensionMethod) {
          if (extensionMethod.equals(Request.NOTIFY)) {
              if (isLoggingEnabled())
                  stackLogger.logDebug("NOTIFY Supported Natively");
          } else {
              dialogCreatingMethods.add(extensionMethod.trim().toUpperCase());
          }
      }
  
      /**
       * Put a dialog into the dialog table.
       *
       * @param dialog -- dialog to put into the dialog table.
       *
       */
      public void putDialog(SIPDialog dialog) {
          String dialogId = dialog.getDialogId();
          if (dialogTable.containsKey(dialogId)) {
              if (isLoggingEnabled()) {
                  stackLogger.logDebug("putDialog: dialog already exists" + dialogId + " in table = "
                          + dialogTable.get(dialogId));
              }
              return;
          }
          if (isLoggingEnabled()) {
              stackLogger.logDebug("putDialog dialogId=" + dialogId + " dialog = " + dialog);
          }
          dialog.setStack(this);
          if (isLoggingEnabled())
              stackLogger.logStackTrace();
          dialogTable.put(dialogId, dialog);
  
      }
  
      /**
       * Create a dialog and add this transaction to it.
       *
       * @param transaction -- tx to add to the dialog.
       * @return the newly created Dialog.
       */
      public SIPDialog createDialog(SIPTransaction transaction) {
  
          SIPDialog retval = null;
  
          if (transaction instanceof SIPClientTransaction) {
              String dialogId = ((SIPRequest) transaction.getRequest()).getDialogId(false);
              if (this.earlyDialogTable.get(dialogId) != null) {
                  SIPDialog dialog = this.earlyDialogTable.get(dialogId);
                  if (dialog.getState() == null || dialog.getState() == DialogState.EARLY) {
                      retval = dialog;
                  } else {
                      retval = new SIPDialog(transaction);
                      this.earlyDialogTable.put(dialogId, retval);
                  }
              } else {
                  retval = new SIPDialog(transaction);
                  this.earlyDialogTable.put(dialogId, retval);
              }
          } else {
              retval = new SIPDialog(transaction);
          }
  
          return retval;
  
      }
  
      /**
       * Create a Dialog given a client tx and response.
       *
       * @param transaction
       * @param sipResponse
       * @return
       */
  
      public SIPDialog createDialog(SIPClientTransaction transaction, SIPResponse sipResponse) {
          String dialogId = ((SIPRequest) transaction.getRequest()).getDialogId(false);
          SIPDialog retval = null;
          if (this.earlyDialogTable.get(dialogId) != null) {
              retval = this.earlyDialogTable.get(dialogId);
              if (sipResponse.isFinalResponse()) {
                  this.earlyDialogTable.remove(dialogId);
              }
  
          } else {
              retval = new SIPDialog(transaction, sipResponse);
          }
          return retval;
  
      }
      /**
       * Create a Dialog given a sip provider and response.
       *
       * @param sipProvider
       * @param sipResponse
       * @return
       */
      public SIPDialog createDialog(SipProviderImpl sipProvider,
  			SIPResponse sipResponse) {
  		return new SIPDialog(sipProvider, sipResponse);
  	}
  
      /**
       * Remove the dialog from the dialog table.
       *
       * @param dialog -- dialog to remove.
       */
      public void removeDialog(SIPDialog dialog) {
  
          String id = dialog.getDialogId();
  
          String earlyId = dialog.getEarlyDialogId();
  
          if (earlyId != null) {
              this.earlyDialogTable.remove(earlyId);
              this.dialogTable.remove(earlyId);
          }
  
          if (id != null) {
  
              // FHT: Remove dialog from table only if its associated dialog is the same as the one
              // specified
  
              Object old = this.dialogTable.get(id);
  
              if (old == dialog) {
                  this.dialogTable.remove(id);
              }
         
              // We now deliver DTE even when the dialog is not originally present in the Dialog
              // Table
              // This happens before the dialog state is assigned.
  
              if (!dialog.testAndSetIsDialogTerminatedEventDelivered()) {
                  DialogTerminatedEvent event = new DialogTerminatedEvent(dialog.getSipProvider(),
                          dialog);
  
                  // Provide notification to the listener that the dialog has
                  // ended.
                  dialog.getSipProvider().handleEvent(event, null);
  
              }
  
          } else if ( this.isDialogTerminatedEventDeliveredForNullDialog ) {
              if (!dialog.testAndSetIsDialogTerminatedEventDelivered()) {
                  DialogTerminatedEvent event = new DialogTerminatedEvent(dialog.getSipProvider(),
                          dialog);
  
                  // Provide notification to the listener that the dialog has
                  // ended.
                  dialog.getSipProvider().handleEvent(event, null);
  
              }
          }
  
      }
  
      /**
       * Return the dialog for a given dialog ID. If compatibility is enabled then we do not assume
       * the presence of tags and hence need to add a flag to indicate whether this is a server or
       * client transaction.
       *
       * @param dialogId is the dialog id to check.
       */
  
      public SIPDialog getDialog(String dialogId) {
  
          SIPDialog sipDialog = (SIPDialog) dialogTable.get(dialogId);
          if (isLoggingEnabled()) {
              stackLogger.logDebug("getDialog(" + dialogId + ") : returning " + sipDialog);
          }
          return sipDialog;
  
      }
  
      /**
       * Remove the dialog given its dialog id. This is used for dialog id re-assignment only.
       *
       * @param dialogId is the dialog Id to remove.
       */
      public void removeDialog(String dialogId) {
          if (isLoggingEnabled()) {
              stackLogger.logWarning("Silently removing dialog from table");
          }
          dialogTable.remove(dialogId);
      }
  
      /**
       * Find a matching client SUBSCRIBE to the incoming notify. NOTIFY requests are matched to
       * such SUBSCRIBE requests if they contain the same "Call-ID", a "To" header "tag" parameter
       * which matches the "From" header "tag" parameter of the SUBSCRIBE, and the same "Event"
       * header field. Rules for comparisons of the "Event" headers are described in section 7.2.1.
       * If a matching NOTIFY request contains a "Subscription-State" of "active" or "pending", it
       * creates a new subscription and a new dialog (unless they have already been created by a
       * matching response, as described above).
       *
       * @param notifyMessage
       * @return -- the matching ClientTransaction with semaphore aquired or null if no such client
       *         transaction can be found.
       */
      public SIPClientTransaction findSubscribeTransaction(SIPRequest notifyMessage,
              ListeningPointImpl listeningPoint) {
          SIPClientTransaction retval = null;
          try {
              Iterator it = clientTransactionTable.values().iterator();
              if (isLoggingEnabled())
              	stackLogger.logDebug("ct table size = " + clientTransactionTable.size());
              String thisToTag = notifyMessage.getTo().getTag();
              if (thisToTag == null) {
                  return retval;
              }
              Event eventHdr = (Event) notifyMessage.getHeader(EventHeader.NAME);
              if (eventHdr == null) {
                  if (isLoggingEnabled()) {
                      stackLogger.logDebug("event Header is null -- returning null");
                  }
  
                  return retval;
              }
              while (it.hasNext()) {
                  SIPClientTransaction ct = (SIPClientTransaction) it.next();
                  if (!ct.getMethod().equals(Request.SUBSCRIBE))
                      continue;
  
                  // if ( sipProvider.getListeningPoint(transport) == null)
                  String fromTag = ct.from.getTag();
                  Event hisEvent = ct.event;
                  // Event header is mandatory but some slopply clients
                  // dont include it.
                  if (hisEvent == null)
                      continue;
                  if (isLoggingEnabled()) {
                      stackLogger.logDebug("ct.fromTag = " + fromTag);
                      stackLogger.logDebug("thisToTag = " + thisToTag);
                      stackLogger.logDebug("hisEvent = " + hisEvent);
                      stackLogger.logDebug("eventHdr " + eventHdr);
                  }
  
                  if (  fromTag.equalsIgnoreCase(thisToTag)
                        && hisEvent != null
                        && eventHdr.match(hisEvent)
                        && notifyMessage.getCallId().getCallId().equalsIgnoreCase(
                                  ct.callId.getCallId())) {
                      if (ct.acquireSem())
                          retval = ct;
                      return retval;
                  }
              }
  
              return retval;
          } finally {
          	if (isLoggingEnabled())
                  stackLogger.logDebug("findSubscribeTransaction : returning " + retval);
  
          }
  
      }
      
      /**
       * Add entry to "Transaction Pending ACK" table.
       * 
       * @param serverTransaction
       */
      public void addTransactionPendingAck(SIPServerTransaction serverTransaction) {
          String branchId = ((SIPRequest)serverTransaction.getRequest()).getTopmostVia().getBranch();
          if ( branchId != null ) {
              this.terminatedServerTransactionsPendingAck.put(branchId, serverTransaction);
          }
          
      }
      
      /**
       * Get entry in the server transaction pending ACK table corresponding to an ACK.
       * 
       * @param ackMessage
       * @return
       */
      public SIPServerTransaction findTransactionPendingAck(SIPRequest ackMessage) {
          return this.terminatedServerTransactionsPendingAck.get(ackMessage.getTopmostVia().getBranch());
      }
      
      /**
       * Remove entry from "Transaction Pending ACK" table.
       * 
       * @param serverTransaction
       * @return
       */
      
      public boolean removeTransactionPendingAck(SIPServerTransaction serverTransaction) {
          String branchId = ((SIPRequest)serverTransaction.getRequest()).getTopmostVia().getBranch();
          if ( branchId != null && this.terminatedServerTransactionsPendingAck.containsKey(branchId) ) {
              this.terminatedServerTransactionsPendingAck.remove(branchId);
              return true;
          } else {
              return false;
          }
      }
      
      /**
       * Check if this entry exists in the "Transaction Pending ACK" table.
       * 
       * @param serverTransaction
       * @return
       */
      public boolean isTransactionPendingAck(SIPServerTransaction serverTransaction) {
          String branchId = ((SIPRequest)serverTransaction.getRequest()).getTopmostVia().getBranch();
          return this.terminatedServerTransactionsPendingAck.contains(branchId); 
      }
      
      /**
       * Find the transaction corresponding to a given request.
       *
       * @param sipMessage request for which to retrieve the transaction.
       *
       * @param isServer search the server transaction table if true.
       *
       * @return the transaction object corresponding to the request or null if no such mapping
       *         exists.
       */
      public SIPTransaction findTransaction(SIPMessage sipMessage, boolean isServer) {
          SIPTransaction retval = null;
          try {
              if (isServer) {
                  Via via = sipMessage.getTopmostVia();
                  if (via.getBranch() != null) {
                      String key = sipMessage.getTransactionId();
  
                      retval = (SIPTransaction) serverTransactionTable.get(key);
                      // Changed by Deutsche Telekom
                      if (isLoggingEnabled())
                          getStackLogger().logDebug(
                                  "serverTx: looking for key " + key + " existing="
                                  + serverTransactionTable);
                      if (key.startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
                          return retval;
                      }
  
                  }
                  // Need to scan the table for old style transactions (RFC 2543
                  // style)
                  Iterator<SIPServerTransaction> it = serverTransactionTable.values().iterator();
                  while (it.hasNext()) {
                      SIPServerTransaction sipServerTransaction = (SIPServerTransaction) it.next();
                      if (sipServerTransaction.isMessagePartOfTransaction(sipMessage)) {
                          retval = sipServerTransaction;
                          return retval;
                      }
                  }
  
              } else {
                  Via via = sipMessage.getTopmostVia();
                  if (via.getBranch() != null) {
                      String key = sipMessage.getTransactionId();
                      // Changed by Deutsche Telekom
                      if (isLoggingEnabled())
                          getStackLogger().logDebug("clientTx: looking for key " + key);
                      retval = (SIPTransaction) clientTransactionTable.get(key);
                      if (key.startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
                          return retval;
                      }
  
                  }
                  // Need to scan the table for old style transactions (RFC 2543
                  // style). This is terribly slow but we need to do this
                  // for backasswords compatibility.
                  Iterator<SIPClientTransaction> it = clientTransactionTable.values().iterator();
                  while (it.hasNext()) {
                      SIPClientTransaction clientTransaction = (SIPClientTransaction) it.next();
                      if (clientTransaction.isMessagePartOfTransaction(sipMessage)) {
                          retval = clientTransaction;
                          return retval;
                      }
                  }
  
              }
          } finally {
          	// Changed by Deutsche Telekom
          	if (isLoggingEnabled()) {
          	  getStackLogger().logDebug("findTransaction: returning  : " + retval);
          	}
          }
          return retval;
  
      }
  
      /**
       * Get the transaction to cancel. Search the server transaction table for a transaction that
       * matches the given transaction.
       */
      public SIPTransaction findCancelTransaction(SIPRequest cancelRequest, boolean isServer) {
  
          if (isLoggingEnabled()) {
              stackLogger.logDebug("findCancelTransaction request= \n" + cancelRequest
                      + "\nfindCancelRequest isServer=" + isServer);
          }
  
          if (isServer) {
              Iterator<SIPServerTransaction> li = this.serverTransactionTable.values().iterator();
              while (li.hasNext()) {
                  SIPTransaction transaction = (SIPTransaction) li.next();
  
                  SIPServerTransaction sipServerTransaction = (SIPServerTransaction) transaction;
                  if (sipServerTransaction.doesCancelMatchTransaction(cancelRequest))
                      return sipServerTransaction;
              }
  
          } else {
              Iterator<SIPClientTransaction> li = this.clientTransactionTable.values().iterator();
              while (li.hasNext()) {
                  SIPTransaction transaction = (SIPTransaction) li.next();
  
                  SIPClientTransaction sipClientTransaction = (SIPClientTransaction) transaction;
                  if (sipClientTransaction.doesCancelMatchTransaction(cancelRequest))
                      return sipClientTransaction;
  
              }
  
          }
          if (isLoggingEnabled())
              stackLogger.logDebug("Could not find transaction for cancel request");
          return null;
      }
  
      /**
       * Construcor for the stack. Registers the request and response factories for the stack.
       *
       * @param messageFactory User-implemented factory for processing messages.
       */
      protected SIPTransactionStack(StackMessageFactory messageFactory) {
          this();
          this.sipMessageFactory = messageFactory;
      }
  
      /**
       * Finds a pending server transaction. Since each request may be handled either statefully or
       * statelessly, we keep a map of pending transactions so that a duplicate transaction is not
       * created if a second request is recieved while the first one is being processed.
       *
       * @param requestReceived
       * @return -- the pending transaction or null if no such transaction exists.
       */
      public SIPServerTransaction findPendingTransaction(SIPRequest requestReceived) {
          if (isLoggingEnabled()) {
              stackLogger.logDebug("looking for pending tx for :"
                      + requestReceived.getTransactionId());
          }
          return (SIPServerTransaction) pendingTransactions.get(requestReceived.getTransactionId());
  
      }
  
      /**
       * See if there is a pending transaction with the same Merge ID as the Merge ID obtained from
       * the SIP Request. The Merge table is for handling the following condition: If the request
       * has no tag in the To header field, the UAS core MUST check the request against ongoing
       * transactions. If the From tag, Call-ID, and CSeq exactly match those associated with an
       * ongoing transaction, but the request does not match that transaction (based on the matching
       * rules in Section 17.2.3), the UAS core SHOULD generate a 482 (Loop Detected) response and
       * pass it to the server transaction.
       */
      public SIPServerTransaction findMergedTransaction(SIPRequest sipRequest) {
          if (! sipRequest.getMethod().equals(Request.INVITE)) {
              /*
               * Dont need to worry about request merging for Non-INVITE transactions.
               */
              return null;
          }
          String mergeId = sipRequest.getMergeId();
          SIPServerTransaction mergedTransaction = (SIPServerTransaction) this.mergeTable.get(mergeId);
          if (mergeId == null ) {
              return null;
          } else if (mergedTransaction != null && !mergedTransaction.isMessagePartOfTransaction(sipRequest) ) {
              return mergedTransaction;
          } else {
              /*
               * Check the server transactions that have resulted in dialogs.
               */
             for (Dialog dialog: this.dialogTable.values() ) {
                 SIPDialog sipDialog = (SIPDialog) dialog ;
                 if (sipDialog.getFirstTransaction()  != null && 
                     sipDialog.getFirstTransaction() instanceof ServerTransaction) {
                     SIPServerTransaction serverTransaction = ((SIPServerTransaction) sipDialog.getFirstTransaction());
                     SIPRequest transactionRequest = ((SIPServerTransaction) sipDialog.getFirstTransaction()).getOriginalRequest();
                     if ( (! serverTransaction.isMessagePartOfTransaction(sipRequest))
                             && sipRequest.getMergeId().equals(transactionRequest.getMergeId())) {
                             return (SIPServerTransaction) sipDialog.getFirstTransaction();  
                     }
                 }
             } 
             return null;
          }
      }
  
      /**
       * Remove a pending Server transaction from the stack. This is called after the user code has
       * completed execution in the listener.
       *
       * @param tr -- pending transaction to remove.
       */
      public void removePendingTransaction(SIPServerTransaction tr) {
          if (isLoggingEnabled()) {
              stackLogger.logDebug("removePendingTx: " + tr.getTransactionId());
          }
          this.pendingTransactions.remove(tr.getTransactionId());
  
      }
  
      /**
       * Remove a transaction from the merge table.
       *
       * @param tr -- the server transaction to remove from the merge table.
       *
       */
      public void removeFromMergeTable(SIPServerTransaction tr) {
          if (isLoggingEnabled()) {
              stackLogger.logDebug("Removing tx from merge table ");
          }
          String key = ((SIPRequest) tr.getRequest()).getMergeId();
          if (key != null) {
              this.mergeTable.remove(key);
          }
      }
  
      /**
       * Put this into the merge request table.
       *
       * @param sipTransaction -- transaction to put into the merge table.
       *
       */
      public void putInMergeTable(SIPServerTransaction sipTransaction, SIPRequest sipRequest) {
          String mergeKey = sipRequest.getMergeId();
          if (mergeKey != null) {
              this.mergeTable.put(mergeKey, sipTransaction);
          }
      }
  
      /**
       * Map a Server transaction (possibly sending out a 100 if the server tx is an INVITE). This
       * actually places it in the hash table and makes it known to the stack.
       *
       * @param transaction -- the server transaction to map.
       */
      public void mapTransaction(SIPServerTransaction transaction) {
          if (transaction.isMapped)
              return;
          addTransactionHash(transaction);
          // transaction.startTransactionTimer();
          transaction.isMapped = true;
      }
  
      /**
       * Handles a new SIP request. It finds a server transaction to handle this message. If none
       * exists, it creates a new transaction.
       *
       * @param requestReceived Request to handle.
       * @param requestMessageChannel Channel that received message.
       *
       * @return A server transaction.
       */
      public ServerRequestInterface newSIPServerRequest(SIPRequest requestReceived,
              MessageChannel requestMessageChannel) {
          // Iterator through all server transactions
          Iterator<SIPServerTransaction> transactionIterator;
          // Next transaction in the set
          SIPServerTransaction nextTransaction;
          // Transaction to handle this request
          SIPServerTransaction currentTransaction;
  
          String key = requestReceived.getTransactionId();
  
          requestReceived.setMessageChannel(requestMessageChannel);
  
          currentTransaction = (SIPServerTransaction) serverTransactionTable.get(key);
  
          // Got to do this for bacasswards compatibility.
          if (currentTransaction == null
                  || !currentTransaction.isMessagePartOfTransaction(requestReceived)) {
  
              // Loop through all server transactions
              transactionIterator = serverTransactionTable.values().iterator();
              currentTransaction = null;
              if (!key.toLowerCase().startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
                  while (transactionIterator.hasNext() && currentTransaction == null) {
  
                      nextTransaction = (SIPServerTransaction) transactionIterator.next();
  
                      // If this transaction should handle this request,
                      if (nextTransaction.isMessagePartOfTransaction(requestReceived)) {
                          // Mark this transaction as the one
                          // to handle this message
                          currentTransaction = nextTransaction;
                      }
                  }
              }
  
              // If no transaction exists to handle this message
              if (currentTransaction == null) {
                  currentTransaction = findPendingTransaction(requestReceived);
                  if (currentTransaction != null) {
                      // Associate the tx with the received request.
                      requestReceived.setTransaction(currentTransaction);
                      if (currentTransaction != null && currentTransaction.acquireSem())
                          return currentTransaction;
                      else
                          return null;
  
                  }
                  // Creating a new server tx. May fail under heavy load.
                  currentTransaction = createServerTransaction(requestMessageChannel);
                  if (currentTransaction != null) {
                      // currentTransaction.setPassToListener();
                      currentTransaction.setOriginalRequest(requestReceived);
                      // Associate the tx with the received request.
                      requestReceived.setTransaction(currentTransaction);
                  }
  
              }
  
          }
  
          // Set ths transaction's encapsulated request
          // interface from the superclass
          if (isLoggingEnabled()) {
              stackLogger.logDebug("newSIPServerRequest( " + requestReceived.getMethod() + ":"
                      + requestReceived.getTopmostVia().getBranch() + "):" + currentTransaction);
          }
  
          if (currentTransaction != null)
              currentTransaction.setRequestInterface(sipMessageFactory.newSIPServerRequest(
                      requestReceived, currentTransaction));
  
          if (currentTransaction != null && currentTransaction.acquireSem()) {
              return currentTransaction;
          } else if (currentTransaction != null) {
              try {
                  /*
                   * Already processing a message for this transaction.
                   * SEND a trying ( message already being processed ).
                   */
                  if (currentTransaction.isMessagePartOfTransaction(requestReceived) &&
                      currentTransaction.getMethod().equals(requestReceived.getMethod())) {
                      SIPResponse trying = requestReceived.createResponse(Response.TRYING);
                      trying.removeContent();
                      currentTransaction.getMessageChannel().sendMessage(trying);
                  }
              } catch (Exception ex) {
              	if (isLoggingEnabled())
              		stackLogger.logError("Exception occured sending TRYING");
              }
              return null;
          } else {
              return null;
          }
      }
  
      /**
       * Handles a new SIP response. It finds a client transaction to handle this message. If none
       * exists, it sends the message directly to the superclass.
       *
       * @param responseReceived Response to handle.
       * @param responseMessageChannel Channel that received message.
       *
       * @return A client transaction.
       */
      public ServerResponseInterface newSIPServerResponse(SIPResponse responseReceived,
              MessageChannel responseMessageChannel) {
  
          // Iterator through all client transactions
          Iterator<SIPClientTransaction> transactionIterator;
          // Next transaction in the set
          SIPClientTransaction nextTransaction;
          // Transaction to handle this request
          SIPClientTransaction currentTransaction;
  
          String key = responseReceived.getTransactionId();
  
          // Note that for RFC 3261 compliant operation, this lookup will
          // return a tx if one exists and hence no need to search through
          // the table.
          currentTransaction = (SIPClientTransaction) clientTransactionTable.get(key);
  
          if (currentTransaction == null
                  || (!currentTransaction.isMessagePartOfTransaction(responseReceived) && !key
                          .startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE))) {
              // Loop through all client transactions
  
              transactionIterator = clientTransactionTable.values().iterator();
              currentTransaction = null;
              while (transactionIterator.hasNext() && currentTransaction == null) {
  
                  nextTransaction = (SIPClientTransaction) transactionIterator.next();
  
                  // If this transaction should handle this request,
                  if (nextTransaction.isMessagePartOfTransaction(responseReceived)) {
  
                      // Mark this transaction as the one to
                      // handle this message
                      currentTransaction = nextTransaction;
  
                  }
  
              }
  
              // If no transaction exists to handle this message,
              if (currentTransaction == null) {
                  // JvB: Need to log before passing the response to the client
                  // app, it
                  // gets modified!
                  if (isLoggingEnabled(StackLogger.TRACE_INFO)) {
                      responseMessageChannel.logResponse(responseReceived, System
                              .currentTimeMillis(), "before processing");
                  }
  
                  // Pass the message directly to the TU
                  return sipMessageFactory.newSIPServerResponse(responseReceived,
                          responseMessageChannel);
  
              }
          }
  
          // Aquire the sem -- previous request may still be processing.
          boolean acquired = currentTransaction.acquireSem();
          // Set ths transaction's encapsulated response interface
          // from the superclass
          if (isLoggingEnabled(StackLogger.TRACE_INFO)) {
              currentTransaction.logResponse(responseReceived, System.currentTimeMillis(),
                      "before processing");
          }
  
          if (acquired) {
              ServerResponseInterface sri = sipMessageFactory.newSIPServerResponse(
                      responseReceived, currentTransaction);
              if (sri != null) {
                  currentTransaction.setResponseInterface(sri);
              } else {
                  if (isLoggingEnabled()) {
                      stackLogger.logDebug("returning null - serverResponseInterface is null!");
                  }
                  currentTransaction.releaseSem();
                  return null;
              }
          } else {
          	if (isLoggingEnabled())
          		stackLogger.logDebug("Could not aquire semaphore !!");
          }
  
          if (acquired)
              return currentTransaction;
          else
              return null;
  
      }
  
      /**
       * Creates a client transaction to handle a new request. Gets the real message channel from
       * the superclass, and then creates a new client transaction wrapped around this channel.
       *
       * @param nextHop Hop to create a channel to contact.
       */
      public MessageChannel createMessageChannel(SIPRequest request, MessageProcessor mp,
              Hop nextHop) throws IOException {
          // New client transaction to return
          SIPTransaction returnChannel;
  
          // Create a new client transaction around the
          // superclass' message channel
          // Create the host/port of the target hop
          Host targetHost = new Host();
          targetHost.setHostname(nextHop.getHost());
          HostPort targetHostPort = new HostPort();
          targetHostPort.setHost(targetHost);
          targetHostPort.setPort(nextHop.getPort());
          MessageChannel mc = mp.createMessageChannel(targetHostPort);
  
          // Superclass will return null if no message processor
          // available for the transport.
          if (mc == null)
              return null;
  
          returnChannel = createClientTransaction(request, mc);
  
          ((SIPClientTransaction) returnChannel).setViaPort(nextHop.getPort());
          ((SIPClientTransaction) returnChannel).setViaHost(nextHop.getHost());
          addTransactionHash(returnChannel);
          // clientTransactionTable.put(returnChannel.getTransactionId(),
          // returnChannel);
          // Add the transaction timer for the state machine.
          // returnChannel.startTransactionTimer();
          return returnChannel;
  
      }
  
      /**
       * Creates a client transaction that encapsulates a MessageChannel. Useful for implementations
       * that want to subclass the standard
       *
       * @param encapsulatedMessageChannel Message channel of the transport layer.
       */
      public SIPClientTransaction createClientTransaction(SIPRequest sipRequest,
              MessageChannel encapsulatedMessageChannel) {
          SIPClientTransaction ct = new SIPClientTransaction(this, encapsulatedMessageChannel);
          ct.setOriginalRequest(sipRequest);
          return ct;
      }
  
      /**
       * Creates a server transaction that encapsulates a MessageChannel. Useful for implementations
       * that want to subclass the standard
       *
       * @param encapsulatedMessageChannel Message channel of the transport layer.
       */
      public SIPServerTransaction createServerTransaction(MessageChannel encapsulatedMessageChannel) {
      	// Issue 256 : be consistent with createClientTransaction, if unlimitedServerTransactionTableSize is true,
      	// a new Server Transaction is created no matter what
          if (unlimitedServerTransactionTableSize) {                
              return new SIPServerTransaction(this, encapsulatedMessageChannel);
          } else {
              float threshold = ((float) (serverTransactionTable.size() - serverTransactionTableLowaterMark))
                      / ((float) (serverTransactionTableHighwaterMark - serverTransactionTableLowaterMark));
              boolean decision = Math.random() > 1.0 - threshold;
              if (decision) {
                  return null;
              } else {
                  return new SIPServerTransaction(this, encapsulatedMessageChannel);
              }
  
          }
  
      }
  
      /**
       * Get the size of the client transaction table.
       *
       * @return -- size of the ct table.
       */
      public int getClientTransactionTableSize() {
          return this.clientTransactionTable.size();
      }
      
      /**
       * Get the size of the server transaction table.
       *
       * @return -- size of the server table.
       */
      public int getServerTransactionTableSize() {
          return this.serverTransactionTable.size();
      }
  
      /**
       * Add a new client transaction to the set of existing transactions. Add it to the top of the
       * list so an incoming response has less work to do in order to find the transaction.
       *
       * @param clientTransaction -- client transaction to add to the set.
       */
      public void addTransaction(SIPClientTransaction clientTransaction) {
          if (isLoggingEnabled())
              stackLogger.logDebug("added transaction " + clientTransaction);
          addTransactionHash(clientTransaction);
         
      }
  
      /**
       * Remove transaction. This actually gets the tx out of the search structures which the stack
       * keeps around. When the tx
       */
      public void removeTransaction(SIPTransaction sipTransaction) {
          if (isLoggingEnabled()) {
              stackLogger.logDebug("Removing Transaction = " + sipTransaction.getTransactionId()
                      + " transaction = " + sipTransaction);
          }
          if (sipTransaction instanceof SIPServerTransaction) {
              if (isLoggingEnabled())
                  stackLogger.logStackTrace();
              String key = sipTransaction.getTransactionId();
              Object removed = serverTransactionTable.remove(key);
              String method = sipTransaction.getMethod();
              this.removePendingTransaction((SIPServerTransaction) sipTransaction);
              this.removeTransactionPendingAck((SIPServerTransaction) sipTransaction);
              if (method.equalsIgnoreCase(Request.INVITE)) {
                  this.removeFromMergeTable((SIPServerTransaction) sipTransaction);
              }
              // Send a notification to the listener.
              SipProviderImpl sipProvider = (SipProviderImpl) sipTransaction.getSipProvider();
              if (removed != null && sipTransaction.testAndSetTransactionTerminatedEvent()) {
                  TransactionTerminatedEvent event = new TransactionTerminatedEvent(sipProvider,
                          (ServerTransaction) sipTransaction);
  
                  sipProvider.handleEvent(event, sipTransaction);
  
              }
          } else {
  
              String key = sipTransaction.getTransactionId();
              Object removed = clientTransactionTable.remove(key);
  
              if (isLoggingEnabled()) {
                  stackLogger.logDebug("REMOVED client tx " + removed + " KEY = " + key);
                  if ( removed != null ) {
                     SIPClientTransaction clientTx = (SIPClientTransaction)removed;
                     if ( clientTx.getMethod().equals(Request.INVITE) && this.maxForkTime != 0 ) {
                         RemoveForkedTransactionTimerTask ttask = new RemoveForkedTransactionTimerTask(clientTx);
                         this.timer.schedule(ttask, this.maxForkTime * 1000);
                     }
                  }
              }
  
              // Send a notification to the listener.
              if (removed != null && sipTransaction.testAndSetTransactionTerminatedEvent()) {
                  SipProviderImpl sipProvider = (SipProviderImpl) sipTransaction.getSipProvider();
                  TransactionTerminatedEvent event = new TransactionTerminatedEvent(sipProvider,
                          (ClientTransaction) sipTransaction);
  
                  sipProvider.handleEvent(event, sipTransaction);
              }
  
          }
      }
  
      /**
       * Add a new server transaction to the set of existing transactions. Add it to the top of the
       * list so an incoming ack has less work to do in order to find the transaction.
       *
       * @param serverTransaction -- server transaction to add to the set.
       */
      public void addTransaction(SIPServerTransaction serverTransaction) throws IOException {
          if (isLoggingEnabled())
              stackLogger.logDebug("added transaction " + serverTransaction);
          serverTransaction.map();
  
          addTransactionHash(serverTransaction);
        
      }
  
      /**
       * Hash table for quick lookup of transactions. Here we wait for room if needed.
       */
      private void addTransactionHash(SIPTransaction sipTransaction) {
          SIPRequest sipRequest = sipTransaction.getOriginalRequest();
          if (sipTransaction instanceof SIPClientTransaction) {
              if (!this.unlimitedClientTransactionTableSize) {
                  if (this.activeClientTransactionCount.get() > clientTransactionTableHiwaterMark) {
                      try {
                          synchronized (this.clientTransactionTable) {
                              this.clientTransactionTable.wait();
                              this.activeClientTransactionCount.incrementAndGet();
                          }
  
                      } catch (Exception ex) {
                          if (isLoggingEnabled()) {
                              stackLogger.logError("Exception occured while waiting for room", ex);
                          }
  
                      }
                  }
              } else {
                  this.activeClientTransactionCount.incrementAndGet();
              }
              String key = sipRequest.getTransactionId();
              clientTransactionTable.put(key, (SIPClientTransaction) sipTransaction);
              
              if (isLoggingEnabled()) {
                  stackLogger.logDebug(" putTransactionHash : " + " key = " + key);
              }
          } else {
              String key = sipRequest.getTransactionId();
  
              if (isLoggingEnabled()) {
                  stackLogger.logDebug(" putTransactionHash : " + " key = " + key);
              }
              serverTransactionTable.put(key, (SIPServerTransaction) sipTransaction);
  
          }
  
      }
  
      /**
       * This method is called when a client tx transitions to the Completed or Terminated state.
       *
       */
      protected void decrementActiveClientTransactionCount() {
  
          if (this.activeClientTransactionCount.decrementAndGet() <= this.clientTransactionTableLowaterMark
                  && !this.unlimitedClientTransactionTableSize) {
              synchronized (this.clientTransactionTable) {
  
                  clientTransactionTable.notify();
  
              }
          }
      }
  
      /**
       * Remove the transaction from transaction hash.
       */
      protected void removeTransactionHash(SIPTransaction sipTransaction) {
          SIPRequest sipRequest = sipTransaction.getOriginalRequest();
          if (sipRequest == null)
              return;
          if (sipTransaction instanceof SIPClientTransaction) {
              String key = sipTransaction.getTransactionId();
              if (isLoggingEnabled()) {
                  stackLogger.logStackTrace();
                  stackLogger.logDebug("removing client Tx : " + key);
              }
              clientTransactionTable.remove(key);
  
          } else if (sipTransaction instanceof SIPServerTransaction) {
              String key = sipTransaction.getTransactionId();
              serverTransactionTable.remove(key);
              if (isLoggingEnabled()) {
                  stackLogger.logDebug("removing server Tx : " + key);
              }
          }
      }
  
      /**
       * Invoked when an error has ocurred with a transaction.
       *
       * @param transactionErrorEvent Error event.
       */
      public synchronized void transactionErrorEvent(SIPTransactionErrorEvent transactionErrorEvent) {
          SIPTransaction transaction = (SIPTransaction) transactionErrorEvent.getSource();
  
          if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TRANSPORT_ERROR) {
              // Kill scanning of this transaction.
              transaction.setState(SIPTransaction.TERMINATED_STATE);
              if (transaction instanceof SIPServerTransaction) {
                  // let the reaper get him
                  ((SIPServerTransaction) transaction).collectionTime = 0;
              }
              transaction.disableTimeoutTimer();
              transaction.disableRetransmissionTimer();
              // Send a IO Exception to the Listener.
          }
      }
      
      /*
       * (non-Javadoc)
       * @see gov2.nist.javax2.sip.stack.SIPDialogEventListener#dialogErrorEvent(gov2.nist.javax2.sip.stack.SIPDialogErrorEvent)
       */
      public synchronized void dialogErrorEvent(SIPDialogErrorEvent dialogErrorEvent) {
          SIPDialog sipDialog = (SIPDialog) dialogErrorEvent.getSource();
          SipListener sipListener = ((SipStackImpl)this).getSipListener();
          // if the app is not implementing the SipListenerExt interface we delete the dialog to avoid leaks
          if(sipDialog != null && !(sipListener instanceof SipListenerExt)) {
          	sipDialog.delete();
          }
      }
  
      /**
       * Stop stack. Clear all the timer stuff. Make the stack close all accept connections and
       * return. This is useful if you want to start/stop the stack several times from your
       * application. Caution : use of this function could cause peculiar bugs as messages are
       * prcessed asynchronously by the stack.
       */
      public void stopStack() {
          // Prevent NPE on two concurrent stops
          if (this.timer != null)
              this.timer.cancel();
  
          // JvB: set it to null, SIPDialog tries to schedule things after stop
          timer = null;
          this.pendingTransactions.clear();
          this.toExit = true;
          synchronized (this) {
              this.notifyAll();
          }
          synchronized (this.clientTransactionTable) {
              clientTransactionTable.notifyAll();
          }
  
          synchronized (this.messageProcessors) {
              // Threads must periodically check this flag.
              MessageProcessor[] processorList;
              processorList = getMessageProcessors();
              for (int processorIndex = 0; processorIndex < processorList.length; processorIndex++) {
                  removeMessageProcessor(processorList[processorIndex]);
              }
              this.ioHandler.closeAll();
              // Let the processing complete.
  
          }
          try {
  
              Thread.sleep(1000);
  
          } catch (InterruptedException ex) {
          }
          this.clientTransactionTable.clear();
          this.serverTransactionTable.clear();
  
          this.dialogTable.clear();
          this.serverLogger.closeLogFile();
  
      }
  
      /**
       * Put a transaction in the pending transaction list. This is to avoid a race condition when a
       * duplicate may arrive when the application is deciding whether to create a transaction or
       * not.
       */
      public void putPendingTransaction(SIPServerTransaction tr) {
          if (isLoggingEnabled())
              stackLogger.logDebug("putPendingTransaction: " + tr);
  
          this.pendingTransactions.put(tr.getTransactionId(), tr);
  
      }
  
      /**
       * Return the network layer (i.e. the interface for socket creation or the socket factory for
       * the stack).
       *
       * @return -- the registered Network Layer.
       */
      public NetworkLayer getNetworkLayer() {
          if (networkLayer == null) {
              return DefaultNetworkLayer.SINGLETON;
          } else {
              return networkLayer;
          }
      }
  
      /**
       * Return true if logging is enabled for this stack.
       *
       * @return true if logging is enabled for this stack instance.
       */
      public boolean isLoggingEnabled() {
          return this.stackLogger == null ? false : this.stackLogger.isLoggingEnabled();
      }
  
      // Changed by Deutsche Telekom
      /**
       * Return true if logging is enabled for this stack for the according logLevel.
       *
       * @return true if logging is enabled for this stack instance for the according logLevel.
       */
      public boolean isLoggingEnabled(int logLevel) {
          return this.stackLogger == null ? false : this.stackLogger.isLoggingEnabled(logLevel);
      }
  
      /**
       * Get the logger.
       *
       * @return --the logger for the sip stack. Each stack has its own logger instance.
       */
      public StackLogger getStackLogger() {
          return this.stackLogger;
      }
  
      /**
       * Server log is the place where we log messages for the signaling trace viewer.
       *
       * @return -- the log file where messages are logged for viewing by the trace viewer.
       */
      public ServerLogger getServerLogger() {
          return this.serverLogger;
      }
  
      /**
       * Maximum size of a single TCP message. Limiting the size of a single TCP message prevents
       * flooding attacks.
       *
       * @return the size of a single TCP message.
       */
      public int getMaxMessageSize() {
          return this.maxMessageSize;
      }
  
      /**
       * Set the flag that instructs the stack to only start a single thread for sequentially
       * processing incoming udp messages (thus serializing the processing). Same as setting thread
       * pool size to 1.
       */
      public void setSingleThreaded() {
          this.threadPoolSize = 1;
      }
  
      /**
       * Set the thread pool size for processing incoming UDP messages. Limit the total number of
       * threads for processing udp messages.
       *
       * @param size -- the thread pool size.
       *
       */
      public void setThreadPoolSize(int size) {
          this.threadPoolSize = size;
      }
  
      /**
       * Set the max # of simultaneously handled TCP connections.
       *
       * @param nconnections -- the number of connections to handle.
       */
      public void setMaxConnections(int nconnections) {
          this.maxConnections = nconnections;
      }
  
      /**
       * Get the default route string.
       *
       * @param sipRequest is the request for which we want to compute the next hop.
       * @throws SipException
       */
      public Hop getNextHop(SIPRequest sipRequest) throws SipException {
          if (this.useRouterForAll) {
              // Use custom router to route all messages.
              if (router != null)
                  return router.getNextHop(sipRequest);
              else
                  return null;
          } else {
              // Also non-SIP request containing Route headers goes to the default
              // router
              if (sipRequest.getRequestURI().isSipURI() || sipRequest.getRouteHeaders() != null) {
                  return defaultRouter.getNextHop(sipRequest);
              } else if (router != null) {
                  return router.getNextHop(sipRequest);
              } else
                  return null;
          }
      }
  
      /**
       * Set the descriptive name of the stack.
       *
       * @param stackName -- descriptive name of the stack.
       */
      public void setStackName(String stackName) {
          this.stackName = stackName;
      }
  
  
  
      /**
       * Set my address.
       *
       * @param stackAddress -- A string containing the stack address.
       */
      protected void setHostAddress(String stackAddress) throws UnknownHostException {
          if (stackAddress.indexOf(':') != stackAddress.lastIndexOf(':')
                  && stackAddress.trim().charAt(0) != '[')
              this.stackAddress = '[' + stackAddress + ']';
          else
              this.stackAddress = stackAddress;
          this.stackInetAddress = InetAddress.getByName(stackAddress);
          
          // Changed by Deutsche Telekom
          try{
              this.mtuSize = NetworkInterface.getByInetAddress(stackInetAddress).getMTU();
              if (stackLogger.isLoggingEnabled()) {
                  stackLogger.logInfo("MTU size: "+ this.mtuSize);
              } 
          } catch (Exception e) {
              this.mtuSize = DEFAULT_MTU_SIZE;
              if (stackLogger.isLoggingEnabled()) {
                  stackLogger.logError("Unable to determine MTU size: ", e);
              }            
          }
          if (stackLogger.isLoggingEnabled()) {
              stackLogger.logDebug("Current MTU size: " + this.mtuSize);
          }            
      }
  
      /**
       * Get my address.
       *
       * @return hostAddress - my host address or null if no host address is defined.
       * @deprecated
       */
      public String getHostAddress() {
  
          // JvB: for 1.2 this may return null...
          return this.stackAddress;
      }
  
      // Changed by Deutsche Telekom
      /**
       * Return the MTU size
       *
       * @return MTU size
       */
      public int getMtuSize() {
          return mtuSize;
      }
  
      /**
       * Set the router algorithm. This is meant for routing messages out of dialog or for non-sip
       * uri's.
       *
       * @param router A class that implements the Router interface.
       */
      protected void setRouter(Router router) {
          this.router = router;
      }
  
      /**
       * Get the router algorithm.
       *
       * @return Router router
       */
      public Router getRouter(SIPRequest request) {
          if (request.getRequestLine() == null) {
              return this.defaultRouter;
          } else if (this.useRouterForAll) {
              return this.router;
          } else {
              if (request.getRequestURI().getScheme().equals("sip")
                      || request.getRequestURI().getScheme().equals("sips")) {
                  return this.defaultRouter;
              } else {
                  if (this.router != null)
                      return this.router;
                  else
                      return defaultRouter;
              }
          }
      }
  
      /*
       * (non-Javadoc)
       *
       * @see javax2.sip.SipStack#getRouter()
       */
      public Router getRouter() {
          return this.router;
      }
  
      /**
       * return the status of the toExit flag.
       *
       * @return true if the stack object is alive and false otherwise.
       */
      public boolean isAlive() {
          return !toExit;
      }
  
      /**
       * Adds a new MessageProcessor to the list of running processors for this SIPStack and starts
       * it. You can use this method for dynamic stack configuration.
       */
      protected void addMessageProcessor(MessageProcessor newMessageProcessor) throws IOException {
          synchronized (messageProcessors) {
              // Suggested changes by Jeyashankher, jai@lucent.com
              // newMessageProcessor.start() can fail
              // because a local port is not available
              // This throws an IOException.
              // We should not add the message processor to the
              // local list of processors unless the start()
              // call is successful.
              // newMessageProcessor.start();
              messageProcessors.add(newMessageProcessor);
  
          }
      }
  
      /**
       * Removes a MessageProcessor from this SIPStack.
       *
       * @param oldMessageProcessor
       */
      protected void removeMessageProcessor(MessageProcessor oldMessageProcessor) {
          synchronized (messageProcessors) {
              if (messageProcessors.remove(oldMessageProcessor)) {
                  oldMessageProcessor.stop();
              }
          }
      }
  
      /**
       * Gets an array of running MessageProcessors on this SIPStack. Acknowledgement: Jeff Keyser
       * suggested that applications should have access to the running message processors and
       * contributed this code.
       *
       * @return an array of running message processors.
       */
      protected MessageProcessor[] getMessageProcessors() {
          synchronized (messageProcessors) {
              return (MessageProcessor[]) messageProcessors.toArray(new MessageProcessor[0]);
          }
      }
  
      /**
       * Creates the equivalent of a JAIN listening point and attaches to the stack.
       *
       * @param ipAddress -- ip address for the listening point.
       * @param port -- port for the listening point.
       * @param transport -- transport for the listening point.
       */
      protected MessageProcessor createMessageProcessor(InetAddress ipAddress, int port,
              String transport) throws java.io.IOException {
          if (transport.equalsIgnoreCase("udp")) {
              UDPMessageProcessor udpMessageProcessor = new UDPMessageProcessor(ipAddress, this,
                      port);
              this.addMessageProcessor(udpMessageProcessor);
              this.udpFlag = true;
              return udpMessageProcessor;
          } else if (transport.equalsIgnoreCase("tcp")) {
              TCPMessageProcessor tcpMessageProcessor = new TCPMessageProcessor(ipAddress, this,
                      port);
              this.addMessageProcessor(tcpMessageProcessor);
              // this.tcpFlag = true;
              return tcpMessageProcessor;
          } else if (transport.equalsIgnoreCase("tls")) {
              TLSMessageProcessor tlsMessageProcessor = new TLSMessageProcessor(ipAddress, this,
                      port);
              this.addMessageProcessor(tlsMessageProcessor);
              // this.tlsFlag = true;
              return tlsMessageProcessor;
          } else if (transport.equalsIgnoreCase("sctp")) {
          	
          	// Need Java 7 for this, so these classes are packaged in a separate jar
          	// Try to load it indirectly, if fails report an error
          	try {
  				Class<?> mpc = ClassLoader.getSystemClassLoader().loadClass( "gov2.nist.javax2.sip.stack.sctp.SCTPMessageProcessor" );
  				MessageProcessor mp = (MessageProcessor) mpc.newInstance();
  				mp.initialize( ipAddress, port, this );
  				this.addMessageProcessor(mp);
  				return mp;
  			} catch (ClassNotFoundException e) {
  				throw new IllegalArgumentException("SCTP not supported (needs Java 7 and SCTP jar in classpath)");
  			} catch ( InstantiationException ie ) {
  				throw new IllegalArgumentException("Error initializing SCTP", ie);				
  			} catch ( IllegalAccessException ie ) {
  				throw new IllegalArgumentException("Error initializing SCTP", ie);				
  			}
          } else {
              throw new IllegalArgumentException("bad transport");
          }
  
      }
  
      /**
       * Set the message factory.
       *
       * @param messageFactory -- messageFactory to set.
       */
      protected void setMessageFactory(StackMessageFactory messageFactory) {
          this.sipMessageFactory = messageFactory;
      }
  
      /**
       * Creates a new MessageChannel for a given Hop.
       *
       * @param sourceIpAddress - Ip address of the source of this message.
       *
       * @param sourcePort - source port of the message channel to be created.
       *
       * @param nextHop Hop to create a MessageChannel to.
       *
       * @return A MessageChannel to the specified Hop, or null if no MessageProcessors support
       *         contacting that Hop.
       *
       * @throws UnknownHostException If the host in the Hop doesn't exist.
       */
      public MessageChannel createRawMessageChannel(String sourceIpAddress, int sourcePort,
              Hop nextHop) throws UnknownHostException {
          Host targetHost;
          HostPort targetHostPort;
          Iterator processorIterator;
          MessageProcessor nextProcessor;
          MessageChannel newChannel;
  
          // Create the host/port of the target hop
          targetHost = new Host();
          targetHost.setHostname(nextHop.getHost());
          targetHostPort = new HostPort();
          targetHostPort.setHost(targetHost);
          targetHostPort.setPort(nextHop.getPort());
  
          // Search each processor for the correct transport
          newChannel = null;
          processorIterator = messageProcessors.iterator();
          while (processorIterator.hasNext() && newChannel == null) {
              nextProcessor = (MessageProcessor) processorIterator.next();
              // If a processor that supports the correct
              // transport is found,
              if (nextHop.getTransport().equalsIgnoreCase(nextProcessor.getTransport())
                      && sourceIpAddress.equals(nextProcessor.getIpAddress().getHostAddress())
                      && sourcePort == nextProcessor.getPort()) {
                  try {
                      // Create a channel to the target
                      // host/port
                      newChannel = nextProcessor.createMessageChannel(targetHostPort);
                  } catch (UnknownHostException ex) {
                      if (isLoggingEnabled())
                          stackLogger.logException(ex);
                      throw ex;
                  } catch (IOException e) {
                      if (isLoggingEnabled())
                          stackLogger.logException(e);
                      // Ignore channel creation error -
                      // try next processor
                  }
              }
          }
          // Return the newly-created channel
          return newChannel;
      }
  
      /**
       * Return true if a given event can result in a forked subscription. The stack is configured
       * with a set of event names that can result in forked subscriptions.
       *
       * @param ename -- event name to check.
       *
       */
      public boolean isEventForked(String ename) {
          if (isLoggingEnabled()) {
              stackLogger.logDebug("isEventForked: " + ename + " returning "
                      + this.forkedEvents.contains(ename));
          }
          return this.forkedEvents.contains(ename);
      }
  
      /**
       * get the address resolver interface.
       *
       * @return -- the registered address resolver.
       */
      public AddressResolver getAddressResolver() {
          return this.addressResolver;
      }
  
      /**
       * Set the address resolution interface
       *
       * @param addressResolver -- the address resolver to set.
       */
      public void setAddressResolver(AddressResolver addressResolver) {
          this.addressResolver = addressResolver;
      }
  
      /**
       * Set the logger factory.
       *
       * @param logRecordFactory -- the log record factory to set.
       */
      public void setLogRecordFactory(LogRecordFactory logRecordFactory) {
          this.logRecordFactory = logRecordFactory;
      }
  
      /**
       * get the thread auditor object
       *
       * @return -- the thread auditor of the stack
       */
      public ThreadAuditor getThreadAuditor() {
          return this.threadAuditor;
      }
  
      // /
      // / Stack Audit methods
      // /
  
      /**
       * Audits the SIP Stack for leaks
       *
       * @return Audit report, null if no leaks were found
       */
      public String auditStack(Set activeCallIDs, long leakedDialogTimer,
              long leakedTransactionTimer) {
          String auditReport = null;
          String leakedDialogs = auditDialogs(activeCallIDs, leakedDialogTimer);
          String leakedServerTransactions = auditTransactions(serverTransactionTable,
                  leakedTransactionTimer);
          String leakedClientTransactions = auditTransactions(clientTransactionTable,
                  leakedTransactionTimer);
          if (leakedDialogs != null || leakedServerTransactions != null
                  || leakedClientTransactions != null) {
              auditReport = "SIP Stack Audit:\n" + (leakedDialogs != null ? leakedDialogs : "")
                      + (leakedServerTransactions != null ? leakedServerTransactions : "")
                      + (leakedClientTransactions != null ? leakedClientTransactions : "");
          }
          return auditReport;
      }
  
      /**
       * Audits SIP dialogs for leaks - Compares the dialogs in the dialogTable with a list of Call
       * IDs passed by the application. - Dialogs that are not known by the application are leak
       * suspects. - Kill the dialogs that are still around after the timer specified.
       *
       * @return Audit report, null if no dialog leaks were found
       */
      private String auditDialogs(Set activeCallIDs, long leakedDialogTimer) {
          String auditReport = "  Leaked dialogs:\n";
          int leakedDialogs = 0;
          long currentTime = System.currentTimeMillis();
  
          // Make a shallow copy of the dialog list.
          // This copy will remain intact as leaked dialogs are removed by the
          // stack.
          LinkedList dialogs;
          synchronized (dialogTable) {
              dialogs = new LinkedList(dialogTable.values());
          }
  
          // Iterate through the dialogDialog, get the callID of each dialog and
          // check if it's in the
          // list of active calls passed by the application. If it isn't, start
          // the timer on it.
          // If the timer has expired, kill the dialog.
          Iterator it = dialogs.iterator();
          while (it.hasNext()) {
              // Get the next dialog
              SIPDialog itDialog = (SIPDialog) it.next();
  
              // Get the call id associated with this dialog
              CallIdHeader callIdHeader = (itDialog != null ? itDialog.getCallId() : null);
              String callID = (callIdHeader != null ? callIdHeader.getCallId() : null);
  
              // Check if the application knows about this call id
              if (itDialog != null && callID != null && !activeCallIDs.contains(callID)) {
                  // Application doesn't know anything about this dialog...
                  if (itDialog.auditTag == 0) {
                      // Mark this dialog as suspect
                      itDialog.auditTag = currentTime;
                  } else {
                      // We already audited this dialog before. Check if his
                      // time's up.
                      if (currentTime - itDialog.auditTag >= leakedDialogTimer) {
                          // Leaked dialog found
                          leakedDialogs++;
  
                          // Generate report
                          DialogState dialogState = itDialog.getState();
                          String dialogReport = "dialog id: " + itDialog.getDialogId()
                                  + ", dialog state: "
                                  + (dialogState != null ? dialogState.toString() : "null");
                          auditReport += "    " + dialogReport + "\n";
  
                          // Kill it
                          itDialog.setState(SIPDialog.TERMINATED_STATE);
                          if (isLoggingEnabled())
                          	stackLogger.logDebug("auditDialogs: leaked " + dialogReport);
                      }
                  }
              }
          }
  
          // Return final report
          if (leakedDialogs > 0) {
              auditReport += "    Total: " + Integer.toString(leakedDialogs)
                      + " leaked dialogs detected and removed.\n";
          } else {
              auditReport = null;
          }
          return auditReport;
      }
  
      /**
       * Audits SIP transactions for leaks
       *
       * @return Audit report, null if no transaction leaks were found
       */
      private String auditTransactions(ConcurrentHashMap transactionsMap,
              long a_nLeakedTransactionTimer) {
          String auditReport = "  Leaked transactions:\n";
          int leakedTransactions = 0;
          long currentTime = System.currentTimeMillis();
  
          // Make a shallow copy of the transaction list.
          // This copy will remain intact as leaked transactions are removed by
          // the stack.
          LinkedList transactionsList = new LinkedList(transactionsMap.values());
  
          // Iterate through our copy
          Iterator it = transactionsList.iterator();
          while (it.hasNext()) {
              SIPTransaction sipTransaction = (SIPTransaction) it.next();
              if (sipTransaction != null) {
                  if (sipTransaction.auditTag == 0) {
                      // First time we see this transaction. Mark it as audited.
                      sipTransaction.auditTag = currentTime;
                  } else {
                      // We've seen this transaction before. Check if his time's
                      // up.
                      if (currentTime - sipTransaction.auditTag >= a_nLeakedTransactionTimer) {
                          // Leaked transaction found
                          leakedTransactions++;
  
                          // Generate some report
                          TransactionState transactionState = sipTransaction.getState();
                          SIPRequest origRequest = sipTransaction.getOriginalRequest();
                          String origRequestMethod = (origRequest != null ? origRequest.getMethod()
                                  : null);
                          String transactionReport = sipTransaction.getClass().getName()
                                  + ", state: "
                                  + (transactionState != null ? transactionState.toString()
                                          : "null") + ", OR: "
                                  + (origRequestMethod != null ? origRequestMethod : "null");
                          auditReport += "    " + transactionReport + "\n";
  
                          // Kill it
                          removeTransaction(sipTransaction);
                          if (isLoggingEnabled())
                          	stackLogger.logDebug("auditTransactions: leaked " + transactionReport);
                      }
                  }
              }
          }
  
          // Return final report
          if (leakedTransactions > 0) {
              auditReport += "    Total: " + Integer.toString(leakedTransactions)
                      + " leaked transactions detected and removed.\n";
          } else {
              auditReport = null;
          }
          return auditReport;
      }
  
      public void setNon2XXAckPassedToListener(boolean passToListener) {
          this.non2XXAckPassedToListener = passToListener;
      }
  
      /**
       * @return the non2XXAckPassedToListener
       */
      public boolean isNon2XXAckPassedToListener() {
          return non2XXAckPassedToListener;
      }
  
      /**
       * Get the count of client transactions that is not in the completed or terminated state.
       *
       * @return the activeClientTransactionCount
       */
      public int getActiveClientTransactionCount() {
          return activeClientTransactionCount.get();
      }
  
      public boolean isRfc2543Supported() {
  
          return this.rfc2543Supported;
      }
  
      public boolean isCancelClientTransactionChecked() {
          return this.cancelClientTransactionChecked;
      }
  
      public boolean isRemoteTagReassignmentAllowed() {
          return this.remoteTagReassignmentAllowed;
      }
  
      /**
       * This method is slated for addition to the next spec revision.
       *
       *
       * @return -- the collection of dialogs that is being managed by the stack.
       */
      public Collection<Dialog> getDialogs() {
          HashSet<Dialog> dialogs = new HashSet<Dialog>();
          dialogs.addAll(this.dialogTable.values());
          dialogs.addAll(this.earlyDialogTable.values());
          return dialogs;
      }
  
      /**
       *
       * @return -- the collection of dialogs matching the state that is being managed by the stack.
       */
      public Collection<Dialog> getDialogs(DialogState state) {
          HashSet<Dialog> matchingDialogs = new HashSet<Dialog>();
          if (DialogState.EARLY.equals(state)) {
              matchingDialogs.addAll(this.earlyDialogTable.values());
          } else {
              Collection<SIPDialog> dialogs = dialogTable.values();
              for (SIPDialog dialog : dialogs) {
                  if (dialog.getState() != null && dialog.getState().equals(state)) {
                      matchingDialogs.add(dialog);
                  }
              }
          }
          return matchingDialogs;
      }
  
      /**
       * Get the Replaced Dialog from the stack.
       *
       * @param replacesHeader -- the header that references the dialog being replaced.
       */
      public Dialog getReplacesDialog(ReplacesHeader replacesHeader) {
          String cid = replacesHeader.getCallId();
          String fromTag = replacesHeader.getFromTag();
          String toTag = replacesHeader.getToTag();
  
          StringBuffer dialogId = new StringBuffer(cid);
  
          // retval.append(COLON).append(to.getUserAtHostPort());
          if (toTag != null) {
              dialogId.append(":");
              dialogId.append(toTag);
          }
          // retval.append(COLON).append(from.getUserAtHostPort());
          if (fromTag != null) {
              dialogId.append(":");
              dialogId.append(fromTag);
          }
          String did = dialogId.toString().toLowerCase();
          if (isLoggingEnabled())
          	stackLogger.logDebug("Looking for dialog " + did);
          /*
           * Check if we can find this dialog in our dialog table.
           */
          Dialog replacesDialog =  this.dialogTable.get(did);
          /*
           * This could be a forked dialog. Search for it.
           */
          if ( replacesDialog == null ) {
             for ( SIPClientTransaction ctx : this.clientTransactionTable.values()) {
                 if ( ctx.getDialog(did) != null ) {
                     replacesDialog = ctx.getDialog(did);
                     break;
                 }
             }
          }
  
          return replacesDialog;
      }
  
      /**
       * Get the Join Dialog from the stack.
       *
       * @param joinHeader -- the header that references the dialog being joined.
       */
      public Dialog getJoinDialog(JoinHeader joinHeader) {
          String cid = joinHeader.getCallId();
          String fromTag = joinHeader.getFromTag();
          String toTag = joinHeader.getToTag();
  
          StringBuffer retval = new StringBuffer(cid);
  
          // retval.append(COLON).append(to.getUserAtHostPort());
          if (toTag != null) {
              retval.append(":");
              retval.append(toTag);
          }
          // retval.append(COLON).append(from.getUserAtHostPort());
          if (fromTag != null) {
              retval.append(":");
              retval.append(fromTag);
          }
          return this.dialogTable.get(retval.toString().toLowerCase());
      }
  
      /**
       * @param timer the timer to set
       */
      public void setTimer(Timer timer) {
          this.timer = timer;
      }
  
      /**
       * @return the timer
       */
      public Timer getTimer() {
          return timer;
      }
  
      
      /**
       * Size of the receive UDP buffer. This property affects performance under load. Bigger buffer
       * is better under load.
       * 
       * @return
       */
  	public int getReceiveUdpBufferSize() {
  		return receiveUdpBufferSize;
  	}
  
      /**
       * Size of the receive UDP buffer. This property affects performance under load. Bigger buffer
       * is better under load.
       * 
       * @return
       */
  	public void setReceiveUdpBufferSize(int receiveUdpBufferSize) {
  		this.receiveUdpBufferSize = receiveUdpBufferSize;
  	}
  
      /**
       * Size of the send UDP buffer. This property affects performance under load. Bigger buffer
       * is better under load.
       * 
       * @return
       */
  	public int getSendUdpBufferSize() {
  		return sendUdpBufferSize;
  	}
  
      /**
       * Size of the send UDP buffer. This property affects performance under load. Bigger buffer
       * is better under load.
       * 
       * @return
       */
  	public void setSendUdpBufferSize(int sendUdpBufferSize) {
  		this.sendUdpBufferSize = sendUdpBufferSize;
  	}
  
  	/**
  	 * @param stackLogger the stackLogger to set
  	 */
  	public void setStackLogger(StackLogger stackLogger) {		
  		this.stackLogger = stackLogger;
  	}
  	
  	 /**
  	  * Flag that reqests checking of branch IDs on responses.
  	  * 
  	  * @return
  	  */
  	 public boolean checkBranchId() {
  	       return this.checkBranchId;
  	 }
  
      /**
       * @param logStackTraceOnMessageSend the logStackTraceOnMessageSend to set
       */
      public void setLogStackTraceOnMessageSend(boolean logStackTraceOnMessageSend) {
          this.logStackTraceOnMessageSend = logStackTraceOnMessageSend;
      }
  
      /**
       * @return the logStackTraceOnMessageSend
       */
      public boolean isLogStackTraceOnMessageSend() {
          return logStackTraceOnMessageSend;
      }
      
      public void setDeliverDialogTerminatedEventForNullDialog() {
          this.isDialogTerminatedEventDeliveredForNullDialog = true;
      }
      
      public void addForkedClientTransaction(SIPClientTransaction clientTransaction) {
          this.forkedClientTransactionTable.put(clientTransaction.getTransactionId(), clientTransaction );
      }
  
      public SIPClientTransaction getForkedTransaction(String transactionId) {
          return this.forkedClientTransactionTable.get(transactionId);
      }
  	
  	
  }
  