Package gov.nist.javax.sip.stack

Source Code of gov.nist.javax.sip.stack.SIPTransactionStack

/*
* 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 gov.nist.javax.sip.stack;

import gov.nist.core.Host;
import gov.nist.core.HostPort;
import gov.nist.core.LogWriter;
import gov.nist.core.ThreadAuditor;
import gov.nist.core.net.AddressResolver;
import gov.nist.core.net.DefaultNetworkLayer;
import gov.nist.core.net.NetworkLayer;
import gov.nist.javax.sip.DefaultAddressResolver;
import gov.nist.javax.sip.ListeningPointImpl;
import gov.nist.javax.sip.LogRecordFactory;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.header.Contact;
import gov.nist.javax.sip.header.Event;
import gov.nist.javax.sip.header.Server;
import gov.nist.javax.sip.header.Via;
import gov.nist.javax.sip.header.extensions.JoinHeader;
import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;

import java.io.IOException;
import java.net.InetAddress;
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 javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.DialogTerminatedEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.TransactionState;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.address.Address;
import javax.sip.address.Hop;
import javax.sip.address.Router;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.header.EventHeader;
import javax.sip.message.Request;
import javax.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 gov.nist.javax.sip.SipStackImpl
*
* @author M. Ranganathan <br/>
*
* @version 1.2 $Revision: 1.101 $ $Date: 2009/01/22 19:33:49 $
*/
public abstract class SIPTransactionStack implements SIPTransactionEventListener {

    /*
     * 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 HashSet<String> dialogCreatingMethods;

    // 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.
    private boolean unlimitedServerTransactionTableSize = false;

    // 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;

    /*
     * A wrapper around log4j to help log debug.
     */
    protected LogWriter logWriter;

    /*
     * ServerLog is used just for logging stack message tracecs.
     */
    protected ServerLog serverLog;

    /*
     * 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;

    /*
     * Request factory interface (to be provided by the application)
     */
    protected StackMessageFactory sipMessageFactory;

    /*
     * Router to determine where to forward the request.
     */
    protected javax.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;

    /*
     * Flag to indicate whether the stack will delegate the TLS encryption/decryption to external
     * hardware.
     */
    protected boolean useTlsAccelerator = false;

    // 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;

