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).: * * * @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; } }