/*
* 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.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.message.*;
import gov.nist.javax.sip.header.*;
import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import gov.nist.core.*;
import gov.nist.core.net.AddressResolver;
import gov.nist.core.net.DefaultNetworkLayer;
import gov.nist.core.net.NetworkLayer;
import javax.sip.*;
import javax.sip.message.*;
import javax.sip.address.*;
import javax.sip.header.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import java.io.IOException;
import java.net.*;
/*
* 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.94 $ $Date: 2008/06/12 12:50:24 $
*/
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 int activeClientTransactionCount;
// 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();
}
/**
* 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);
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
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 > clientTransactionTableHiwaterMark) {
try {
synchronized (this.clientTransactionTable) {
this.clientTransactionTable.wait();
}
} catch (Exception ex) {
if (logWriter.isLoggingEnabled()) {
logWriter.logError(
"Exception occured while waiting for room",
ex);
}
}
}
}
this.activeClientTransactionCount++;
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() {
this.activeClientTransactionCount--;
if (this.activeClientTransactionCount <= 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;
}
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());
}
/**
* @param timer the timer to set
*/
public void setTimer(Timer timer) {
this.timer = timer;
}
/**
* @return the timer
*/
public Timer getTimer() {
return timer;
}
}