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: *
027: *
028: * Created on Jan 29, 2004
029: *
030: */
031: package gov.nist.microedition.sip;
032:
033: import java.io.IOException;
034: import java.io.InputStream;
035: import java.io.InterruptedIOException;
036: import java.io.OutputStream;
037: import java.io.ByteArrayInputStream;
038:
039: import java.util.Vector;
040: import java.util.Enumeration;
041:
042: import javax.microedition.sip.SipDialog;
043: import javax.microedition.sip.SipException;
044: import javax.microedition.sip.SipServerConnection;
045: import javax.microedition.sip.SipConnection;
046:
047: import gov.nist.core.HostPort;
048: import gov.nist.core.ParseException;
049: import gov.nist.siplite.SIPConstants;
050: import gov.nist.siplite.SipStack;
051: import gov.nist.siplite.SipProvider;
052: import gov.nist.siplite.TransactionAlreadyExistsException;
053: import gov.nist.siplite.TransactionUnavailableException;
054: import gov.nist.siplite.message.*;
055: import gov.nist.siplite.stack.Dialog;
056: import gov.nist.siplite.stack.ServerTransaction;
057: import gov.nist.siplite.stack.Subscription;
058: import gov.nist.siplite.header.ContactHeader;
059: import gov.nist.siplite.header.ContactList;
060: import gov.nist.siplite.header.ExpiresHeader;
061: import gov.nist.siplite.header.Header;
062: import gov.nist.siplite.header.HeaderList;
063: import gov.nist.siplite.header.ToHeader;
064: import gov.nist.siplite.header.SubscriptionStateHeader;
065: import gov.nist.siplite.header.ContentLengthHeader;
066: import gov.nist.siplite.address.*;
067: import gov.nist.siplite.stack.Transaction;
068:
069: import com.sun.midp.log.Logging;
070: import com.sun.midp.log.LogChannels;
071:
072: /**
073: * SIP ServerConnection implementation.
074: *
075: * <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
076: */
077: public class SipServerConnectionImpl implements SipServerConnection {
078: // Server Transaction States
079: /**
080: * Terminated, the final state, in which the SIP connection has
081: * been terminated by error or closed
082: */
083: public static final int TERMINATED = 0;
084: /**
085: * Request Received, SipServerConnection returned from
086: * SipConnectionNotifier or provisional response(s) (1xx) sent.
087: */
088: public static final int REQUEST_RECEIVED = 1;
089: /**
090: * Initialized, response initialized calling initResponse()
091: */
092: public static final int INITIALIZED = 2;
093: /**
094: * Stream Open, OutputStream opened with openContentOutputStream().
095: * Opening InputStream for received request does not trigger state
096: * transition.
097: */
098: public static final int STREAM_OPEN = 3;
099: /**
100: * Completed, transaction completed with sending final response
101: * (2xx, 3xx, 4xx, 5xx, 6xx)
102: */
103: public static final int COMPLETED = 4;
104: /**
105: * Attribute keeping the actual state of this server transaction
106: */
107: private int state;
108: /**
109: * the sip dialog this client transaction belongs to
110: */
111: private SipDialog sipDialog = null;
112: /**
113: * the request for this server transaction
114: */
115: private Request request = null;
116: /**
117: * the response to the actual request
118: */
119: private Response response = null;
120: /**
121: * content of the response body
122: */
123: private SDPOutputStream contentOutputStream = null;
124: /**
125: * content from the request body
126: */
127: private InputStream contentInputStream = null;
128:
129: /**
130: * Receiver of incoming messages
131: */
132: private SipConnectionNotifierImpl sipConnectionNotifierImpl;
133: /**
134: * Flag indicating which SIP message (request or response)
135: * should be used in getHeader()/getHeaders()/setHeader()/removeHeader().
136: */
137: private boolean useResponse = false;
138:
139: /**
140: * Boolean flag used to indicate if 2xx is allowed to resend
141: * This flag is set to true if 2xx is sent and SipServerConnectionImpl
142: * transitions to COMPLETED state
143: */
144: private boolean resend2xxAllowed = false;
145:
146: /**
147: * Constructor.
148: * @param request the protocol connection request
149: * @param sipDialog the current transaction state
150: * @param sipConnectionNotifierImpl the notification handler
151: */
152: protected SipServerConnectionImpl(Request request,
153: SipDialog sipDialog,
154: SipConnectionNotifierImpl sipConnectionNotifierImpl) {
155: this .request = request;
156: this .sipDialog = sipDialog;
157: this .sipConnectionNotifierImpl = sipConnectionNotifierImpl;
158: if (request.getMethod() == Request.ACK) {
159: state = COMPLETED;
160: } else {
161: state = REQUEST_RECEIVED;
162: }
163: }
164:
165: /**
166: * Initializes SipServerConnection with a specific SIP response to the
167: * received request.
168: * The default headers and reason phrase will be initialized automatically.
169: * After this the SipServerConnection is in Initialized state.
170: * The response can be sent. The procedure of generating the response and
171: * header fields is defined in RFC 3261 [1] p. 49-50. At least following
172: * information is set by the method:
173: * From MUST equal the From header field of the request
174: * Call-ID MUST equal the Call-ID header field of the request
175: * CSeq MUST equal the CSeq field of the request
176: * Via MUST equal the Via header field values in the request
177: * and MUST maintain the same ordering
178: * To MUST Copy if exists in the original request,
179: * 'tag' MUST be added if not present
180: * Furthermore, if the system has automatically sent the 100 Trying
181: * response, the 100 response initialized and sent by the user
182: * is just ignored.
183: * @param code - Response status code 1xx - 6xx
184: * @throws IllegalArgumentException - if the status code is out of
185: * range 100-699 (RFC 3261 p.28-29)
186: * @throws SipException - INVALID_STATE if the response can not be
187: * initialized, because of wrong state.
188: */
189: public void initResponse(int code) throws IllegalArgumentException,
190: SipException {
191: // Check if the code is not out of range
192: if (code < 100 || code > 699)
193: throw new IllegalArgumentException(
194: "the response code is out of range.");
195:
196: // Check if we are in a good state to init the response
197: if (state != REQUEST_RECEIVED)
198: throw new SipException(
199: "the response can not be initialized,"
200: + " because of wrong state.",
201: SipException.INVALID_STATE);
202:
203: // Generating the response to the request
204: try {
205: response = StackConnector.messageFactory.createResponse(
206: code, request);
207: } catch (ParseException pe) {
208: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
209: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
210: "Exception in SSC.initResponse(): " + pe);
211: pe.printStackTrace();
212: }
213: }
214:
215: // Set the toTag in the ToHeader if not already present
216: ToHeader toHeader = (ToHeader) response
217: .getHeader(ToHeader.NAME);
218: if (toHeader.getTag() == null)
219: toHeader.setTag(StackConnector.generateTag());
220:
221: // if we don't have any contact headers we add one
222: ContactList contactList = response.getContactHeaders();
223: if (contactList == null || contactList.isEmpty()) {
224: ContactHeader contactHeader = null;
225: String transport = sipConnectionNotifierImpl
226: .getSipProvider().getListeningPoint()
227: .getTransport();
228: try {
229: Address address = StackConnector.addressFactory
230: .createAddress("<sip:"
231: + sipConnectionNotifierImpl
232: .getLocalAddress()
233: + ":"
234: + sipConnectionNotifierImpl
235: .getLocalPort() + ";transport="
236: + transport + ">");
237: contactHeader = StackConnector.headerFactory
238: .createContactHeader(address);
239: response.addHeader(contactHeader);
240: } catch (IOException ioe) {
241: ioe.printStackTrace();
242: } catch (ParseException pe) {
243: pe.printStackTrace();
244: }
245: }
246:
247: state = INITIALIZED;
248: useResponse = true;
249:
250: // System.out.println("The following response has been initialized:\n"+
251: // response.toString());
252: }
253:
254: /**
255: * Changes the default reason phrase.
256: * @param phrase the default reason phrase.
257: * @throws SipException INVALID_STATE if the response can not
258: * be initialized, because of wrong state.
259: * INVALID_OPERATION if the reason phrase can not be set.
260: * @throws IllegalArgumentException if the reason phrase is illegal.
261: */
262: public void setReasonPhrase(String phrase) throws SipException,
263: IllegalArgumentException {
264: if (state != INITIALIZED)
265: throw new SipException("the Reason Phrase can not be set,"
266: + " because of wrong state.",
267: SipException.INVALID_STATE);
268:
269: if (phrase == null) {
270: throw new IllegalArgumentException(
271: "The reason phrase can not be null.");
272: }
273:
274: // RFC 3261, section 7.2: No CR or LF is allowed
275: // (in the Status Line) except in the final CRLF sequence.
276: if ((phrase.indexOf("\n") != -1)
277: || (phrase.indexOf("\r") != -1)) {
278: throw new IllegalArgumentException("Invalid reason phrase.");
279: }
280:
281: response.setReasonPhrase(phrase);
282: }
283:
284: /**
285: * (non-Javadoc)
286: * @see javax.microedition.sip.SipConnection#send()
287: */
288: public void send() throws IOException, InterruptedIOException,
289: SipException {
290: if (state == REQUEST_RECEIVED) {
291: throw new SipException("can not send response"
292: + " because of wrong state.",
293: SipException.INVALID_STATE);
294: }
295:
296: if ((state == COMPLETED) && !resend2xxAllowed) {
297: throw new SipException("COMPLETED state allows"
298: + " only resend of 2xx responses",
299: SipException.INVALID_STATE);
300: }
301:
302: if (state == TERMINATED) {
303: throw new SipException("can not send response"
304: + " because SipServerConnection is TERMINATED",
305: SipException.INVALID_STATE);
306: }
307:
308: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
309: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
310: "Actual request for the response we want to send:\n"
311: + request.toString());
312: }
313:
314: ServerTransaction serverTransaction = (ServerTransaction) request
315: .getTransaction();
316:
317: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
318: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
319: "SipServerTransaction :" + serverTransaction);
320: }
321:
322: // Set the sdp body of the message
323: if (contentOutputStream != null) {
324: contentOutputStream.setOpen(false);
325: response.setContent(contentOutputStream
326: .getByteArrayOutputStream().toByteArray());
327: contentOutputStream = null;
328: }
329:
330: String method = request.getMethod();
331: final int statusCode = response.getStatusCode();
332: final int statusGroup = statusCode / 100;
333:
334: // send the response
335: if (!resend2xxAllowed) { // don't create new transaction on resending
336: SipStack sipStack = sipConnectionNotifierImpl
337: .getStackConnector().getSipStack();
338:
339: if (serverTransaction == null
340: || sipStack.isDialogCreated(method)) {
341: try {
342: SipProvider sipProvider = sipConnectionNotifierImpl
343: .getSipProvider();
344: if (serverTransaction == null) {
345: serverTransaction = sipProvider
346: .getNewServerTransaction(request);
347: } else {
348: /*
349: * 12.1 Creation of a Dialog
350: * Dialogs are created through the generation of
351: * non-failure responses to requests with specific
352: * methods. Within this specification, only 2xx and
353: * 101-199 responses with a To tag, where the request
354: * was INVITE, will establish a dialog.
355: */
356: if (statusCode > 100 && statusCode < 300) {
357: // Equip a dialog in case of dialog is null
358: // or its state is INITIALIZED only prevent
359: // changing contact property of dialog by
360: // sending 200 OK response for INVITE
361: // after sending 200 OK for UPDATE
362: boolean equipDialog = false;
363: if (sipDialog == null) {
364: equipDialog = true;
365: } else if (sipDialog.getState() == Dialog.INITIAL_STATE) {
366: equipDialog = true;
367: }
368:
369: if (equipDialog) {
370: sipProvider.equipADialogForTransaction(
371: serverTransaction, request);
372: }
373: } else if ((sipDialog != null)
374: && (statusGroup > 2 && statusGroup < 7)) {
375: // JSR180, p.42
376: // Terminated state: error response (3xx-6xx)
377: // received (or sent).
378: ((SipDialogImpl) sipDialog)
379: .setState(SipDialog.TERMINATED);
380: }
381: }
382: } catch (TransactionAlreadyExistsException taee) {
383: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
384: Logging.report(Logging.ERROR,
385: LogChannels.LC_JSR180,
386: "Exception in SSC.send(): " + taee);
387: taee.printStackTrace();
388: }
389: // return;
390: } catch (TransactionUnavailableException tue) {
391: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
392: Logging.report(Logging.ERROR,
393: LogChannels.LC_JSR180,
394: "Exception in SSC.send(): " + tue);
395: tue.printStackTrace();
396: }
397: // return;
398: }
399: }
400: }
401:
402: SipConnectionNotifierImpl newNotifier = sipConnectionNotifierImpl;
403:
404: if (sipDialog != null) {
405: Dialog dialog = ((SipDialogImpl) sipDialog).getDialog();
406:
407: if (dialog != null
408: && (statusCode > 199 && statusCode < 300)) {
409: if (method.equals(Request.UPDATE)) {
410: // processing UPDATE - change dialog contact property
411: dialog.addRoute(serverTransaction
412: .getOriginalRequest());
413: }
414:
415: newNotifier = findNotifier();
416: Transaction transaction = (Transaction) dialog
417: .getFirstTransaction();
418: if (transaction instanceof ServerTransaction) {
419: transaction.setApplicationData(newNotifier);
420: }
421:
422: if (newNotifier != null) {
423: sipConnectionNotifierImpl = newNotifier;
424: }
425: }
426: }
427:
428: // Set the application data so that when the request comes in,
429: // it will retrieve this SipConnectionNotifier
430: serverTransaction.setApplicationData(newNotifier);
431:
432: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
433: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
434: "response to send : " + response);
435: }
436:
437: // May throw IOException and SipException
438: serverTransaction.sendResponse(response);
439:
440: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
441: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
442: "response sent");
443: }
444:
445: // Change the state of SipServerConnection
446: int responseClass = response.getStatusCode() / 100;
447: if (responseClass == 1) { // 1xx responses
448: state = REQUEST_RECEIVED;
449: } else {
450: state = COMPLETED;
451: if (responseClass == 2) {
452: // Allow 2xx response to be resent
453: resend2xxAllowed = true;
454: }
455: }
456:
457: // Change the dialog state
458: changeDialogState(serverTransaction);
459: }
460:
461: /**
462: * Finds a connection notifier that listens on the port given in the
463: * Contact header of the response. When a 2xx response contains a new
464: * Contact with the port that is different from one that is used by the
465: * connection notifier associated with this server connection object
466: * (sipConnectionNotifierImpl), the notifier must be changed accordingly.
467: * @returns SipConnectionNotifier listening on the new Contact or
468: * null if such notifier was not found.
469: */
470: private SipConnectionNotifierImpl findNotifier()
471: throws SipException {
472: // IMPL_NOTE: handle shared connections
473: int localPort = SIPConstants.DEFAULT_NONTLS_PORT;
474: String localTransport = SIPConstants.TRANSPORT_UDP;
475:
476: ContactList contactList = response.getContactHeaders();
477: if (contactList != null) {
478: ContactHeader contact = (ContactHeader) contactList
479: .getFirst();
480: HostPort hp = contact.getHostPort();
481: if (hp != null) {
482: localPort = hp.getPort();
483: }
484:
485: URI uri = contact.getAddress().getURI();
486: String transport = uri.isSipURI() ? ((SipURI) uri)
487: .getTransportParam() : null;
488: if (transport != null) {
489: localTransport = transport;
490: }
491: }
492:
493: // Find a SipConnectionNotifier listening on the given port.
494: StackConnector stackConnector = sipConnectionNotifierImpl
495: .getStackConnector();
496:
497: SipConnectionNotifierImpl newNotifier = (SipConnectionNotifierImpl) stackConnector
498: .getSipConnectionNotifier(localPort,
499: sipConnectionNotifierImpl.getMIMEType());
500:
501: return newNotifier;
502: }
503:
504: /**
505: * Sets header value in SIP message. If the header does not exist
506: * it will be added to the message, otherwise the existing header is
507: * overwritten. If multiple header field values exist the topmost is
508: * overwritten. The implementations MAY restrict the access to some headers
509: * according to RFC 3261.
510: * @param name - name of the header, either in full or compact form.
511: * RFC 3261 p.32
512: * @param value - the header value
513: * @throws SipException - INVALID_STATE if header can not be set in
514: * this state. <br> INVALID_OPERATION if the system does not allow to set
515: * this header.
516: * @throws IllegalArgumentException - MAY be thrown if the header or
517: * value is invalid
518: */
519: public void setHeader(String name, String value)
520: throws SipException, IllegalArgumentException {
521: if (state != INITIALIZED)
522: throw new SipException("the Header can not be set,"
523: + " because of wrong state.",
524: SipException.INVALID_STATE);
525:
526: if (name == null)
527: throw new IllegalArgumentException(
528: "The header name can not be null");
529:
530: if (value == null)
531: throw new IllegalArgumentException(
532: "The header value can not be null");
533:
534: Header header = null;
535:
536: try {
537: header = StackConnector.headerFactory.createHeader(name,
538: value);
539: } catch (ParseException pe) {
540: throw new IllegalArgumentException(pe.getMessage());
541: }
542:
543: if (header == null)
544: throw new IllegalArgumentException("null header!");
545:
546: // response.attachHeader(header, false, true);
547: Message currentMessage = useResponse ? (Message) response
548: : (Message) request;
549:
550: if (currentMessage == null) {
551: throw new SipException("Failure in setHeader(),"
552: + " associated request or response is null",
553: SipException.INVALID_MESSAGE);
554: }
555:
556: currentMessage.attachHeader(header, true, true);
557: }
558:
559: /**
560: * Adds a header to the SIP message. If multiple header field values exist
561: * the header value is added topmost of this type of headers.
562: * The implementations MAY restrict the access to some headers
563: * according to RFC 3261.
564: * @param name - name of the header, either in full or compact form.
565: * RFC 3261 p.32
566: * @param value - the header value
567: * @throws SipException - INVALID_STATE if header can not be added in
568: * this state. <br> INVALID_OPERATION if the system does not allow to add
569: * this header.
570: * @throws IllegalArgumentException - MAY be thrown if the header or
571: * value is invalid
572: */
573: public void addHeader(String name, String value)
574: throws SipException, IllegalArgumentException {
575: if (state != INITIALIZED)
576: throw new SipException("the Header can not be add,"
577: + " because of wrong state.",
578: SipException.INVALID_STATE);
579: if (name == null)
580: throw new IllegalArgumentException(
581: "The header name can not be null");
582: if (value == null)
583: throw new IllegalArgumentException(
584: "The header value can not be null");
585:
586: Header header = null;
587: try {
588: header = StackConnector.headerFactory.createHeader(name,
589: value);
590: } catch (ParseException pe) {
591: throw new IllegalArgumentException(
592: "The header can not be created,"
593: + " check if it is correct");
594: }
595:
596: Message currentMessage = useResponse ? (Message) response
597: : (Message) request;
598:
599: if (currentMessage == null) {
600: throw new SipException("Failure in addHeader(),"
601: + " associated request or response is null",
602: SipException.INVALID_MESSAGE);
603: }
604:
605: currentMessage.addHeader(header);
606: }
607:
608: /**
609: * Removes header from the SIP message. If multiple header field
610: * values exist the topmost is removed.
611: * The implementations MAY restrict the access to some headers
612: * according to RFC 3261.
613: * If the named header is not found this method does nothing.
614: * @param name - name of the header to be removed, either int
615: * full or compact form RFC 3261 p.32.
616: * @throws SipException - INVALID_STATE if header can not be removed in
617: * this state. <br> INVALID_OPERATION if the system does not allow to remove
618: * this header.
619: */
620: public void removeHeader(String name) throws SipException,
621: IllegalArgumentException {
622: if (state != INITIALIZED) {
623: throw new SipException("the Header can not be removed,"
624: + " because of wrong state.",
625: SipException.INVALID_STATE);
626: }
627:
628: if (name == null) {
629: throw new IllegalArgumentException(
630: "The header name can not be null");
631: }
632:
633: Message currentMessage = useResponse ? (Message) response
634: : (Message) request;
635:
636: if (currentMessage == null) {
637: throw new SipException("Failure in removeHeader(),"
638: + " associated request or response is null",
639: SipException.INVALID_MESSAGE);
640: }
641:
642: currentMessage.removeHeader(name, true);
643: }
644:
645: /**
646: * Gets the header field value(s) of specified header type
647: * @param name - name of the header, either in full or compact form.
648: * RFC 3261 p.32
649: * @return array of header field values (topmost first), or null if the
650: * current message does not have such a header or the header is for other
651: * reason not available (e.g. message not initialized).
652: */
653: public String[] getHeaders(String name) {
654: Message currentMessage = useResponse ? (Message) response
655: : (Message) request;
656:
657: // Return null if associated request or response is null
658: if (currentMessage == null) {
659: return null;
660: }
661:
662: HeaderList nameList = currentMessage.getHeaderList(name);
663:
664: if (nameList == null) {
665: return null;
666: }
667:
668: int size = nameList.size();
669:
670: if (size < 1) {
671: return null;
672: }
673:
674: String[] headerValues = new String[size];
675:
676: for (int count = 0; count < size; count++) {
677: headerValues[count] = ((Header) nameList.elementAt(count))
678: .getHeaderValue();
679: }
680:
681: return headerValues;
682: }
683:
684: /**
685: * Gets the header field value of specified header type.
686: * @param name - name of the header type, either in full or compact form.
687: * RFC 3261 p.32
688: * @return topmost header field value, or null if the
689: * current message does not have such a header or the header is for other
690: * reason not available (e.g. message not initialized).
691: */
692: public String getHeader(String name) {
693: Message currentMessage = useResponse ? (Message) response
694: : (Message) request;
695:
696: // Return null if associated request or response is null
697: if (currentMessage == null) {
698: return null;
699: }
700:
701: Header header = currentMessage.getHeader(name);
702:
703: if (header == null) {
704: return null;
705: }
706:
707: return header.getHeaderValue();
708: }
709:
710: /**
711: * Gets the SIP method. Applicable when a message has been
712: * initialized or received.
713: * @return SIP method name REGISTER, INVITE, NOTIFY, etc. Returns null if
714: * the method is not available.
715: */
716: public String getMethod() {
717: if (TERMINATED == state) {
718: return null;
719: } else {
720: return request.getMethod();
721: }
722: }
723:
724: /**
725: * Gets Request-URI. Available when SipClientConnection is in Initialized
726: * state or when SipServerConnection is in Request Received state.
727: * Built from the original URI given in Connector.open().
728: * See RFC 3261 p.35 (8.1.1.1 Request-URI)
729: * @return Request-URI of the message. Returns null if the Request-URI
730: * is not available.
731: */
732: public String getRequestURI() {
733: // from the JSR180 spec:
734: // "Returns null if the Request-URI is not available...
735: // Available when... SipServerConnection is in Request Received state."
736: // Most likely, because only in this case there's no ambiguity whether
737: // the request or the response was meant.
738: if (REQUEST_RECEIVED != state) {
739: return null;
740: } else {
741: return request.getRequestURI().toString();
742: }
743: }
744:
745: /**
746: * Gets SIP response status code. Available when SipClientConnection is in
747: * Proceeding or Completed state or when SipServerConnection is in
748: * Initialized state.
749: * @return status code 1xx, 2xx, 3xx, 4xx, ... Returns 0 if the status code
750: * is not available.
751: */
752: public int getStatusCode() {
753: if (state != INITIALIZED || response == null) {
754: return 0;
755: } else {
756: return response.getStatusCode();
757: }
758: }
759:
760: /**
761: * Gets SIP response reason phrase. Available when SipClientConnection is in
762: * Proceeding or Completed state or when SipServerConnection is in
763: * Initialized state.
764: * @return reason phrase. Returns null if the reason phrase is
765: * not available.
766: */
767: public String getReasonPhrase() {
768: if (state != INITIALIZED || response == null) {
769: return null;
770: } else {
771: return response.getReasonPhrase();
772: }
773: }
774:
775: /**
776: * Returns the current SIP dialog. This is available when the SipConnection
777: * belongs to a created SipDialog and the system has received (or sent)
778: * provisional (101-199) or final response (200).
779: * @return SipDialog object if this connection belongs to a dialog,
780: * otherwise returns null.
781: */
782: public SipDialog getDialog() {
783: if (sipDialog != null) {
784: byte dialogState = this .sipDialog.getState();
785: if ((dialogState != SipDialog.EARLY)
786: && (dialogState != SipDialog.CONFIRMED)) {
787: return null;
788: }
789: }
790:
791: return sipDialog;
792: }
793:
794: /**
795: * Returns InputStream to read SIP message body content.
796: * @return InputStream to read body content
797: * @throws java.io.IOException - if the InputStream can not be opened,
798: * because of an I/O error occurred.
799: * @throws SipException - INVALID_STATE the InputStream can not be opened
800: * in this state (e.g. no message received).
801: */
802: public InputStream openContentInputStream() throws IOException,
803: SipException {
804: if (state != REQUEST_RECEIVED)
805: throw new SipException(
806: "the content input stream can not be open,"
807: + " because of wrong state: " + state,
808: SipException.INVALID_STATE);
809:
810: if (request == null) {
811: throw new IOException("Request is null.");
812: }
813:
814: ContentLengthHeader contentLengthHeader = request
815: .getContentLengthHeader();
816: if (contentLengthHeader == null) {
817: throw new IOException(
818: "Request contains no content length header.");
819: }
820:
821: int bodyLength = contentLengthHeader.getContentLength();
822: if (bodyLength == 0) {
823: throw new IOException("Request's body has zero length.");
824: }
825:
826: byte[] buf = request.getRawContent();
827: if (buf == null) { // body is empty
828: throw new IOException("Body of SIP request is empty.");
829: }
830:
831: contentInputStream = new ByteArrayInputStream(buf);
832: return contentInputStream;
833: }
834:
835: /**
836: * Returns OutputStream to fill the SIP message body content.
837: * When calling close() on OutputStream the message will be sent
838: * to the network. So it is equivalent to call send(). Again send() must
839: * not be called after closing the OutputStream, since it will throw
840: * Exception because of calling the method in wrong state.
841: * Before opening OutputStream the Content-Length and Content-Type headers
842: * has to se set. If not SipException.UNKNOWN_LENGTH or
843: * SipException.UNKNOWN_TYPE will be thrown respectively.
844: * @return OutputStream to write body content
845: * @throws IOException if the OutputStream can not be opened,
846: * because of an I/O error occurred.
847: * @throws SipException INVALID_STATE the OutputStream can not be opened
848: * in this state (e.g. no message initialized).
849: * UNKNOWN_LENGTH Content-Length header not set.
850: * UNKNOWN_TYPE Content-Type header not set.
851: */
852: public OutputStream openContentOutputStream() throws IOException,
853: SipException {
854: if (state != INITIALIZED)
855: throw new SipException(
856: "the content output strean can not be open,"
857: + " because of wrong state.",
858: SipException.INVALID_STATE);
859: if (state == TERMINATED) {
860: throw new IOException("can not open content output stream"
861: + " because SipServerConnection is TERMINATED");
862: }
863:
864: if (response.getHeader(Header.CONTENT_TYPE) == null)
865: throw new SipException("Content-Type unknown, set the"
866: + " content-type header first",
867: SipException.UNKNOWN_TYPE);
868: if (response.getHeader(Header.CONTENT_LENGTH) == null)
869: throw new SipException("Content-Length unknown, set the "
870: + "content-length header first",
871: SipException.UNKNOWN_LENGTH);
872: contentOutputStream = new SDPOutputStream(this );
873: state = STREAM_OPEN;
874: return contentOutputStream;
875: }
876:
877: /**
878: * Closes the connection.
879: * @exception IOException if an I/O error occurs
880: * @see javax.microedition.io.Connection#close()
881: */
882: public void close() throws IOException {
883: state = TERMINATED;
884: }
885:
886: /**
887: * Change the state of the dialog after sending a response.
888: * @param serverTransaction current transaction
889: */
890: private void changeDialogState(ServerTransaction serverTransaction) {
891: // System.out.println(">>> SERVER: changing state, " +
892: // response.getCSeqHeader().getMethod());
893:
894: int statusCode = response.getStatusCode();
895:
896: if (statusCode == 100 || sipDialog == null) {
897: return;
898: }
899:
900: String cseqMethod = response.getCSeqHeader().getMethod();
901: SipDialogImpl sipDialogImpl = (SipDialogImpl) sipDialog;
902:
903: if (cseqMethod.equals(Request.NOTIFY)) {
904: // System.out.println(">>> SERVER: NOTIFY!!!");
905: sipDialogImpl.handleNotify(request, serverTransaction
906: .getDialog(), null);
907: return;
908: }
909:
910: // Default is to not create a dialog.
911: // The dialog only is created for the methods that are known
912: // to establish a dialog.
913: int statusGroup = statusCode / 100;
914:
915: if (sipConnectionNotifierImpl.getStackConnector().getSipStack()
916: .isDialogCreated(cseqMethod)
917: || cseqMethod.equals(Request.BYE)) {
918: if (statusGroup == 2) {
919: // RFC 3261, section 13.2.2.4:
920: // If the dialog identifier in the 2xx response matches the
921: // dialog identifier of an existing dialog, the dialog MUST
922: // be transitioned to the "confirmed" state.
923: sipDialogImpl.setDialog(serverTransaction.getDialog());
924: sipDialogImpl.setState(SipDialog.CONFIRMED);
925:
926: if (statusCode == 200) {
927: if (cseqMethod.equals(Request.SUBSCRIBE)
928: || cseqMethod.equals(Request.REFER)) {
929: sipDialogImpl.addSubscription(new Subscription(
930: sipDialogImpl.getDialog(), request));
931: } else if (cseqMethod.equals(Request.BYE)) {
932: sipDialogImpl.setWaitForBye(false);
933: sipDialogImpl.terminateIfNoSubscriptions();
934: }
935: }
936: } else if (statusGroup == 1) {
937: // provisional response
938: if (sipDialog.getState() == SipDialogImpl.INITIALIZED) {
939: // switch to EARLY state
940: sipDialogImpl.setState(SipDialog.EARLY);
941: }
942: } else { // another response code - switch to TERMINATED state
943: sipDialogImpl.terminateIfNoSubscriptions();
944: }
945:
946: // set dialog ID if need
947: if (sipDialogImpl.getDialogID() == null) {
948: int state = sipDialog.getState();
949: if ((state == SipDialog.EARLY)
950: || (state == SipDialog.CONFIRMED)) {
951: sipDialogImpl.setDialog(serverTransaction
952: .getDialog());
953: sipDialogImpl.setDialogID(response
954: .getDialogId(true));
955: }
956: }
957: }
958: }
959:
960: /**
961: * Return the state of SIP server connection
962: *
963: * @return state of the SIP Server Connection
964: */
965: public int getState() {
966: return state;
967: }
968: }
|