001: /*
002: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
003: * Reserved. Use is subject to license terms.
004: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License version
008: * 2 only, as published by the Free Software Foundation.
009: *
010: * This program is distributed in the hope that it will be useful, but
011: * WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * General Public License version 2 for more details (a copy is
014: * included at /legal/license.txt).
015: *
016: * You should have received a copy of the GNU General Public License
017: * version 2 along with this work; if not, write to the Free Software
018: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
022: * Clara, CA 95054 or visit www.sun.com if you need additional
023: * information or have any questions.
024: */
025: /*
026: * StackConnector.java
027: *
028: * Created on Feb 11, 2004
029: *
030: */
031: package gov.nist.microedition.sip;
032:
033: import java.io.IOException;
034: import java.util.Enumeration;
035: import java.util.Random;
036: import java.util.Vector;
037:
038: import javax.microedition.io.Connector;
039: import javax.microedition.io.ServerSocketConnection;
040:
041: import javax.microedition.sip.SipClientConnection;
042: import javax.microedition.sip.SipConnectionNotifier;
043: import javax.microedition.sip.SipDialog;
044:
045: import gov.nist.microedition.io.j2me.sip.DistributedRandom;
046: import gov.nist.siplite.ConfigurationProperties;
047: import gov.nist.siplite.ListeningPoint;
048: import gov.nist.siplite.ObjectInUseException;
049: import gov.nist.siplite.PeerUnavailableException;
050: import gov.nist.siplite.RequestEvent;
051: import gov.nist.siplite.ResponseEvent;
052: import gov.nist.siplite.SipFactory;
053: import gov.nist.siplite.SipListener;
054: import gov.nist.siplite.SipProvider;
055: import gov.nist.siplite.SipStack;
056: import gov.nist.siplite.TimeoutEvent;
057: import gov.nist.siplite.TooManyListenersException;
058: import gov.nist.siplite.TransportNotSupportedException;
059: import gov.nist.siplite.address.AddressFactory;
060: import gov.nist.siplite.address.SipURI;
061: import gov.nist.siplite.header.HeaderFactory;
062: import gov.nist.siplite.header.AcceptContactHeader;
063: import gov.nist.siplite.message.MessageFactory;
064: import gov.nist.siplite.message.Request;
065: import gov.nist.siplite.message.Response;
066: import gov.nist.siplite.stack.ClientTransaction;
067: import gov.nist.siplite.stack.ServerTransaction;
068: import gov.nist.core.NameValueList;
069:
070: import com.sun.midp.security.SecurityToken;
071: import gov.nist.siplite.SIPConstants;
072: import com.sun.midp.log.Logging;
073: import com.sun.midp.log.LogChannels;
074:
075: /**
076: * This class is the connector between the JSR180 and
077: * the nist-siplite stack. This class create a stack from the
078: * SipConnector.open(SIP_URI) with the listening point equals
079: * to the one specified in the SIP URI. If none is specified, a random one is
080: * allowed by the system.
081: * This class receive the messages from the stack because it's implementing the
082: * SipListener class and transmit them to either SipConnectionNotifier or
083: * SipClientConnection or both.
084: * This class follow the singleton design pattern and is thread-safe
085: *
086: *
087: * <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
088: */
089: public class StackConnector implements SipListener {
090:
091: /**
092: * Security token for SIP/SIPS protocol class
093: */
094: private SecurityToken classSecurityToken;
095: /**
096: * The unique instance of this class
097: */
098: private static StackConnector instance = null;
099: /**
100: * The actual stack
101: */
102: protected SipStack sipStack = null;
103: /**
104: * listen address
105: */
106: private String localAddress = null;
107: /**
108: * Temporary listening point.
109: */
110: private ListeningPoint tempListeningPoint = null;
111: /**
112: * Temporary sip provider.
113: */
114: private SipProvider tempSipProvider = null;
115: /**
116: * list of connection notifiers
117: */
118: protected Vector connectionNotifiersList = null;
119: /**
120: * list of client connections
121: */
122: // protected Vector clientConnectionList = null;
123: /**
124: * list of all current dialogs
125: */
126: protected Vector sipDialogList = null;
127: /**
128: * Address factory handle.
129: */
130: public static AddressFactory addressFactory = null;
131: /**
132: * Message factory handle.
133: */
134: public static MessageFactory messageFactory = null;
135: /**
136: * Header factory handle.
137: */
138: public static HeaderFactory headerFactory = null;
139: /**
140: * Shared listening point
141: */
142: private ListeningPoint sharedListeningPoint = null;
143: /**
144: * Shared sipProvider instance
145: */
146: private SipProvider sharedSipProvider = null;
147: /**
148: * Shared port number
149: */
150: int sharedPortNumber = -1;
151:
152: /**
153: * Indicates mime types used by applications for SipConnectionNotifiers
154: * in shared mode
155: */
156: Vector sharedMimeTypes = null;
157:
158: static {
159: // Creates the factories to help to construct messages
160: messageFactory = new MessageFactory();
161: addressFactory = new AddressFactory();
162: headerFactory = new HeaderFactory();
163: }
164:
165: /**
166: * Constructor
167: * Creates the stack
168: * @param classSecurityToken security token for SIP/SIPS protocol class
169: */
170: private StackConnector(SecurityToken classSecurityToken)
171: throws IOException {
172: com.sun.midp.io.j2me.socket.Protocol conn;
173:
174: connectionNotifiersList = new Vector();
175: // clientConnectionList = new Vector();
176: sipDialogList = new Vector();
177: // Create the sipStack
178: SipFactory sipFactory = SipFactory.getInstance();
179: ConfigurationProperties properties = new ConfigurationProperties();
180: int randomPort = new DistributedRandom().nextInt(60000) + 1024;
181:
182: /*
183: * Original NIST method for opening the serversocket connection
184: * has been replaced by direct calls to instantiate the protocol
185: * handler, in order to pass the security token for use of lower
186: * level transport connection.
187: * Original NIST sequence is :
188: *
189: * ServerSocketConnection serverSoc =
190: * (ServerSocketConnection)Connector.open("socket://:"+randomPort);
191: *
192: */
193:
194: conn = new com.sun.midp.io.j2me.socket.Protocol();
195: ServerSocketConnection serverSoc = (ServerSocketConnection) conn
196: .openPrim(classSecurityToken, "//:" + randomPort);
197:
198: localAddress = serverSoc.getLocalAddress();
199: properties.setProperty("javax.sip.IP_ADDRESS", localAddress);
200: properties.setProperty("javax.sip.STACK_NAME", "shootme");
201: properties.setProperty("gov.nist.javax.sip.LOG_FILE_NAME",
202: "/tmp/jsr180-log");
203: properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0"); // TRACE_PRIVATE
204:
205: // Initialise the log file
206: serverSoc.close();
207: try {
208: // Create SipStack object
209: sipStack = sipFactory.createSipStack(properties,
210: classSecurityToken);
211: sipStack.setStackConnector(this );
212: } catch (PeerUnavailableException e) {
213: // SipStackImpl in the classpath
214: // e.printStackTrace();
215: throw new IOException(e.getMessage());
216: }
217: // save security token
218: this .classSecurityToken = classSecurityToken;
219: }
220:
221: /**
222: * Get the unique instance of this class
223: * @param classSecurityToken security token for SIP/SIPS protocol class
224: * @return the unique instance of this class
225: */
226: public synchronized static StackConnector getInstance(
227: SecurityToken classSecurityToken) throws IOException {
228: if (instance == null)
229: instance = new StackConnector(classSecurityToken);
230: return instance;
231: }
232:
233: /**
234: * remove the instance of the stack.
235: */
236: public synchronized static void releaseInstance() {
237: instance = null;
238: }
239:
240: /**
241: * Creates a sip connection notifier in shared mode. In shared mode,
242: * SipConnectionNotifier is supposed to use a single shared port or
243: * listening point for all applications
244: *
245: * @param secure flag to specify whether to use or not the secure layer
246: * @param transport transport protocol name
247: * @param mimeType parameter for filtering incomming SIP packets or null
248: * @return the sip connection notifier that will receive request
249: * @throws IOException if we cannot create the sip connection notifier
250: * for whatsoever reason
251: */
252: public SipConnectionNotifier createSharedSipConnectionNotifier(
253: boolean secure, String transport, String mimeType)
254: throws IOException {
255:
256: if (sharedMimeTypes != null) {
257: for (int i = 0; i < sharedMimeTypes.size(); i++) {
258: if (mimeType.equalsIgnoreCase(((String) sharedMimeTypes
259: .elementAt(i)))) {
260: throw new IOException(
261: "Application type is already " + "reserved");
262: }
263: }
264: } else {
265: sharedMimeTypes = new Vector();
266: }
267:
268: /*
269: * Add the mimeType to sharedMimeTypes vector
270: */
271: sharedMimeTypes.addElement((String) mimeType);
272:
273: /*
274: * SipConnectionNotifier in shared mode must use shared system SIP port
275: * and shared SIP identity. So a shared listening point and sipProvider
276: * must be used for every SipConnectionNotifier in shared mode
277: */
278:
279: if ((sharedListeningPoint == null)
280: && (sharedSipProvider == null)) {
281:
282: // select a free port
283: sharedPortNumber = selectPort(sharedPortNumber, transport);
284:
285: sharedListeningPoint = this .tempListeningPoint; // initialized by "selectPort()"
286: sharedSipProvider = this .tempSipProvider; // initialized by "selectPort()"
287: }
288:
289: SipConnectionNotifier sipConnectionNotifier = new SipConnectionNotifierImpl(
290: sharedSipProvider, localAddress, sharedPortNumber,
291: this .classSecurityToken, mimeType, true);
292: // ((SipConnectionNotifierImpl)sipConnectionNotifier).start();
293: // Add the the newly created sip connection notifier to the list of
294: // connection notifiers
295: this .connectionNotifiersList.addElement(sipConnectionNotifier);
296:
297: return sipConnectionNotifier;
298: }
299:
300: /**
301: * Create a listening point ans sip provider on given or random
302: * selected port. When port is selected successfully,
303: * ListeningPoint and SipProvider instances are created.
304: * @param portNumber the number of the port on which we must listen
305: * for incoming requests or -1 to select random port
306: * @param transport transport protocol name
307: * @return the selected port number
308: * @throws IOException if given port is busy
309: */
310: private int selectPort(int portNumber, String transport)
311: throws IOException {
312:
313: boolean isRandomPort = (portNumber == -1);
314: final int MAX_ATTEMPTS = 1000;
315: int attemps = 0;
316:
317: if (isRandomPort) {
318: // Try the default port first.
319: portNumber = SIPConstants.DEFAULT_NONTLS_PORT;
320: }
321:
322: do {
323: if (attemps++ > MAX_ATTEMPTS) {
324: throw new IOException("Cannot select a port!");
325: }
326:
327: // Creates the listening point
328: // IMPL_NOTE : Use the parameters to restrain the incoming messages
329: try {
330: tempListeningPoint = sipStack.createListeningPoint(
331: portNumber, transport);
332: } catch (TransportNotSupportedException tnse) {
333: // tnse.printStackTrace();
334: throw new IOException(tnse.getMessage());
335: } catch (IllegalArgumentException iae) {
336: if (isRandomPort) { // port is busy
337: // Select a random port from 1024 to 10000.
338: portNumber = new DistributedRandom().nextInt(8975) + 1024;
339: continue;
340: } else {
341: throw new IOException(iae.getMessage());
342: }
343: }
344:
345: // Creates the sip provider
346: try {
347: tempSipProvider = sipStack
348: .createSipProvider(tempListeningPoint);
349: } catch (ObjectInUseException oiue) {
350: if (isRandomPort) { // port is busy
351: // Select a random port from 1024 to 10000.
352: portNumber = new DistributedRandom().nextInt(8975) + 1024;
353: continue;
354: } else {
355: // oiue.printStackTrace();
356: throw new ObjectInUseException(oiue.getMessage());
357: }
358: }
359:
360: isRandomPort = false;
361:
362: // Add this class as a listener for incoming messages
363: try {
364: tempSipProvider.addSipListener(this );
365: } catch (TooManyListenersException tmle) {
366: // tmle.printStackTrace();
367: throw new IOException(tmle.getMessage());
368: }
369: } while (isRandomPort);
370:
371: return portNumber;
372: }
373:
374: /**
375: * Create a sip connection notifier on a specific port
376: * using or not the sip secure layer and with some restrictive parameters
377: * to receive requests
378: * @param portNumber the number of the port on which we must listen
379: * for incoming requests or -1 to select random port
380: * @param secure flag to specify whether to use or not the secure layer
381: * @param transport transport protocol name
382: * @param mimeType parameter for filtering incomming SIP packets or null
383: * @return the sip connection notifier that will receive request
384: * @throws IOException if we cannot create the sip connection notifier
385: * for whatsoever reason
386: */
387: public SipConnectionNotifier createSipConnectionNotifier(
388: int portNumber, boolean secure, String transport,
389: String mimeType) throws IOException {
390:
391: // select a free port (if need)
392: portNumber = selectPort(portNumber, transport);
393:
394: SipConnectionNotifier sipConnectionNotifier = new SipConnectionNotifierImpl(
395: tempSipProvider, localAddress, portNumber,
396: this .classSecurityToken, mimeType, false);
397: // ((SipConnectionNotifierImpl)sipConnectionNotifier).start();
398: // Add the the newly created sip connection notifier to the list of
399: // connection notifiers
400: this .connectionNotifiersList.addElement(sipConnectionNotifier);
401:
402: return sipConnectionNotifier;
403: }
404:
405: /**
406: * Creates a sip Client Connection to send a request to the
407: * following SIP URI user@host:portNumber;parameters
408: * @param inputURI input SIP URI
409: * @return the sip client connection
410: */
411: public SipClientConnection createSipClientConnection(SipURI inputURI) {
412:
413: SipClientConnection sipClientConnection = new SipClientConnectionImpl(
414: inputURI, this .classSecurityToken);
415: return sipClientConnection;
416: }
417:
418: /**
419: * Gets the current connection notifier list.
420: * @return the connection notifier list
421: */
422: public Vector getConnectionNotifiersList() {
423: return connectionNotifiersList;
424: }
425:
426: /**
427: * Gets the current SipStack object.
428: * @return the current SipStack object
429: */
430: public SipStack getSipStack() {
431: return sipStack;
432: }
433:
434: /**
435: * Retrieve from the list of connection notifier the one that use the same
436: * port as in parameter
437: *
438: * @param portNumber the port number
439: * @param acceptContactType MIME type as in Accept-Contact header
440: *
441: * @return the connection notifier matching the same port
442: */
443: public SipConnectionNotifier getSipConnectionNotifier(
444: int portNumber, String acceptContactType) {
445: Enumeration e = connectionNotifiersList.elements();
446: while (e.hasMoreElements()) {
447: SipConnectionNotifier sipConnectionNotifier = (SipConnectionNotifier) e
448: .nextElement();
449: try {
450: if (sipConnectionNotifier.getLocalPort() != portNumber) {
451: continue;
452: }
453:
454: if (acceptContactType != null) {
455: String scnMimeType = ((SipConnectionNotifierImpl) sipConnectionNotifier)
456: .getMIMEType();
457: if (scnMimeType != null) {
458: if (scnMimeType
459: .equalsIgnoreCase(acceptContactType)
460: || "*".equals(acceptContactType)) {
461: return sipConnectionNotifier;
462: }
463: }
464: } else {
465: return sipConnectionNotifier;
466: }
467: } catch (IOException ioe) {
468: // Intentionally ignored.
469: }
470: }
471: return null;
472: }
473:
474: /**
475: * generate a random tag that can be used either in the FromHeader or in the
476: * ToHeader
477: * @return the randomly generated tag
478: */
479: protected static String generateTag() {
480: return String.valueOf(new Random().nextInt(Integer.MAX_VALUE));
481: }
482:
483: /**
484: * find in the dialog list, the sip dialog with the same dialog ID
485: * as the one in parameter
486: * @param dialogID dialogID to test against
487: * @return the sip dialog with the same dialog ID
488: */
489: protected SipDialog findDialog(String dialogID) {
490: Enumeration e = sipDialogList.elements();
491: while (e.hasMoreElements()) {
492: SipDialog sipDialog = (SipDialog) e.nextElement();
493: if (sipDialog.getDialogID() != null
494: && sipDialog.getDialogID().equals(dialogID)) {
495: return sipDialog;
496: }
497: }
498: return null;
499: }
500:
501: /**
502: * Processes the current transaction event.
503: * @param requestEvent the protocol transition event
504: */
505: public void processRequest(RequestEvent requestEvent) {
506: try {
507: String acceptContactType = null;
508: Request request = requestEvent.getRequest();
509: AcceptContactHeader acHdr = request.getAcceptContact();
510: if (acHdr != null) { // Accept-Contact header present
511: acceptContactType = acHdr.getType();
512: }
513:
514: // Retrieve the SipConnectionNotifier from the transaction
515: SipConnectionNotifierImpl sipConnectionNotifier = null;
516: ServerTransaction serverTransaction = requestEvent
517: .getServerTransaction();
518: if (serverTransaction != null)
519: sipConnectionNotifier = (SipConnectionNotifierImpl) serverTransaction
520: .getApplicationData();
521: // If it's a new request coming in, the
522: // sipConnectionNotifier will certainly be null, so
523: // retrieve from the list of connection notifier the one
524: // that use the same port as in parameter
525: if (sipConnectionNotifier == null) {
526: SipProvider sipProvider = (SipProvider) requestEvent
527: .getSource();
528: ListeningPoint listeningPoint = sipProvider
529: .getListeningPoint();
530: sipConnectionNotifier = ((SipConnectionNotifierImpl) getSipConnectionNotifier(
531: listeningPoint.getPort(), acceptContactType));
532: if ((serverTransaction != null)
533: && (sipConnectionNotifier != null)) {
534: serverTransaction
535: .setApplicationData(sipConnectionNotifier);
536: }
537: }
538:
539: if (sipConnectionNotifier != null) {
540: sipConnectionNotifier.notifyRequestReceived(request);
541: } else {
542: // No need to throw any RuntimeException; just log the error
543: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
544: Logging
545: .report(
546: Logging.WARNING,
547: LogChannels.LC_JSR180,
548: "we cannot find any connection notifier"
549: + "matching to handle this request");
550: }
551: }
552: } catch (NullPointerException npe) {
553: // npe.printStackTrace();
554: } catch (IllegalArgumentException iae) {
555: // iae.printStackTrace();
556: }
557: }
558:
559: /**
560: * Processes the resposne event.
561: * @param responseEvent the transition reply event
562: */
563: public void processResponse(ResponseEvent responseEvent) {
564: try {
565: Response response = responseEvent.getResponse();
566:
567: // Retrieve the SipClientConnection from the transaction
568: ClientTransaction clientTransaction = responseEvent
569: .getClientTransaction();
570: SipClientConnectionImpl sipClientConnection = (SipClientConnectionImpl) clientTransaction
571: .getApplicationData();
572:
573: if (sipClientConnection != null) {
574: // translate null when client transactions are same
575: if (sipClientConnection.getClientTransaction().equals(
576: clientTransaction)) {
577: sipClientConnection.notifyResponseReceived(
578: response, null);
579: } else { // send new client transaction
580: sipClientConnection.notifyResponseReceived(
581: response, clientTransaction);
582: }
583: } else {
584: throw new RuntimeException(
585: "we cannot find any client connection"
586: + "matching to handle this request");
587: }
588: } catch (NullPointerException npe) {
589: // npe.printStackTrace();
590: } catch (IllegalArgumentException iae) {
591: // iae.printStackTrace();
592: }
593: }
594:
595: /**
596: * Process a timeout event.
597: * @param timeoutEvent state transition timeout event
598: */
599: public void processTimeout(TimeoutEvent timeoutEvent) {
600:
601: }
602:
603: /**
604: * Close a SipConnectionNotifier in shared mode
605: *
606: * @param mimeType MIME type of the SipConnectionNotifier
607: */
608: public void closeSharedSipConnectionNotifier(String mimeType)
609: throws IOException {
610:
611: // Ignore when sharedMimeTypes already removed
612: if (sharedMimeTypes == null) {
613: return;
614: }
615:
616: sharedMimeTypes.removeElement((String) mimeType);
617:
618: if (sharedMimeTypes.size() == 0) {
619: try {
620: sipStack.deleteListeningPoint(sharedListeningPoint);
621: sipStack.deleteSipProvider(sharedSipProvider);
622: } catch (ObjectInUseException oiue) {
623: throw new IOException(oiue.getMessage());
624: }
625:
626: sharedMimeTypes = null;
627:
628: /*
629: * There are no more associated listening points in shared mode;
630: * so stop the stack
631: */
632: sipStack.stopStack();
633: }
634: }
635: }
|