    // / 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());
            }
        }

    }

    /**
     * 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;

        // a set of methods that result in dialog creation.
        this.dialogCreatingMethods = new HashSet<String>();
        // Standard set of methods that create dialogs.
        this.dialogCreatingMethods.add(Request.REFER);
        this.dialogCreatingMethods.add(Request.INVITE);
        this.dialogCreatingMethods.add(Request.SUBSCRIBE);
        // 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>();
        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>();

        if (getThreadAuditor().isEnabled()) {
            // Start monitoring the timer thread
            timer.schedule(new PingTimer(null), 0);
        }
    }

    /**
     * Re Initialize the stack instance.
     */
    protected void reInit() {
        if (logWriter.isLoggingEnabled())
            logWriter.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.timer = new Timer();

        this.activeClientTransactionCount = new AtomicInteger(0);

    }

    /**
     * For debugging -- allows you to disable logging or enable logging selectively.
     *
     *
     */
    public void disableLogging() {
        this.getLogWriter().disableLogging();
    }

    /**
     * Globally enable message logging ( for debugging)
     *
     */
    public void enableLogging() {
        this.getLogWriter().enableLogging();
    }

    /**
     * Print the dialog table.
     *
     */
    public void printDialogTable() {
        if (this.getLogWriter().isLoggingEnabled()) {
            this.getLogWriter().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 boolean isDialogCreated(String method) {

        boolean retval = dialogCreatingMethods.contains(method);
        if (this.isLoggingEnabled()) {
            this.getLogWriter().logDebug("isDialogCreated : " + method + " returning " + retval);
        }
        return retval;
    }

    /**
     * Add an extension method.
     *
     * @param extensionMethod -- extension method to support for dialog creation
     */
    public void addExtensionMethod(String extensionMethod) {
        if (extensionMethod.equals(Request.NOTIFY)) {
            if (logWriter.isLoggingEnabled())
                logWriter.logDebug("NOTIFY Supported Natively");
        } else {
            this.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 (logWriter.isLoggingEnabled()) {
                logWriter.logDebug("putDialog: dialog already exists" + dialogId + " in table = "
                        + dialogTable.get(dialogId));
            }
            return;
        }
        if (logWriter.isLoggingEnabled()) {
            logWriter.logDebug("putDialog dialogId=" + dialogId + " dialog = " + dialog);
        }
        dialog.setStack(this);
        if (logWriter.isLoggingEnabled())
            logWriter.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;

    }

    /**
     * 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);

            }

        }

    }

    /**
     * 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 (logWriter.isLoggingEnabled()) {
            logWriter.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 (logWriter.isLoggingEnabled()) {
            logWriter.logWarning("Silently removing dialog from table");
        }
        if (dialogTable.containsKey(dialogId)) {
            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();
            logWriter.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 (logWriter.isLoggingEnabled()) {
                    logWriter.logDebug("event Header is null -- returning null");
                }

                return retval;
            }
            while (it.hasNext()) {
                SIPClientTransaction ct = (SIPClientTransaction) it.next();
                if (!ct.getMethod().equals(Request.SUBSCRIBE))
                    continue;
                SIPRequest sipRequest = ct.getOriginalRequest();
                Contact contact = sipRequest.getContactHeader();
                Address address = contact.getAddress();
                SipURI uri = (SipURI) address.getURI();
                String host = uri.getHost();
                int port = uri.getPort();
                String transport = uri.getTransportParam();
                if (transport == null)
                    transport = "udp";
                if (port == -1) {
                    if (transport.equals("udp") || transport.equals("tcp"))
                        port = 5060;
                    else
                        port = 5061;
                }
                // 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 (this.isLoggingEnabled()) {
                    logWriter.logDebug("ct.fromTag = " + fromTag);
                    logWriter.logDebug("thisToTag = " + thisToTag);
                    logWriter.logDebug("hisEvent = " + hisEvent);
                    logWriter.logDebug("eventHdr " + eventHdr);

                }

                // Check that the NOTIFY is directed at the contact address
                // specified by the SUBSCRIBE ( this is to prevent spurious
                // NOTOFY's
                if (listeningPoint.getPort() == port
                        && listeningPoint.getIPAddress().equals(host)
                        && 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 (this.isLoggingEnabled())
                logWriter.logDebug("findSubscribeTransaction : returning " + retval);

        }

    }

    /**
     * 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;

        if (isServer) {
            Via via = sipMessage.getTopmostVia();
            if (via.getBranch() != null) {
                String key = sipMessage.getTransactionId();

                retval = (SIPTransaction) serverTransactionTable.get(key);
                if (logWriter.isLoggingEnabled())
                    getLogWriter().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))
                    return sipServerTransaction;
            }

        } else {
            Via via = sipMessage.getTopmostVia();
            if (via.getBranch() != null) {
                String key = sipMessage.getTransactionId();
                if (logWriter.isLoggingEnabled())
                    getLogWriter().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))
                    return clientTransaction;
            }

        }
        return null;

    }

    /**
     * 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 (logWriter.isLoggingEnabled()) {
            logWriter.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 (logWriter.isLoggingEnabled())
            logWriter.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 (this.logWriter.isLoggingEnabled()) {
            this.logWriter.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 (!this.isDialogCreated(sipRequest.getMethod()))
            return null;
        String mergeId = sipRequest.getMergeId();
        if (mergeId != null) {
            return (SIPServerTransaction) this.mergeTable.get(mergeId);
        } else {
            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 (this.logWriter.isLoggingEnabled()) {
            this.logWriter.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 (logWriter.isLoggingEnabled()) {
            this.logWriter.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 (logWriter.isLoggingEnabled()) {
            logWriter.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) {
                logWriter.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.
     */
    protected 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 (this.logWriter.isLoggingEnabled(LogWriter.TRACE_MESSAGES)) {
                    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 (this.logWriter.isLoggingEnabled(LogWriter.TRACE_MESSAGES)) {
            currentTransaction.logResponse(responseReceived, System.currentTimeMillis(),
                    "before processing");
        }

        if (acquired) {
            ServerResponseInterface sri = sipMessageFactory.newSIPServerResponse(
                    responseReceived, currentTransaction);
            if (sri != null) {
                currentTransaction.setResponseInterface(sri);
            } else {
                if (this.logWriter.isLoggingEnabled()) {
                    this.logWriter.logDebug("returning null - serverResponseInterface is null!");
                }
                currentTransaction.releaseSem();
                return null;
            }
        }

        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) {
        if (unlimitedServerTransactionTableSize
                || this.serverTransactionTable.size() < serverTransactionTableLowaterMark)
            return new SIPServerTransaction(this, encapsulatedMessageChannel);
        else if (this.serverTransactionTable.size() >= serverTransactionTableHighwaterMark) {

            return null;
        } 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();
    }

    /**
     * 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 (logWriter.isLoggingEnabled())
            logWriter.logDebug("added transaction " + clientTransaction);
        addTransactionHash(clientTransaction);
        // clientTransaction.startTransactionTimer();
    }

    /**
     * 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 (logWriter.isLoggingEnabled()) {
            logWriter.logDebug("Removing Transaction = " + sipTransaction.getTransactionId()
                    + " transaction = " + sipTransaction);
        }
        if (sipTransaction instanceof SIPServerTransaction) {
            if (logWriter.isLoggingEnabled())
                logWriter.logStackTrace();
            String key = sipTransaction.getTransactionId();
            Object removed = serverTransactionTable.remove(key);
            String method = sipTransaction.getMethod();
            this.removePendingTransaction((SIPServerTransaction) sipTransaction);
            if (this.isDialogCreated(method)) {
                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 (logWriter.isLoggingEnabled()) {
                logWriter.logDebug("REMOVED client tx " + removed + " KEY = " + key);
            }

            // 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 (logWriter.isLoggingEnabled())
            logWriter.logDebug("added transaction " + serverTransaction);
        serverTransaction.map();

        addTransactionHash(serverTransaction);
        // serverTransaction.startTransactionTimer();
    }

    /**
     * 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 (logWriter.isLoggingEnabled()) {
                            logWriter.logError("Exception occured while waiting for room", ex);
                        }

                    }
                }
            } else {
                this.activeClientTransactionCount.incrementAndGet();
            }
            String key = sipRequest.getTransactionId();
            clientTransactionTable.put(key, (SIPClientTransaction) sipTransaction);
            if (logWriter.isLoggingEnabled()) {
                logWriter.logDebug(" putTransactionHash : " + " key = " + key);
            }
        } else {
            String key = sipRequest.getTransactionId();

            if (logWriter.isLoggingEnabled()) {
                logWriter.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 (logWriter.isLoggingEnabled()) {
                logWriter.logStackTrace();
                logWriter.logDebug("removing client Tx : " + key);
            }
            clientTransactionTable.remove(key);

        } else if (sipTransaction instanceof SIPServerTransaction) {
            String key = sipTransaction.getTransactionId();
            serverTransactionTable.remove(key);
            if (logWriter.isLoggingEnabled()) {
                logWriter.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.
        }
    }

    /**
     * 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.serverLog.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 (logWriter.isLoggingEnabled())
            logWriter.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.logWriter == null ? false : this.logWriter.isLoggingEnabled();
    }

    /**
     * Get the logger.
     *
     * @return --the logger for the sip stack. Each stack has its own logger instance.
     */
    public LogWriter getLogWriter() {
        return this.logWriter;
    }

    /**
     * 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 ServerLog getServerLog() {
        return this.serverLog;
    }

    /**
     * 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;
    }

    /**
     * Create a standard Server header for the stack (i.e. one that takes the stack name as a
     * product token) and return it.
     *
     * @return Server header for the stack. The server header is used in automatically generated
     *         responses.
     *
     */
    public Server createServerHeaderForStack() {

        Server retval = new Server();
        retval.addProductToken(this.stackName);
        return retval;
    }

    /**
     * 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);
    }

    /**
     * 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;
    }

    /**
     * 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 javax.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 {
            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 (logWriter.isLoggingEnabled())
                        logWriter.logException(ex);
                    throw ex;
                } catch (IOException e) {
                    if (logWriter.isLoggingEnabled())
                        logWriter.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 (logWriter.isLoggingEnabled()) {
            logWriter.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 (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);
                        logWriter.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);
                        logWriter.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 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());
    }
   
    /**
     * 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;
    }

}
TOP

Related Classes of gov.nist.javax.sip.stack.SIPTransactionStack

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.