package gov.nist.javax.sip;
import java.util.*;
import java.util.StringTokenizer;
import javax.sip.*;
import javax.sip.address.*;
import javax.sip.message.*;
import gov.nist.javax.sip.header.Server;
import gov.nist.javax.sip.stack.*;
import java.lang.reflect.*;
import java.net.InetAddress;
import gov.nist.core.*;
import gov.nist.core.net.AddressResolver;
import gov.nist.core.net.NetworkLayer;
/**
* Implementation of SipStack.
*
* The JAIN-SIP stack is initialized by a set of properties (see the JAIN SIP
* documentation for an explanation of these properties
* {@link javax.sip.SipStack} ). In addition to these, the following are
* meaningful properties for the NIST SIP stack (specify these in the property
* array when you create the JAIN-SIP statck).:
*
*
* - gov.nist.javax.sip.TRACE_LEVEL = integer
Currently only 16
* and 32 is meaningful. If this is set to 16 or above, then incoming valid
* messages are logged in SERVER_LOG. If you set this to 32 and specify a
* DEBUG_LOG then vast amounts of trace information will be dumped in to the
* specified DEBUG_LOG. The server log accumulates the signaling trace. This can be viewed
* using the trace viewer tool . Please send us both the server log and
* debug log when reporting non-obvious problems. You can also use DEBUG or
* TRACE for level 32 and 16 respectively
*
* - gov.nist.javax.sip.SERVER_LOG = fileName
Log valid
* incoming messages here. If this is left null AND the TRACE_LEVEL is above 16
* then the messages are printed to stdout. Otherwise messages are logged in a
* format that can later be viewed using the trace viewer application which is
* located in the tools/tracesviewer directory. Mail this to us
* with bug reports.
*
* - gov.nist.javax.sip.LOG_MESSAGE_CONTENT = true|false
Set
* true if you want to capture content into the log. Default is false. A bad
* idea to log content if you are using SIP to push a lot of bytes through TCP.
*
*
*
*
* - gov.nist.javax.sip.DEBUG_LOG = fileName
Where the debug
* log goes. Mail this to us with bug reports.
*
* - gov.nist.javax.sip.MAX_MESSAGE_SIZE = integer
Maximum size
* of content that a TCP connection can read. Must be at least 4K. Default is
* "infinity" -- ie. no limit. This is to prevent DOS attacks launched by
* writing to a TCP connection until the server chokes.
*
*
*
* - gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS = [true|false]
* Default value is true. Setting this to true makes the Stack close the server
* socket after a Server Transaction goes to the TERMINATED state. This allows a
* server to protectect against TCP based Denial of Service attacks launched by
* clients (ie. initiate hundreds of client gransactions). If false (default
* action), the stack will keep the socket open so as to maximize performance at
* the expense of Thread and memory resources - leaving itself open to DOS
* attacks.
*
* - gov.nist.javax.sip.CACHE_CLIENT_CONNECTIONS = [true|false]
* Default value is true. Setting this to true makes the Stack close the server
* socket aftera Client Transaction goes to the TERMINATED state. This allows a
* client release any buffers threads and socket connections associated with a
* client transaction after the transaction has terminated at the expense of
* performance.
*
* - gov.nist.javax.sip.THREAD_POOL_SIZE = integer
Concurrency
* control for number of simultaneous active threads. If unspecificed, the
* default is "infinity". This feature is useful if you are trying to build a
* container.
*
* -
*
- If this is not specified, and the listener is re-entrant, each
* event delivered to the listener is run in the context of a new thread.
* - If this is specified and the listener is re-entrant, then the stack will
* run the listener using a thread from the thread pool. This allows you to
* manage the level of concurrency to a fixed maximum. Threads are pre-allocated
* when the stack is instantiated.
* - If this is specified and the listener is not re-entrant, then the stack
* will use the thread pool thread from this pool to parse and manage the state
* machine but will run the listener in its own thread.
*
*
* - gov.nist.javax.sip.REENTRANT_LISTENER = true|false
Default
* is false. Set to true if the listener is re-entrant. If the listener is
* re-entrant then the stack manages a thread pool and synchronously calls the
* listener from the same thread which read the message. Multiple transactions
* may concurrently receive messages and this will result in multiple threads
* being active in the listener at the same time. The listener has to be written
* with this in mind. If you want good performance on a multithreaded machine
* write your listener to be re-entrant and set this property to be true
*
* - gov.nist.javax.sip.MAX_CONNECTIONS = integer
Max number of
* simultaneous TCP connections handled by stack. (Was mis-spelled -
* Documentation bug fix by Bob Johnson)
*
* - gov.nist.javax.sip.PASS_INIVTE_NON2XX_ACK_TO_LISTENER = true|false
*
If true then the listener will see the ACK for non-2xx responses
* for server transactions. This is not standard behavior per RFC 3261 (INVITE
* server transaction state machine) but this is a useful flag for testing. The
* TCK uses this flag for example.
*
*
* - gov.nist.javax.sip.READ_TIMEOUT = integer
This is relevant
* for incoming TCP connections to prevent starvation at the server. This
* defines the timeout in miliseconds between successive reads after the first
* byte of a SIP message is read by the stack. All the sip headers must be
* delivered in this interval and each successive buffer must be of the content
* delivered in this interval. Default value is -1 (ie. the stack is wide open
* to starvation attacks) and the client can be as slow as it wants to be.
*
* - gov.nist.javax.sip.NETWORK_LAYER = classpath
This is an
* EXPERIMENTAL property (still under active devlopment). Defines a network
* layer that allows a client to have control over socket allocations and
* monitoring of socket activity. A network layer should implement
* gov.nist.core.net.NetworkLayer. The default implementation simply acts as a
* wrapper for the standard java.net socket layer. This functionality is still
* under active development (may be extended to support security and other
* features).
*
*
*
*
* @version JAIN-SIP-1.1 $Revision: 1.24 $ $Date: 2006/06/11 18:51:06 $
*
* @author M. Ranganathan
*
* This code is in the public domain.
*
*
*/
public class SipStackImpl extends SIPTransactionStack implements
javax.sip.SipStack {
EventScanner eventScanner;
private Hashtable listeningPoints;
private LinkedList sipProviders;
// Flag to indicate that the listener is re-entrant and hence
// Use this flag with caution.
boolean reEntrantListener;
SipListener sipListener;
/**
* Creates a new instance of SipStackImpl.
*/
protected SipStackImpl() {
super();
NistSipMessageFactoryImpl msgFactory = new NistSipMessageFactoryImpl(
this);
super.setMessageFactory(msgFactory);
this.eventScanner = new EventScanner(this);
this.listeningPoints = new Hashtable();
this.sipProviders = new LinkedList();
}
/**
* ReInitialize the stack instance.
*/
private void reInitialize() {
super.reInit();
this.eventScanner = new EventScanner(this);
this.listeningPoints = new Hashtable();
this.sipProviders = new LinkedList();
this.sipListener = null;
}
/**
* Return true if automatic dialog support is enabled for this stack.
*
* @return
*/
boolean isAutomaticDialogSupportEnabled() {
return super.isAutomaticDialogSupportEnabled;
}
/**
* Constructor for the stack.
*
* @param configurationProperties -- stack configuration properties including NIST-specific extensions.
* @throws PeerUnavailableException
*/
public SipStackImpl(Properties configurationProperties)
throws PeerUnavailableException {
this();
String address = configurationProperties
.getProperty("javax.sip.IP_ADDRESS");
try {
/** Retrieve the stack IP address */
if (address != null) {
// In version 1.2 of the spec the IP address is
// associated with the listening point and
// is not madatory.
super.setHostAddress(address);
}
} catch (java.net.UnknownHostException ex) {
throw new PeerUnavailableException("bad address " + address);
}
/** Retrieve the stack name */
String name = configurationProperties
.getProperty("javax.sip.STACK_NAME");
if (name == null)
throw new PeerUnavailableException("stack name is missing");
super.setStackName(name);
// To log debug messages.
this.logWriter = new LogWriter(configurationProperties);
// Server log file.
this.serverLog = new ServerLog(this, configurationProperties);
// Default router -- use this for routing SIP URIs.
// Our router does not do DNS lookups.
this.defaultRouter = new DefaultRouter(this, outboundProxy);
/** Retrieve the router path */
String routerPath = configurationProperties
.getProperty("javax.sip.ROUTER_PATH");
if (routerPath == null)
routerPath = "gov.nist.javax.sip.stack.DefaultRouter";
String outboundProxy = configurationProperties
.getProperty("javax.sip.OUTBOUND_PROXY");
try {
Class routerClass = Class.forName(routerPath);
Class[] constructorArgs = new Class[2];
constructorArgs[0] = javax.sip.SipStack.class;
constructorArgs[1] = String.class;
Constructor cons = routerClass.getConstructor(constructorArgs);
Object[] args = new Object[2];
args[0] = (SipStack) this;
args[1] = outboundProxy;
Router router = (Router) cons.newInstance(args);
super.setRouter(router);
} catch (InvocationTargetException ex1) {
getLogWriter().logError("could not instantiate router -- invocation target problem",
(Exception)ex1.getCause());
throw new PeerUnavailableException(
"Cound not instantiate router - check constructor", ex1);
} catch (Exception ex) {
getLogWriter().logError("could not instantiate router", (Exception)ex.getCause());
throw new PeerUnavailableException("Could not instantiate router",
ex);
}
// The flag that indicates that the default router is to be ignored.
String useRouterForAll = configurationProperties
.getProperty("javax.sip.USE_ROUTER_FOR_ALL_URIS");
this.useRouterForAll = true;
if (useRouterForAll != null) {
this.useRouterForAll = "true".equalsIgnoreCase(useRouterForAll);
}
/*
* Retrieve the EXTENSION Methods. These are used for instantiation of
* Dialogs.
*/
String extensionMethods = configurationProperties
.getProperty("javax.sip.EXTENSION_METHODS");
if (extensionMethods != null) {
java.util.StringTokenizer st = new java.util.StringTokenizer(
extensionMethods);
LinkedList ll = new LinkedList();
while (st.hasMoreTokens()) {
String em = st.nextToken(":");
if (em.equalsIgnoreCase(Request.BYE)
|| em.equalsIgnoreCase(Request.INVITE)
|| em.equalsIgnoreCase(Request.SUBSCRIBE)
|| em.equalsIgnoreCase(Request.NOTIFY)
|| em.equalsIgnoreCase(Request.ACK)
|| em.equalsIgnoreCase(Request.OPTIONS))
throw new PeerUnavailableException("Bad extension method "
+ em);
else
this.addExtensionMethod(em);
}
}
// Set the auto dialog support flag.
super.isAutomaticDialogSupportEnabled = configurationProperties
.getProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT", "on")
.equalsIgnoreCase("on");
String forkedSubscriptions = configurationProperties
.getProperty("javax.sip.FORKABLE_EVENTS");
if (forkedSubscriptions != null) {
StringTokenizer st = new StringTokenizer(forkedSubscriptions);
while (st.hasMoreTokens()) {
String nextEvent = st.nextToken();
this.forkedEvents.add(nextEvent);
}
}
// The following features are unique to the NIST implementation.
/*
* gets the NetworkLayer implementation, if any. Note that this is a
* NIST only feature.
*/
final String NETWORK_LAYER_KEY = "gov.nist.javax.sip.NETWORK_LAYER";
if (configurationProperties.containsKey(NETWORK_LAYER_KEY)) {
String path = configurationProperties
.getProperty(NETWORK_LAYER_KEY);
try {
Class clazz = Class.forName(path);
Constructor c = clazz.getConstructor(new Class[0]);
networkLayer = (NetworkLayer) c.newInstance(new Object[0]);
} catch (Exception e) {
throw new PeerUnavailableException(
"can't find or instantiate NetworkLayer implementation: "
+ path);
}
}
String maxConnections = configurationProperties
.getProperty("gov.nist.javax.sip.MAX_CONNECTIONS");
if (maxConnections != null) {
try {
this.maxConnections = new Integer(maxConnections).intValue();
} catch (NumberFormatException ex) {
System.out.println("max connections - bad value "
+ ex.getMessage());
}
}
String threadPoolSize = configurationProperties
.getProperty("gov.nist.javax.sip.THREAD_POOL_SIZE");
if (threadPoolSize != null) {
try {
this.threadPoolSize = new Integer(threadPoolSize).intValue();
} catch (NumberFormatException ex) {
System.out.println("thread pool size - bad value "
+ ex.getMessage());
}
}
String transactionTableSize = configurationProperties
.getProperty("gov.nist.javax.sip.MAX_SERVER_TRANSACTIONS");
if (transactionTableSize != null) {
try {
this.transactionTableSize = new Integer(transactionTableSize)
.intValue();
} catch (NumberFormatException ex) {
System.out.println("transaction table size - bad value "
+ ex.getMessage());
}
}
super.cacheServerConnections = true;
String flag = configurationProperties
.getProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS");
if (flag != null && "false".equalsIgnoreCase(flag.trim())) {
super.cacheServerConnections = false;
}
super.cacheClientConnections = true;
String cacheflag = configurationProperties
.getProperty("gov.nist.javax.sip.CACHE_CLIENT_CONNECTIONS");
if (cacheflag != null && "false".equalsIgnoreCase(cacheflag.trim())) {
super.cacheClientConnections = false;
}
String readTimeout = configurationProperties
.getProperty("gov.nist.javax.sip.READ_TIMEOUT");
if (readTimeout != null) {
try {
int rt = Integer.parseInt(readTimeout);
if (rt >= 100) {
super.readTimeout = rt;
} else {
System.out.println("Value too low " + readTimeout);
}
} catch (NumberFormatException nfe) {
// Ignore.
System.out.println("Bad read timeout " + readTimeout);
}
}
// Get the address of the stun server.
String stunAddr = configurationProperties
.getProperty("gov.nist.javax.sip.STUN_SERVER");
if ( stunAddr != null )
this.logWriter.logWarning("Ignoring obsolete property "
+ "gov.nist.javax.sip.STUN_SERVER");
String maxMsgSize = configurationProperties
.getProperty("gov.nist.javax.sip.MAX_MESSAGE_SIZE");
try {
if (maxMsgSize != null) {
super.maxMessageSize = new Integer(maxMsgSize).intValue();
if (super.maxMessageSize < 4096)
super.maxMessageSize = 4096;
} else {
// Allow for "infinite" size of message
super.maxMessageSize = 0;
}
} catch (NumberFormatException ex) {
System.out.println("maxMessageSize - bad value " + ex.getMessage());
}
String rel = configurationProperties
.getProperty("gov.nist.javax.sip.REENTRANT_LISTENER");
this.reEntrantListener = (rel != null && "true".equalsIgnoreCase(rel));
// JvB: added property for testing
this.non2XXAckPassedToListener = Boolean
.valueOf(
configurationProperties
.getProperty(
"gov.nist.javax.sip.PASS_INVITE_NON_2XX_ACK_TO_LISTENER",
"false")).booleanValue();
}
/*
* (non-Javadoc)
*
* @see javax.sip.SipStack#createListeningPoint(java.lang.String, int,
* java.lang.String)
*/
public synchronized ListeningPoint createListeningPoint(String address,
int port, String transport) throws TransportNotSupportedException,
InvalidArgumentException {
getLogWriter().logDebug(
"createListeningPoint : address = " + address + " port = " + port
+ " transport = " + transport);
if (address == null)
throw new NullPointerException(
"Address for listening point is null!");
if (transport == null)
throw new NullPointerException("null transport");
if (port <= 0)
throw new InvalidArgumentException("bad port");
if (!transport.equalsIgnoreCase("UDP")
&& !transport.equalsIgnoreCase("TLS") // Added by Daniel J.
// Martinez Manzano
//
&& !transport.equalsIgnoreCase("TCP"))
throw new TransportNotSupportedException("bad transport "
+ transport);
/** Reusing an old stack instance */
if (!this.isAlive()) {
this.toExit = false;
this.reInitialize();
}
String key = ListeningPointImpl.makeKey(address, port, transport);
ListeningPointImpl lip = (ListeningPointImpl) listeningPoints.get(key);
if (lip != null) {
return lip;
} else {
try {
InetAddress inetAddr = InetAddress.getByName(address);
MessageProcessor messageProcessor = this
.createMessageProcessor(inetAddr, port, transport);
if (this.isLoggingEnabled()) {
this.getLogWriter().logDebug(
"Created Message Processor: " + address
+ " port = " + port + " transport = "
+ transport);
}
lip = new ListeningPointImpl(this, port, transport);
lip.messageProcessor = messageProcessor;
messageProcessor.setListeningPoint(lip);
this.listeningPoints.put(key, lip);
// start processing messages.
messageProcessor.start();
return (ListeningPoint) lip;
} catch (java.io.IOException ex) {
getLogWriter().logError(
"Invalid argument address = " + address + " port = "
+ port + " transport = " + transport);
throw new InvalidArgumentException(ex.getMessage());
}
}
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#createSipProvider(javax.sip.ListeningPoint)
*/
public SipProvider createSipProvider(ListeningPoint listeningPoint)
throws ObjectInUseException {
if (listeningPoint == null)
throw new NullPointerException("null listeningPoint");
if (this.getLogWriter().isLoggingEnabled())
this.getLogWriter()
.logDebug("createSipProvider: " + listeningPoint);
ListeningPointImpl listeningPointImpl = (ListeningPointImpl) listeningPoint;
if (listeningPointImpl.sipProvider != null)
throw new ObjectInUseException("Provider already attached!");
SipProviderImpl provider = new SipProviderImpl(this);
provider.setListeningPoint(listeningPointImpl);
listeningPointImpl.sipProvider = provider;
this.sipProviders.add(provider);
return provider;
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#deleteListeningPoint(javax.sip.ListeningPoint)
*/
public void deleteListeningPoint(ListeningPoint listeningPoint)
throws ObjectInUseException {
if (listeningPoint == null)
throw new NullPointerException("null listeningPoint arg");
ListeningPointImpl lip = (ListeningPointImpl) listeningPoint;
// Stop the message processing thread in the listening point.
super.removeMessageProcessor(lip.messageProcessor);
String key = lip.getKey();
this.listeningPoints.remove(key);
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#deleteSipProvider(javax.sip.SipProvider)
*/
public void deleteSipProvider(SipProvider sipProvider)
throws ObjectInUseException {
if (sipProvider == null)
throw new NullPointerException("null provider arg");
SipProviderImpl sipProviderImpl = (SipProviderImpl) sipProvider;
sipProviderImpl.sipListener = null;
sipProviderImpl.removeListeningPoints();
// Bug reported by Rafael Barriuso
sipProviderImpl.stop();
sipProviders.remove(sipProvider);
if (sipProviders.isEmpty()) {
this.stopStack();
}
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#getIPAddress()
*/
public String getIPAddress() {
return super.getHostAddress();
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#getListeningPoints()
*/
public java.util.Iterator getListeningPoints() {
return this.listeningPoints.values().iterator();
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#isRetransmissionFilterActive()
*/
public boolean isRetransmissionFilterActive() {
return false;
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#getSipProviders()
*/
public java.util.Iterator getSipProviders() {
return this.sipProviders.iterator();
}
/*
* (non-Javadoc)
* @see javax.sip.SipStack#getStackName()
*/
public String getStackName() {
return this.stackName;
}
/**
* The default transport to use for via headers.
protected String getDefaultTransport() {
if (isTransportEnabled("udp"))
return "udp";
else if (isTransportEnabled("tcp"))
return "tcp";
else if (isTransportEnabled("tls")) // Added by Daniel J. Martinez
// Manzano
return "tls";
else
return null;
}
*/
/**
* Finalization -- stop the stack on finalization. Exit the transaction
* scanner and release all resources.
*/
public void finalize() {
this.stopStack();
}
/*
* This uses the default stack address. (non-Javadoc)
*
* @see javax.sip.SipStack#createListeningPoint(java.lang.String, int,
* java.lang.String)
*/
public ListeningPoint createListeningPoint(int port, String transport)
throws TransportNotSupportedException, InvalidArgumentException {
if (super.stackAddress == null)
throw new NullPointerException(
"Stack does not have a default IP Address!");
return this.createListeningPoint(super.stackAddress, port, transport);
}
/*
* (non-Javadoc)
*
* @see javax.sip.SipStack#stop()
*/
public void stop() {
if (getLogWriter().isLoggingEnabled()) {
getLogWriter().logDebug("stopStack -- stoppping the stack");
}
this.stopStack();
}
/*
* (non-Javadoc)
*
* @see javax.sip.SipStack#start()
*/
public void start() throws ProviderDoesNotExistException, SipException {
if (this.sipProviders.isEmpty())
throw new ProviderDoesNotExistException("No providers specified");
}
/**
* 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 listener for the stack. A stack can have only one listener. To get
* an event from a provider, the listener has to be registered with the
* provider. The SipListener is application code.
*
* @return -- the stack SipListener
*
*/
public SipListener getSipListener() {
return this.sipListener;
}
}