You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1497 lines
56 KiB
1497 lines
56 KiB
/*
|
|
* 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;
|
|
|
|
import gov.nist.core.ServerLogger;
|
|
import gov.nist.core.StackLogger;
|
|
import gov.nist.core.net.AddressResolver;
|
|
import gov.nist.core.net.NetworkLayer;
|
|
import gov.nist.core.net.SslNetworkLayer;
|
|
import gov.nist.javax.sip.clientauthutils.AccountManager;
|
|
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
|
|
import gov.nist.javax.sip.clientauthutils.AuthenticationHelperImpl;
|
|
import gov.nist.javax.sip.clientauthutils.SecureAccountManager;
|
|
import gov.nist.javax.sip.parser.StringMsgParser;
|
|
import gov.nist.javax.sip.stack.DefaultMessageLogFactory;
|
|
import gov.nist.javax.sip.stack.DefaultRouter;
|
|
import gov.nist.javax.sip.stack.MessageProcessor;
|
|
import gov.nist.javax.sip.stack.SIPTransactionStack;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.net.InetAddress;
|
|
import java.util.Hashtable;
|
|
import java.util.LinkedList;
|
|
import java.util.Properties;
|
|
import java.util.StringTokenizer;
|
|
import java.util.concurrent.Semaphore;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import javax.sip.InvalidArgumentException;
|
|
import javax.sip.ListeningPoint;
|
|
import javax.sip.ObjectInUseException;
|
|
import javax.sip.PeerUnavailableException;
|
|
import javax.sip.ProviderDoesNotExistException;
|
|
import javax.sip.SipException;
|
|
import javax.sip.SipListener;
|
|
import javax.sip.SipProvider;
|
|
import javax.sip.SipStack;
|
|
import javax.sip.TransportNotSupportedException;
|
|
import javax.sip.address.Router;
|
|
import javax.sip.header.HeaderFactory;
|
|
import javax.sip.message.Request;
|
|
|
|
/**
|
|
* 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):
|
|
* <ul>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.TRACE_LEVEL = integer </b><br/>
|
|
* <b> Use of this property is still supported but deprecated. Please use
|
|
* gov.nist.javax.sip.STACK_LOGGER and gov.nist.javax.sip.SERVER_LOGGER for
|
|
* integration with logging frameworks and for custom formatting of log records.
|
|
* </b> This property is used by the built in log4j based logger. You can use
|
|
* the standard log4j level names here (i.e. ERROR, INFO, WARNING, OFF, DEBUG,
|
|
* TRACE) If this is set to INFO 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. <a href="{@docRoot}
|
|
* /tools/tracesviewer/tracesviewer.html"> This can be viewed using the trace
|
|
* viewer tool .</a> Please send us both the server log and debug log when
|
|
* reporting non-obvious problems. You can also use the strings DEBUG or INFO
|
|
* for level 32 and 16 respectively. If the value of this property is set to
|
|
* LOG4J, then the effective log levels are determined from the log4j settings
|
|
* file (e.g. log4j.properties). The logger name for the stack is specified
|
|
* using the gov.nist.javax.sip.LOG4J_LOGGER_NAME property. By default log4j
|
|
* logger name for the stack is the same as the stack name. For example, <code>
|
|
* properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "LOG4J");
|
|
* properties.setProperty("gov.nist.javax.sip.LOG4J_LOGGER_NAME", "SIPStackLogger");
|
|
* </code> allows you to now control logging in the stack entirely using log4j
|
|
* facilities.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.LOG_FACTORY = classpath </b> <b> Use of this
|
|
* property is still supported but deprecated. Please use
|
|
* gov.nist.javax.sip.STACK_LOGGER and gov.nist.javax.sip.SERVER_LOGGER for
|
|
* integration with logging frameworks and for custom formatting of log records.
|
|
* </b> <br/>
|
|
* The fully qualified classpath for an implementation of the MessageLogFactory.
|
|
* The stack calls the MessageLogFactory functions to format the log for
|
|
* messages that are received or sent. This function allows you to log auxiliary
|
|
* information related to the application or environmental conditions into the
|
|
* log stream. The log factory must have a default constructor.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.SERVER_LOG = fileName </b><br/>
|
|
* <b> Use of this property is still supported but deprecated. Please use
|
|
* gov.nist.javax.sip.STACK_LOGGER and gov.nist.javax.sip.SERVER_LOGGER for
|
|
* integration with logging frameworks and for custom formatting of log records.
|
|
* </b> Log valid incoming messages here. If this is left null AND the
|
|
* TRACE_LEVEL is above INFO (or TRACE) 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. <font color=red> Mail this to us with bug reports. </font></li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.DEBUG_LOG = fileName </b> <b> Use of this property
|
|
* is still supported but deprecated. Please use gov.nist.javax.sip.STACK_LOGGER
|
|
* and gov.nist.javax.sip.SERVER_LOGGER for integration with logging frameworks
|
|
* and for custom formatting of log records. </b> <br/>
|
|
* Where the debug log goes. <font color=red> Mail this to us with bug reports.
|
|
* </font></li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.LOG_MESSAGE_CONTENT = true|false </b><br/>
|
|
* 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.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.LOG_STACK_TRACE_ON_MESSAGE_SEND = true|false </b><br/>
|
|
* Set true if you want to to log a stack trace at INFO level for each message
|
|
* send. This is really handy for debugging.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.STACK_LOGGER = full path name to the class
|
|
* implementing gov.nist.core.StackLogger interface</b><br/>
|
|
* If this property is defined the sip stack will try to instantiate it through
|
|
* a no arg constructor. This allows to use different logging implementations
|
|
* than the ones provided by default to log what happens within the stack while
|
|
* processing SIP messages. If this property is not defined, the default sip
|
|
* stack LogWriter will be used for logging</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.SERVER_LOGGER = full path name to the class
|
|
* implementing gov.nist.core.ServerLogger interface</b><br/>
|
|
* If this property is defined the sip stack will try to instantiate it through
|
|
* a no arg constructor. This allows to use different logging implementations
|
|
* than the ones provided by default to log sent/received messages by the sip
|
|
* stack. If this property is not defined, the default sip stack ServerLog will
|
|
* be used for logging</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.AUTOMATIC_DIALOG_ERROR_HANDLING = [true|false] </b>
|
|
* <br/>
|
|
* Default is <it>true</it>. This is also settable on a per-provider basis. This
|
|
* flag is set to true by default. When set
|
|
* to <it>false</it> the following behaviors are enabled:
|
|
*
|
|
*
|
|
* <li>Turn off Merged requests Loop Detection: The following behavior is turned off
|
|
* : 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.
|
|
*
|
|
* </ul>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.IS_BACK_TO_BACK_USER_AGENT = [true|false] </b> <br/>
|
|
* Default is <it>false</it> This property controls a setting on the Dialog
|
|
* objects that the stack manages. Pure B2BUA applications should set this flag
|
|
* to <it>true</it>. This property can also be set on a per-dialog basis.
|
|
* Setting this to <it>true</it> imposes serialization on re-INVITE and makes
|
|
* the sending of re-INVITEs asynchronous. The sending of re-INVITE is
|
|
* controlled as follows : If the previous in-DIALOG request was an invite
|
|
* ClientTransaction then the next re-INVITEs that uses the dialog will wait
|
|
* till an ACK has been sent before admitting the new re-INVITE. If the previous
|
|
* in-DIALOG transaction was a INVITE ServerTransaction then Dialog waits for
|
|
* ACK before re-INVITE is allowed to be sent. If a dialog is not ACKed within
|
|
* 32 seconds, then the dialog is torn down and a BYE sent to the peer.</li>
|
|
* <li><b>gov.nist.javax.sip.MAX_MESSAGE_SIZE = integer</b> <br/>
|
|
* 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.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG = [true|false] </b><br/>
|
|
* If set to false (the default), the application does NOT get notified when a Dialog in the
|
|
* NULL state is terminated. ( Dialogs in the NULL state are not associated with an actual SIP Dialog.
|
|
* They are a programming convenience. A Dialog is in the NULL state before the first response for the
|
|
* Dialog forming Transaction). If set to true, the SipListener will get a DialogTerminatedEvent
|
|
* when a Dialog in the NULL state is terminated.
|
|
* </li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS = [true|false] </b> <br/>
|
|
* Default value is true. Setting this to false 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 transactions). If true (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.</li>
|
|
*
|
|
*
|
|
* <li><b>gov.nist.javax.sip.CACHE_CLIENT_CONNECTIONS = [true|false] </b> <br/>
|
|
* Default value is true. Setting this to false makes the Stack close the server
|
|
* socket after a 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.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.THREAD_POOL_SIZE = integer </b> <br/>
|
|
* 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.
|
|
* <ul>
|
|
* <li>
|
|
* <li>If this is not specified, <b> and the listener is re-entrant</b>, each
|
|
* event delivered to the listener is run in the context of a new thread.</li>
|
|
* <li>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.</li>
|
|
* <li>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.</li>
|
|
* </ul>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.REENTRANT_LISTENER = true|false </b> <br/>
|
|
* 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. <b> If you want good performance on a
|
|
* multithreaded machine write your listener to be re-entrant and set this
|
|
* property to be true </b></li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.MAX_CONNECTIONS = integer </b> <br/>
|
|
* Max number of simultaneous TCP connections handled by stack.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.MAX_SERVER_TRANSACTIONS = integer </b> <br/>
|
|
* Maximum size of server transaction table. The low water mark is 80% of the
|
|
* high water mark. Requests are selectively dropped in the lowater mark to
|
|
* highwater mark range. Requests are unconditionally accepted if the table is
|
|
* smaller than the low water mark. The default highwater mark is 5000</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.MAX_CLIENT_TRANSACTIONS = integer </b> <br/>
|
|
* Max number of active client transactions before the caller blocks and waits
|
|
* for the number to drop below a threshold. Default is unlimited, i.e. the
|
|
* caller never blocks and waits for a client transaction to become available
|
|
* (i.e. it does its own resource management in the application).</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.PASS_INVITE_NON_2XX_ACK_TO_LISTENER = true|false
|
|
* </b> <br/>
|
|
* 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.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.MAX_LISTENER_RESPONSE_TIME = Integer </b> <br/>
|
|
* Max time (seconds) before sending a response to a server transaction. If a
|
|
* response is not sent within this time period, the transaction will be deleted
|
|
* by the stack. Default time is "infinity" - i.e. if the listener never
|
|
* responds, the stack will hang on to a reference for the transaction and
|
|
* result in a memory leak.
|
|
*
|
|
* <li><b>gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_ACK = [true|false]</b>
|
|
* <br/>
|
|
* Default is <it>false</it>. ACK Server Transaction is a Pseuedo-transaction.
|
|
* If you want termination notification on ACK transactions (so all server
|
|
* transactions can be handled uniformly in user code during cleanup), then set
|
|
* this flag to <it>true</it>.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.READ_TIMEOUT = integer </b> <br/>
|
|
* 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.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.NETWORK_LAYER = classpath </b> <br/>
|
|
* 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).</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.ADDRESS_RESOLVER = classpath </b><br/>
|
|
* The fully qualified class path for an implementation of the AddressResolver
|
|
* interface. The AddressResolver allows you to support lookup schemes for
|
|
* addresses that are not directly resolvable to IP adresses using
|
|
* getHostByName. Specifying your own address resolver allows you to customize
|
|
* address lookup. The default address resolver is a pass-through address
|
|
* resolver (i.e. just returns the input string without doing a resolution). See
|
|
* gov.nist.javax.sip.DefaultAddressResolver.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.AUTO_GENERATE_TIMESTAMP= [true| false] </b><br/>
|
|
* (default is false) Automatically generate a getTimeOfDay timestamp for a
|
|
* retransmitted request if the original request contained a timestamp. This is
|
|
* useful for profiling.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS = long </b> <br/>
|
|
* Defines how often the application intends to audit the SIP Stack about the
|
|
* health of its internal threads (the property specifies the time in
|
|
* miliseconds between successive audits). The audit allows the application to
|
|
* detect catastrophic failures like an internal thread terminating because of
|
|
* an exception or getting stuck in a deadlock condition. Events like these will
|
|
* make the stack inoperable and therefore require immediate action from the
|
|
* application layer (e.g., alarms, traps, reboot, failover, etc.) Thread audits
|
|
* are disabled by default. If this property is not specified, audits will
|
|
* remain disabled. An example of how to use this property is in
|
|
* src/examples/threadaudit.</li>
|
|
*
|
|
*
|
|
*
|
|
* <li><b>gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY =
|
|
* [true|false] </b> <br/>
|
|
* Default is <it>false</it> If set to <it>true</it>, when you are creating a
|
|
* message from a <it>String</it>, the MessageFactory will compute the content
|
|
* length from the message content and ignore the provided content length
|
|
* parameter in the Message. Otherwise, it will use the content length supplied
|
|
* and generate a parse exception if the content is truncated.
|
|
*
|
|
* <li><b>gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED = [true|false]
|
|
* </b> <br/>
|
|
* Default is <it>true</it>. This flag is added in support of load balancers or
|
|
* failover managers where you may want to cancel ongoing transactions from a
|
|
* different stack than the original stack. If set to <it>false</it> then the
|
|
* CANCEL client transaction is not checked for the existence of the INVITE or
|
|
* the state of INVITE when you send the CANCEL request. Hence you can CANCEL an
|
|
* INVITE from a different stack than the INVITE. You can also create a CANCEL
|
|
* client transaction late and send it out after the INVITE server transaction
|
|
* has been Terminated. Clearly this will result in protocol errors. Setting the
|
|
* flag to true ( default ) enables you to avoid common protocol errors.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.IS_BACK_TO_BACK_USER_AGENT = [true|false] </b> <br/>
|
|
* Default is <it>false</it> This property controls a setting on the Dialog
|
|
* objects that the stack manages. Pure B2BUA applications should set this flag
|
|
* to <it>true</it>. This property can also be set on a per-dialog basis.
|
|
* Setting this to <it>true</it> imposes serialization on re-INVITE and makes
|
|
* the sending of re-INVITEs asynchronous. The sending of re-INVITE is
|
|
* controlled as follows : If the previous in-DIALOG request was an invite
|
|
* ClientTransaction then the next re-INVITEs that uses the dialog will wait
|
|
* till an ACK has been sent before admitting the new re-INVITE. If the previous
|
|
* in-DIALOG transaction was a INVITE ServerTransaction then Dialog waits for
|
|
* ACK before re-INVITE is allowed to be sent. If a dialog is not ACKed within
|
|
* 32 seconds, then the dialog is torn down and a BYE sent to the peer.</li>
|
|
*
|
|
*
|
|
* <li><b>gov.nist.javax.sip.RECEIVE_UDP_BUFFER_SIZE = int </b> <br/>
|
|
* Default is <it>8*1024</it>. This property control the size of the UDP buffer
|
|
* used for SIP messages. Under load, if the buffer capacity is overflown the
|
|
* messages are dropped causing retransmissions, further increasing the load and
|
|
* causing even more retransmissions. Good values to this property for servers
|
|
* is a big number in the order of 8*8*1024.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.SEND_UDP_BUFFER_SIZE = int </b> <br/>
|
|
* Default is <it>8*1024</it>. This property control the size of the UDP buffer
|
|
* used for SIP messages. Under load, if the buffer capacity is overflown the
|
|
* messages are dropped causing retransmissions, further increasing the load and
|
|
* causing even more retransmissions. Good values to this property for servers
|
|
* is a big number in the order of 8*8*1024 or higher.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.CONGESTION_CONTROL_ENABLED = boolean </b> Defailt
|
|
* is true. If set to true stack will enforce queue length limitation for UDP.
|
|
* The Max queue size is 5000 messages. The minimum queue size is 2500 messages.
|
|
* </li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY = [true|false] </b> <br/>
|
|
* Default is <it>false</it>. This flag is added to allow Sip Listeners to
|
|
* receive all NOTIFY requests including those that are not part of a valid
|
|
* dialog.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.REJECT_STRAY_RESPONSES = [true|false] </b> Default
|
|
* is <it>false</it> A flag that checks responses to test whether the response
|
|
* corresponds to a via header that was previously generated by us. Note that
|
|
* setting this flag implies that the stack will take control over setting the
|
|
* VIA header for Sip Requests sent through the stack. The stack will attach a
|
|
* suffix to the VIA header branch and check any response arriving at the stack
|
|
* to see if that response suffix is present. If it is not present, then the
|
|
* stack will silently drop the response.</li>
|
|
*
|
|
* <li><b>gov.nist.javax.sip.MAX_FORK_TIME_SECONDS = integer </b> Maximum time for which the original
|
|
* transaction for which a forked response is received is tracked. This property
|
|
* is only relevant to Dialog Stateful applications ( User Agents or B2BUA).
|
|
* When a forked response is received in this time interval from when the original
|
|
* INVITE client transaction was sent, the stack will place the original INVITE
|
|
* client transction in the ResponseEventExt and deliver that to the application.
|
|
* The event handler can get the original transaction from this event. </li>
|
|
*
|
|
* * <li><b>gov.nist.javax.sip.TLS_CLIENT_PROTOCOLS = String </b>
|
|
* Comma-separated list of protocols to use when creating outgoing TLS connections.
|
|
* The default is "SSLv3, SSLv2Hello, TLSv1".
|
|
* Some servers do not support SSLv2Hello, so override to "SSLv3, TLSv1".
|
|
* </li>
|
|
|
|
* <li><b>javax.net.ssl.keyStore = fileName </b> <br/>
|
|
* Default is <it>NULL</it>. If left undefined the keyStore and trustStore will
|
|
* be left to the java runtime defaults. If defined, any TLS sockets created
|
|
* (client and server) will use the key store provided in the fileName. The
|
|
* trust store will default to the same store file. A password must be provided
|
|
* to access the keyStore using the following property: <br>
|
|
* <code>
|
|
* properties.setProperty("javax.net.ssl.keyStorePassword", "<password>");
|
|
* </code> <br>
|
|
* The trust store can be changed, to a separate file with the following
|
|
* setting: <br>
|
|
* <code>
|
|
* properties.setProperty("javax.net.ssl.trustStore", "<trustStoreFileName location>");
|
|
* </code> <br>
|
|
* If the trust store property is provided the password on the trust store must
|
|
* be the same as the key store. <br>
|
|
* <br>
|
|
* <b> Note that the stack supports the extensions that are defined in
|
|
* SipStackExt. These will be supported in the next release of JAIN-SIP. You
|
|
* should only use the extensions that are defined in this class. </b>
|
|
*
|
|
*
|
|
* @version 1.2 $Revision: 1.115 $ $Date: 2010/01/10 00:13:14 $
|
|
*
|
|
* @author M. Ranganathan <br/>
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
public class SipStackImpl extends SIPTransactionStack implements
|
|
javax.sip.SipStack, SipStackExt {
|
|
|
|
private EventScanner eventScanner;
|
|
|
|
private Hashtable<String, ListeningPointImpl> listeningPoints;
|
|
|
|
private LinkedList<SipProviderImpl> sipProviders;
|
|
|
|
/**
|
|
* Max datagram size.
|
|
*/
|
|
public static final Integer MAX_DATAGRAM_SIZE = 8 * 1024;
|
|
|
|
// Flag to indicate that the listener is re-entrant and hence
|
|
// Use this flag with caution.
|
|
boolean reEntrantListener;
|
|
|
|
SipListener sipListener;
|
|
|
|
// If set to true then a transaction terminated event is
|
|
// delivered for ACK transactions.
|
|
boolean deliverTerminatedEventForAck = false;
|
|
|
|
// If set to true then the application want to receive
|
|
// unsolicited NOTIFYs, ie NOTIFYs that don't match any dialog
|
|
boolean deliverUnsolicitedNotify = false;
|
|
|
|
// Stack semaphore (global lock).
|
|
private Semaphore stackSemaphore = new Semaphore(1);
|
|
|
|
// RFC3261: TLS_RSA_WITH_AES_128_CBC_SHA MUST be supported
|
|
// RFC3261: TLS_RSA_WITH_3DES_EDE_CBC_SHA SHOULD be supported for backwards
|
|
// compat
|
|
private String[] cipherSuites = {
|
|
"TLS_RSA_WITH_AES_128_CBC_SHA", // AES difficult to get with
|
|
// c++/Windows
|
|
// "TLS_RSA_WITH_3DES_EDE_CBC_SHA", // Unsupported by Sun impl,
|
|
"SSL_RSA_WITH_3DES_EDE_CBC_SHA", // For backwards comp., C++
|
|
|
|
// JvB: patch from Sebastien Mazy, issue with mismatching
|
|
// ciphersuites
|
|
"TLS_DH_anon_WITH_AES_128_CBC_SHA",
|
|
"SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", };
|
|
|
|
// Supported protocols for TLS client: can be overridden by application
|
|
private String[] enabledProtocols = {
|
|
"SSLv3",
|
|
"SSLv2Hello",
|
|
"TLSv1"
|
|
};
|
|
|
|
/**
|
|
* 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<String, ListeningPointImpl>();
|
|
this.sipProviders = new LinkedList<SipProviderImpl>();
|
|
|
|
}
|
|
|
|
/**
|
|
* ReInitialize the stack instance.
|
|
*/
|
|
private void reInitialize() {
|
|
super.reInit();
|
|
this.eventScanner = new EventScanner(this);
|
|
this.listeningPoints = new Hashtable<String, ListeningPointImpl>();
|
|
this.sipProviders = new LinkedList<SipProviderImpl>();
|
|
this.sipListener = null;
|
|
|
|
}
|
|
|
|
/**
|
|
* Return true if automatic dialog support is enabled for this stack.
|
|
*
|
|
* @return boolean, true if automatic dialog support is enabled for this
|
|
* stack
|
|
*/
|
|
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);
|
|
String stackLoggerClassName = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.STACK_LOGGER");
|
|
// To log debug messages.
|
|
if (stackLoggerClassName == null)
|
|
stackLoggerClassName = "gov.nist.core.LogWriter";
|
|
try {
|
|
Class<?> stackLoggerClass = Class.forName(stackLoggerClassName);
|
|
Class<?>[] constructorArgs = new Class[0];
|
|
Constructor<?> cons = stackLoggerClass
|
|
.getConstructor(constructorArgs);
|
|
Object[] args = new Object[0];
|
|
StackLogger stackLogger = (StackLogger) cons.newInstance(args);
|
|
stackLogger.setStackProperties(configurationProperties);
|
|
super.setStackLogger(stackLogger);
|
|
} catch (InvocationTargetException ex1) {
|
|
throw new IllegalArgumentException(
|
|
"Cound not instantiate stack logger "
|
|
+ stackLoggerClassName
|
|
+ "- check that it is present on the classpath and that there is a no-args constructor defined",
|
|
ex1);
|
|
} catch (Exception ex) {
|
|
throw new IllegalArgumentException(
|
|
"Cound not instantiate stack logger "
|
|
+ stackLoggerClassName
|
|
+ "- check that it is present on the classpath and that there is a no-args constructor defined",
|
|
ex);
|
|
}
|
|
|
|
String serverLoggerClassName = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.SERVER_LOGGER");
|
|
// To log debug messages.
|
|
if (serverLoggerClassName == null)
|
|
serverLoggerClassName = "gov.nist.javax.sip.stack.ServerLog";
|
|
try {
|
|
Class<?> serverLoggerClass = Class
|
|
.forName(serverLoggerClassName);
|
|
Class<?>[] constructorArgs = new Class[0];
|
|
Constructor<?> cons = serverLoggerClass
|
|
.getConstructor(constructorArgs);
|
|
Object[] args = new Object[0];
|
|
this.serverLogger = (ServerLogger) cons.newInstance(args);
|
|
serverLogger.setSipStack(this);
|
|
serverLogger.setStackProperties(configurationProperties);
|
|
} catch (InvocationTargetException ex1) {
|
|
throw new IllegalArgumentException(
|
|
"Cound not instantiate server logger "
|
|
+ stackLoggerClassName
|
|
+ "- check that it is present on the classpath and that there is a no-args constructor defined",
|
|
ex1);
|
|
} catch (Exception ex) {
|
|
throw new IllegalArgumentException(
|
|
"Cound not instantiate server logger "
|
|
+ stackLoggerClassName
|
|
+ "- check that it is present on the classpath and that there is a no-args constructor defined",
|
|
ex);
|
|
}
|
|
|
|
// Default router -- use this for routing SIP URIs.
|
|
// Our router does not do DNS lookups.
|
|
this.outboundProxy = configurationProperties
|
|
.getProperty("javax.sip.OUTBOUND_PROXY");
|
|
|
|
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";
|
|
|
|
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) {
|
|
getStackLogger()
|
|
.logError(
|
|
"could not instantiate router -- invocation target problem",
|
|
(Exception) ex1.getCause());
|
|
throw new PeerUnavailableException(
|
|
"Cound not instantiate router - check constructor", ex1);
|
|
} catch (Exception ex) {
|
|
getStackLogger().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);
|
|
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);
|
|
}
|
|
}
|
|
String keyStoreFile = configurationProperties
|
|
.getProperty("javax.net.ssl.keyStore");
|
|
String trustStoreFile = configurationProperties
|
|
.getProperty("javax.net.ssl.trustStore");
|
|
if (keyStoreFile != null) {
|
|
if (trustStoreFile == null) {
|
|
trustStoreFile = keyStoreFile;
|
|
}
|
|
String keyStorePassword = configurationProperties
|
|
.getProperty("javax.net.ssl.keyStorePassword");
|
|
try {
|
|
this.networkLayer = new SslNetworkLayer(trustStoreFile,
|
|
keyStoreFile, keyStorePassword.toCharArray(),
|
|
configurationProperties
|
|
.getProperty("javax.net.ssl.keyStoreType"));
|
|
} catch (Exception e1) {
|
|
getStackLogger().logError(
|
|
"could not instantiate SSL networking", e1);
|
|
}
|
|
}
|
|
|
|
// Set the auto dialog support flag.
|
|
super.isAutomaticDialogSupportEnabled = configurationProperties
|
|
.getProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT", "on")
|
|
.equalsIgnoreCase("on");
|
|
|
|
super.isAutomaticDialogErrorHandlingEnabled = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.AUTOMATIC_DIALOG_ERROR_HANDLING","true")
|
|
.equals(Boolean.TRUE.toString());
|
|
if ( super.isAutomaticDialogSupportEnabled ) {
|
|
super.isAutomaticDialogErrorHandlingEnabled = true;
|
|
}
|
|
|
|
if (configurationProperties
|
|
.getProperty("gov.nist.javax.sip.MAX_LISTENER_RESPONSE_TIME") != null) {
|
|
super.maxListenerResponseTime = Integer
|
|
.parseInt(configurationProperties
|
|
.getProperty("gov.nist.javax.sip.MAX_LISTENER_RESPONSE_TIME"));
|
|
if (super.maxListenerResponseTime <= 0)
|
|
throw new PeerUnavailableException(
|
|
"Bad configuration parameter gov.nist.javax.sip.MAX_LISTENER_RESPONSE_TIME : should be positive");
|
|
} else {
|
|
super.maxListenerResponseTime = -1;
|
|
}
|
|
|
|
|
|
|
|
this.deliverTerminatedEventForAck = configurationProperties
|
|
.getProperty(
|
|
"gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_ACK",
|
|
"false").equalsIgnoreCase("true");
|
|
|
|
this.deliverUnsolicitedNotify = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "false")
|
|
.equalsIgnoreCase("true");
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
final String ADDRESS_RESOLVER_KEY = "gov.nist.javax.sip.ADDRESS_RESOLVER";
|
|
|
|
if (configurationProperties.containsKey(ADDRESS_RESOLVER_KEY)) {
|
|
String path = configurationProperties
|
|
.getProperty(ADDRESS_RESOLVER_KEY);
|
|
try {
|
|
Class<?> clazz = Class.forName(path);
|
|
Constructor<?> c = clazz.getConstructor(new Class[0]);
|
|
this.addressResolver = (AddressResolver) c
|
|
.newInstance(new Object[0]);
|
|
} catch (Exception e) {
|
|
throw new PeerUnavailableException(
|
|
"can't find or instantiate AddressResolver 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) {
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logError(
|
|
"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) {
|
|
if (isLoggingEnabled())
|
|
this.getStackLogger().logError(
|
|
"thread pool size - bad value " + ex.getMessage());
|
|
}
|
|
}
|
|
|
|
String serverTransactionTableSize = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.MAX_SERVER_TRANSACTIONS");
|
|
if (serverTransactionTableSize != null) {
|
|
try {
|
|
this.serverTransactionTableHighwaterMark = new Integer(
|
|
serverTransactionTableSize).intValue();
|
|
this.serverTransactionTableLowaterMark = this.serverTransactionTableHighwaterMark * 80 / 100;
|
|
// Lowater is 80% of highwater
|
|
} catch (NumberFormatException ex) {
|
|
if (isLoggingEnabled())
|
|
this.getStackLogger()
|
|
.logError(
|
|
"transaction table size - bad value "
|
|
+ ex.getMessage());
|
|
}
|
|
} else {
|
|
// Issue 256 : consistent with MAX_CLIENT_TRANSACTIONS, if the MAX_SERVER_TRANSACTIONS is not set
|
|
// we assume the transaction table size can grow unlimited
|
|
this.unlimitedServerTransactionTableSize = true;
|
|
}
|
|
|
|
String clientTransactionTableSize = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.MAX_CLIENT_TRANSACTIONS");
|
|
if (clientTransactionTableSize != null) {
|
|
try {
|
|
this.clientTransactionTableHiwaterMark = new Integer(
|
|
clientTransactionTableSize).intValue();
|
|
this.clientTransactionTableLowaterMark = this.clientTransactionTableLowaterMark * 80 / 100;
|
|
// Lowater is 80% of highwater
|
|
} catch (NumberFormatException ex) {
|
|
if (isLoggingEnabled())
|
|
this.getStackLogger()
|
|
.logError(
|
|
"transaction table size - bad value "
|
|
+ ex.getMessage());
|
|
}
|
|
} else {
|
|
this.unlimitedClientTransactionTableSize = true;
|
|
}
|
|
|
|
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.err.println("Value too low " + readTimeout);
|
|
}
|
|
} catch (NumberFormatException nfe) {
|
|
// Ignore.
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logError("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.getStackLogger().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) {
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logError(
|
|
"maxMessageSize - bad value " + ex.getMessage());
|
|
}
|
|
|
|
String rel = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.REENTRANT_LISTENER");
|
|
this.reEntrantListener = (rel != null && "true".equalsIgnoreCase(rel));
|
|
|
|
// Check if a thread audit interval is specified
|
|
String interval = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS");
|
|
if (interval != null) {
|
|
try {
|
|
// Make the monitored threads ping the auditor twice as fast as
|
|
// the audits
|
|
getThreadAuditor().setPingIntervalInMillisecs(
|
|
Long.valueOf(interval).longValue() / 2);
|
|
} catch (NumberFormatException ex) {
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logError(
|
|
"THREAD_AUDIT_INTERVAL_IN_MILLISECS - bad value ["
|
|
+ interval + "] " + ex.getMessage());
|
|
}
|
|
}
|
|
|
|
// JvB: added property for testing
|
|
this
|
|
.setNon2XXAckPassedToListener(Boolean
|
|
.valueOf(
|
|
configurationProperties
|
|
.getProperty(
|
|
"gov.nist.javax.sip.PASS_INVITE_NON_2XX_ACK_TO_LISTENER",
|
|
"false")).booleanValue());
|
|
|
|
this.generateTimeStampHeader = Boolean.valueOf(
|
|
configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.AUTO_GENERATE_TIMESTAMP", "false"))
|
|
.booleanValue();
|
|
|
|
String messageLogFactoryClasspath = configurationProperties
|
|
.getProperty("gov.nist.javax.sip.LOG_FACTORY");
|
|
if (messageLogFactoryClasspath != null) {
|
|
try {
|
|
Class<?> clazz = Class.forName(messageLogFactoryClasspath);
|
|
Constructor<?> c = clazz.getConstructor(new Class[0]);
|
|
this.logRecordFactory = (LogRecordFactory) c
|
|
.newInstance(new Object[0]);
|
|
} catch (Exception ex) {
|
|
if (isLoggingEnabled())
|
|
getStackLogger()
|
|
.logError(
|
|
"Bad configuration value for LOG_FACTORY -- using default logger");
|
|
this.logRecordFactory = new DefaultMessageLogFactory();
|
|
}
|
|
|
|
} else {
|
|
this.logRecordFactory = new DefaultMessageLogFactory();
|
|
}
|
|
|
|
boolean computeContentLength = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY",
|
|
"false").equalsIgnoreCase("true");
|
|
StringMsgParser
|
|
.setComputeContentLengthFromMessage(computeContentLength);
|
|
|
|
String tlsClientProtocols = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.TLS_CLIENT_PROTOCOLS");
|
|
if (tlsClientProtocols != null)
|
|
{
|
|
StringTokenizer st = new StringTokenizer(tlsClientProtocols, " ,");
|
|
String[] protocols = new String[st.countTokens()];
|
|
|
|
int i=0;
|
|
while (st.hasMoreTokens()) {
|
|
protocols[i++] = st.nextToken();
|
|
}
|
|
this.enabledProtocols = protocols;
|
|
}
|
|
|
|
super.rfc2543Supported = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.RFC_2543_SUPPORT_ENABLED", "true")
|
|
.equalsIgnoreCase("true");
|
|
|
|
super.cancelClientTransactionChecked = configurationProperties
|
|
.getProperty(
|
|
"gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED",
|
|
"true").equalsIgnoreCase("true");
|
|
super.logStackTraceOnMessageSend = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.LOG_STACK_TRACE_ON_MESSAGE_SEND", "false")
|
|
.equalsIgnoreCase("true");
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logDebug(
|
|
"created Sip stack. Properties = " + configurationProperties);
|
|
InputStream in = getClass().getResourceAsStream("/TIMESTAMP");
|
|
if (in != null) {
|
|
BufferedReader streamReader = new BufferedReader(
|
|
new InputStreamReader(in));
|
|
|
|
try {
|
|
String buildTimeStamp = streamReader.readLine();
|
|
if (in != null) {
|
|
in.close();
|
|
}
|
|
getStackLogger().setBuildTimeStamp(buildTimeStamp);
|
|
} catch (IOException ex) {
|
|
getStackLogger().logError("Could not open build timestamp.");
|
|
}
|
|
}
|
|
|
|
String bufferSize = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.RECEIVE_UDP_BUFFER_SIZE", MAX_DATAGRAM_SIZE
|
|
.toString());
|
|
int bufferSizeInteger = new Integer(bufferSize).intValue();
|
|
super.setReceiveUdpBufferSize(bufferSizeInteger);
|
|
|
|
bufferSize = configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.SEND_UDP_BUFFER_SIZE", MAX_DATAGRAM_SIZE
|
|
.toString());
|
|
bufferSizeInteger = new Integer(bufferSize).intValue();
|
|
super.setSendUdpBufferSize(bufferSizeInteger);
|
|
|
|
boolean congetstionControlEnabled = Boolean
|
|
.parseBoolean(configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.CONGESTION_CONTROL_ENABLED",
|
|
Boolean.TRUE.toString()));
|
|
super.stackDoesCongestionControl = congetstionControlEnabled;
|
|
|
|
super.isBackToBackUserAgent = Boolean
|
|
.parseBoolean(configurationProperties.getProperty(
|
|
"gov.nist.javax.sip.IS_BACK_TO_BACK_USER_AGENT",
|
|
Boolean.FALSE.toString()));
|
|
super.checkBranchId = Boolean.parseBoolean(configurationProperties
|
|
.getProperty("gov.nist.javax.sip.REJECT_STRAY_RESPONSES",
|
|
Boolean.FALSE.toString()));
|
|
|
|
super.isDialogTerminatedEventDeliveredForNullDialog = (Boolean.parseBoolean(configurationProperties.getProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG",
|
|
Boolean.FALSE.toString())));
|
|
|
|
|
|
super.maxForkTime = Integer.parseInt(
|
|
configurationProperties.getProperty("gov.nist.javax.sip.MAX_FORK_TIME_SECONDS","0"));
|
|
|
|
}
|
|
|
|
/*
|
|
* (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 {
|
|
if (isLoggingEnabled())
|
|
getStackLogger().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")
|
|
&& !transport.equalsIgnoreCase("TCP")
|
|
&& !transport.equalsIgnoreCase("SCTP"))
|
|
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.getStackLogger().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) {
|
|
if (isLoggingEnabled())
|
|
getStackLogger().logError(
|
|
"Invalid argument address = " + address + " port = "
|
|
+ port + " transport = " + transport);
|
|
throw new InvalidArgumentException(ex.getMessage(), ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* (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.isLoggingEnabled())
|
|
this.getStackLogger().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;
|
|
|
|
// JvB: API doc is not clear, but in_use ==
|
|
// sipProviderImpl.sipListener!=null
|
|
// so we should throw if app did not call removeSipListener
|
|
// sipProviderImpl.sipListener = null;
|
|
if (sipProviderImpl.getSipListener() != null) {
|
|
throw new ObjectInUseException(
|
|
"SipProvider still has an associated SipListener!");
|
|
}
|
|
|
|
sipProviderImpl.removeListeningPoints();
|
|
|
|
// Bug reported by Rafael Barriuso
|
|
sipProviderImpl.stop();
|
|
sipProviders.remove(sipProvider);
|
|
if (sipProviders.isEmpty()) {
|
|
this.stopStack();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the IP Address of the stack.
|
|
*
|
|
* @see javax.sip.SipStack#getIPAddress()
|
|
* @deprecated
|
|
*/
|
|
public String getIPAddress() {
|
|
return super.getHostAddress();
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see javax.sip.SipStack#getListeningPoints()
|
|
*/
|
|
public java.util.Iterator getListeningPoints() {
|
|
return this.listeningPoints.values().iterator();
|
|
}
|
|
|
|
/**
|
|
* Return true if retransmission filter is active.
|
|
*
|
|
* @see javax.sip.SipStack#isRetransmissionFilterActive()
|
|
* @deprecated
|
|
*/
|
|
public boolean isRetransmissionFilterActive() {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see javax.sip.SipStack#getSipProviders()
|
|
*/
|
|
public java.util.Iterator<SipProviderImpl> getSipProviders() {
|
|
return this.sipProviders.iterator();
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see javax.sip.SipStack#getStackName()
|
|
*/
|
|
public String getStackName() {
|
|
return this.stackName;
|
|
}
|
|
|
|
/**
|
|
* Finalization -- stop the stack on finalization. Exit the transaction
|
|
* scanner and release all resources.
|
|
*
|
|
* @see java.lang.Object#finalize()
|
|
*/
|
|
protected void finalize() {
|
|
this.stopStack();
|
|
}
|
|
|
|
/**
|
|
* This uses the default stack address to create a listening point.
|
|
*
|
|
* @see javax.sip.SipStack#createListeningPoint(java.lang.String, int,
|
|
* java.lang.String)
|
|
* @deprecated
|
|
*/
|
|
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 (isLoggingEnabled()) {
|
|
getStackLogger().logDebug("stopStack -- stoppping the stack");
|
|
}
|
|
this.stopStack();
|
|
this.sipProviders = new LinkedList<SipProviderImpl>();
|
|
this.listeningPoints = new Hashtable<String, ListeningPointImpl>();
|
|
/*
|
|
* Check for presence of an event scanner ( may happen if stack is
|
|
* stopped before listener is attached ).
|
|
*/
|
|
if (this.eventScanner != null)
|
|
this.eventScanner.forceStop();
|
|
this.eventScanner = null;
|
|
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see javax.sip.SipStack#start()
|
|
*/
|
|
public void start() throws ProviderDoesNotExistException, SipException {
|
|
// Start a new event scanner if one does not exist.
|
|
if (this.eventScanner == null) {
|
|
this.eventScanner = new EventScanner(this);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Get the message log factory registered with the stack.
|
|
*
|
|
* @return -- the messageLogFactory of the stack.
|
|
*/
|
|
public LogRecordFactory getLogRecordFactory() {
|
|
return super.logRecordFactory;
|
|
}
|
|
|
|
/**
|
|
* Set the log appender ( this is useful if you want to specify a particular
|
|
* log format or log to something other than a file for example). This method
|
|
* is will be removed May 11, 2010 or shortly there after.
|
|
*
|
|
* @param Appender
|
|
* - the log4j appender to add.
|
|
* @deprecated TODO: remove this method May 11, 2010.
|
|
*/
|
|
// BEGIN android-deleted
|
|
/*
|
|
@Deprecated
|
|
public void addLogAppender(org.apache.log4j.Appender appender) {
|
|
if (this.getStackLogger() instanceof gov.nist.core.LogWriter) {
|
|
((gov.nist.core.LogWriter) this.getStackLogger()).addAppender(appender);
|
|
}
|
|
}
|
|
*/
|
|
// END android-deleted
|
|
|
|
/**
|
|
* Get the log4j logger ( for log stream integration ).
|
|
* This method will be removed May 11, 2010 or shortly there after.
|
|
*
|
|
* @return the log4j logger.
|
|
* @deprecated TODO: This method will be removed May 11, 2010.
|
|
*/
|
|
@Deprecated
|
|
// BEGIN andoird-deleted
|
|
/*
|
|
public org.apache.log4j.Logger getLogger() {
|
|
if (this.getStackLogger() instanceof gov.nist.core.LogWriter) {
|
|
return ((gov.nist.core.LogWriter) this.getStackLogger()).getLogger();
|
|
}
|
|
return null;
|
|
}
|
|
*/
|
|
// END android-deleted
|
|
|
|
public EventScanner getEventScanner() {
|
|
return eventScanner;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see
|
|
* gov.nist.javax.sip.SipStackExt#getAuthenticationHelper(gov.nist.javax
|
|
* .sip.clientauthutils.AccountManager, javax.sip.header.HeaderFactory)
|
|
*/
|
|
public AuthenticationHelper getAuthenticationHelper(
|
|
AccountManager accountManager, HeaderFactory headerFactory) {
|
|
return new AuthenticationHelperImpl(this, accountManager, headerFactory);
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see
|
|
* gov.nist.javax.sip.SipStackExt#getAuthenticationHelper(gov.nist.javax
|
|
* .sip.clientauthutils.AccountManager, javax.sip.header.HeaderFactory)
|
|
*/
|
|
public AuthenticationHelper getSecureAuthenticationHelper(
|
|
SecureAccountManager accountManager, HeaderFactory headerFactory) {
|
|
return new AuthenticationHelperImpl(this, accountManager, headerFactory);
|
|
}
|
|
|
|
/**
|
|
* Set the list of cipher suites supported by the stack. A stack can have
|
|
* only one set of suites. These are not validated against the supported
|
|
* cipher suites of the java runtime, so specifying a cipher here does not
|
|
* guarantee that it will work.<br>
|
|
* The stack has a default cipher suite of:
|
|
* <ul>
|
|
* <li>TLS_RSA_WITH_AES_128_CBC_SHA</li>
|
|
* <li>SSL_RSA_WITH_3DES_EDE_CBC_SHA</li>
|
|
* <li>TLS_DH_anon_WITH_AES_128_CBC_SHA</li>
|
|
* <li>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</li>
|
|
* </ul>
|
|
*
|
|
* <b>NOTE: This function must be called before adding a TLS listener</b>
|
|
*
|
|
* @param String
|
|
* [] The new set of ciphers to support.
|
|
* @return
|
|
*
|
|
*/
|
|
public void setEnabledCipherSuites(String[] newCipherSuites) {
|
|
cipherSuites = newCipherSuites;
|
|
}
|
|
|
|
/**
|
|
* Return the currently enabled cipher suites of the Stack.
|
|
*
|
|
* @return The currently enabled cipher suites.
|
|
*/
|
|
public String[] getEnabledCipherSuites() {
|
|
return cipherSuites;
|
|
}
|
|
|
|
/**
|
|
* Set the list of protocols supported by the stack for outgoing TLS connections.
|
|
* A stack can have only one set of protocols.
|
|
* These are not validated against the supported
|
|
* protocols of the java runtime, so specifying a protocol here does not
|
|
* guarantee that it will work.<br>
|
|
* The stack has a default protocol suite of:
|
|
* <ul>
|
|
* <li>SSLv3</li>
|
|
* <li>SSLv2Hello</li>
|
|
* <li>TLSv1</li>
|
|
* </ul>
|
|
*
|
|
* <b>NOTE: This function must be called before creating a TLSMessageChannel.</b>
|
|
*
|
|
* @param String
|
|
* [] The new set of protocols to use for outgoing TLS connections.
|
|
* @return
|
|
*
|
|
*/
|
|
public void setEnabledProtocols(String[] newProtocols) {
|
|
enabledProtocols = newProtocols;
|
|
}
|
|
|
|
/**
|
|
* Return the currently enabled protocols to use when creating TLS connection.
|
|
*
|
|
* @return The currently enabled protocols.
|
|
*/
|
|
public String[] getEnabledProtocols() {
|
|
return enabledProtocols;
|
|
}
|
|
|
|
/**
|
|
* Set the "back to back User Agent" flag.
|
|
*
|
|
* @param flag
|
|
* - boolean flag to set.
|
|
*
|
|
*/
|
|
public void setIsBackToBackUserAgent(boolean flag) {
|
|
super.isBackToBackUserAgent = flag;
|
|
}
|
|
|
|
/**
|
|
* Get the "back to back User Agent" flag.
|
|
*
|
|
* return the value of the flag
|
|
*
|
|
*/
|
|
public boolean isBackToBackUserAgent() {
|
|
return super.isBackToBackUserAgent;
|
|
}
|
|
|
|
public boolean isAutomaticDialogErrorHandlingEnabled() {
|
|
return super.isAutomaticDialogErrorHandlingEnabled;
|
|
}
|
|
|
|
public boolean acquireSem() {
|
|
try {
|
|
return this.stackSemaphore.tryAcquire(10, TimeUnit.SECONDS);
|
|
} catch ( InterruptedException ex) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void releaseSem() {
|
|
this.stackSemaphore.release();
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|