/*
* Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
* Reserved. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
/*
*
*
* Created on Jan 29, 2004
*
*/
package gov.nist.microedition.sip;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.util.Vector;
import java.util.Enumeration;
import javax.microedition.sip.SipDialog;
import javax.microedition.sip.SipException;
import javax.microedition.sip.SipServerConnection;
import javax.microedition.sip.SipConnection;
import gov.nist.core.HostPort;
import gov.nist.core.ParseException;
import gov.nist.siplite.SIPConstants;
import gov.nist.siplite.SipStack;
import gov.nist.siplite.SipProvider;
import gov.nist.siplite.TransactionAlreadyExistsException;
import gov.nist.siplite.TransactionUnavailableException;
import gov.nist.siplite.message.*;
import gov.nist.siplite.stack.Dialog;
import gov.nist.siplite.stack.ServerTransaction;
import gov.nist.siplite.stack.Subscription;
import gov.nist.siplite.header.ContactHeader;
import gov.nist.siplite.header.ContactList;
import gov.nist.siplite.header.ExpiresHeader;
import gov.nist.siplite.header.Header;
import gov.nist.siplite.header.HeaderList;
import gov.nist.siplite.header.ToHeader;
import gov.nist.siplite.header.SubscriptionStateHeader;
import gov.nist.siplite.header.ContentLengthHeader;
import gov.nist.siplite.address.*;
import gov.nist.siplite.stack.Transaction;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
/**
* SIP ServerConnection implementation.
*
* <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
*/
public class SipServerConnectionImpl implements SipServerConnection {
// Server Transaction States
/**
* Terminated, the final state, in which the SIP connection has
* been terminated by error or closed
*/
public static final int TERMINATED = 0;
/**
* Request Received, SipServerConnection returned from
* SipConnectionNotifier or provisional response(s) (1xx) sent.
*/
public static final int REQUEST_RECEIVED = 1;
/**
* Initialized, response initialized calling initResponse()
*/
public static final int INITIALIZED = 2;
/**
* Stream Open, OutputStream opened with openContentOutputStream().
* Opening InputStream for received request does not trigger state
* transition.
*/
public static final int STREAM_OPEN = 3;
/**
* Completed, transaction completed with sending final response
* (2xx, 3xx, 4xx, 5xx, 6xx)
*/
public static final int COMPLETED = 4;
/**
* Attribute keeping the actual state of this server transaction
*/
private int state;
/**
* the sip dialog this client transaction belongs to
*/
private SipDialog sipDialog = null;
/**
* the request for this server transaction
*/
private Request request = null;
/**
* the response to the actual request
*/
private Response response = null;
/**
* content of the response body
*/
private SDPOutputStream contentOutputStream = null;
/**
* content from the request body
*/
private InputStream contentInputStream = null;
/**
* Receiver of incoming messages
*/
private SipConnectionNotifierImpl sipConnectionNotifierImpl;
/**
* Flag indicating which SIP message (request or response)
* should be used in getHeader()/getHeaders()/setHeader()/removeHeader().
*/
private boolean useResponse = false;
/**
* Boolean flag used to indicate if 2xx is allowed to resend
* This flag is set to true if 2xx is sent and SipServerConnectionImpl
* transitions to COMPLETED state
*/
private boolean resend2xxAllowed = false;
/**
* Constructor.
* @param request the protocol connection request
* @param sipDialog the current transaction state
* @param sipConnectionNotifierImpl the notification handler
*/
protected SipServerConnectionImpl(
Request request,
SipDialog sipDialog,
SipConnectionNotifierImpl sipConnectionNotifierImpl) {
this.request = request;
this.sipDialog = sipDialog;
this.sipConnectionNotifierImpl = sipConnectionNotifierImpl;
if (request.getMethod() == Request.ACK) {
state = COMPLETED;
} else {
state = REQUEST_RECEIVED;
}
}
/**
* Initializes SipServerConnection with a specific SIP response to the
* received request.
* The default headers and reason phrase will be initialized automatically.
* After this the SipServerConnection is in Initialized state.
* The response can be sent. The procedure of generating the response and
* header fields is defined in RFC 3261 [1] p. 49-50. At least following
* information is set by the method:
* From MUST equal the From header field of the request
* Call-ID MUST equal the Call-ID header field of the request
* CSeq MUST equal the CSeq field of the request
* Via MUST equal the Via header field values in the request
* and MUST maintain the same ordering
* To MUST Copy if exists in the original request,
* 'tag' MUST be added if not present
* Furthermore, if the system has automatically sent the 100 Trying
* response, the 100 response initialized and sent by the user
* is just ignored.
* @param code - Response status code 1xx - 6xx
* @throws IllegalArgumentException - if the status code is out of
* range 100-699 (RFC 3261 p.28-29)
* @throws SipException - INVALID_STATE if the response can not be
* initialized, because of wrong state.
*/
public void initResponse(int code)
throws IllegalArgumentException, SipException {
// Check if the code is not out of range
if (code < 100 || code > 699)
throw new
IllegalArgumentException("the response code is out of range.");
// Check if we are in a good state to init the response
if (state != REQUEST_RECEIVED)
throw new SipException("the response can not be initialized,"
+ " because of wrong state.",
SipException.INVALID_STATE);
// Generating the response to the request
try {
response = StackConnector
.messageFactory.createResponse(code, request);
} catch (ParseException pe) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"Exception in SSC.initResponse(): " + pe);
pe.printStackTrace();
}
}
// Set the toTag in the ToHeader if not already present
ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
if (toHeader.getTag() == null)
toHeader.setTag(StackConnector.generateTag());
// if we don't have any contact headers we add one
ContactList contactList = response.getContactHeaders();
if (contactList == null || contactList.isEmpty()) {
ContactHeader contactHeader = null;
String transport = sipConnectionNotifierImpl.
getSipProvider().getListeningPoint().getTransport();
try {
Address address = StackConnector
.addressFactory
.createAddress(
"<sip:"
+ sipConnectionNotifierImpl.getLocalAddress()
+ ":"
+ sipConnectionNotifierImpl.getLocalPort()
+ ";transport="
+ transport
+ ">");
contactHeader = StackConnector.headerFactory
.createContactHeader(address);
response.addHeader(contactHeader);
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (ParseException pe) {
pe.printStackTrace();
}
}
state = INITIALIZED;
useResponse = true;
// System.out.println("The following response has been initialized:\n"+
// response.toString());
}
/**
* Changes the default reason phrase.
* @param phrase the default reason phrase.
* @throws SipException INVALID_STATE if the response can not
* be initialized, because of wrong state.
* INVALID_OPERATION if the reason phrase can not be set.
* @throws IllegalArgumentException if the reason phrase is illegal.
*/
public void setReasonPhrase(String phrase)
throws SipException, IllegalArgumentException {
if (state != INITIALIZED)
throw new SipException("the Reason Phrase can not be set,"
+ " because of wrong state.",
SipException.INVALID_STATE);
if (phrase == null) {
throw new
IllegalArgumentException("The reason phrase can not be null.");
}
// RFC 3261, section 7.2: No CR or LF is allowed
// (in the Status Line) except in the final CRLF sequence.
if ((phrase.indexOf("\n") != -1) || (phrase.indexOf("\r") != -1)) {
throw new
IllegalArgumentException("Invalid reason phrase.");
}
response.setReasonPhrase(phrase);
}
/**
* (non-Javadoc)
* @see javax.microedition.sip.SipConnection#send()
*/
public void send()
throws IOException, InterruptedIOException, SipException {
if (state == REQUEST_RECEIVED) {
throw new SipException("can not send response"
+ " because of wrong state.",
SipException.INVALID_STATE);
}
if ((state == COMPLETED) && !resend2xxAllowed) {
throw new SipException("COMPLETED state allows"
+ " only resend of 2xx responses",
SipException.INVALID_STATE);
}
if (state == TERMINATED) {
throw new SipException("can not send response"
+ " because SipServerConnection is TERMINATED",
SipException.INVALID_STATE);
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"Actual request for the response we want to send:\n" +
request.toString());
}
ServerTransaction serverTransaction =
(ServerTransaction)request.getTransaction();
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"SipServerTransaction :" + serverTransaction);
}
// Set the sdp body of the message
if (contentOutputStream != null) {
contentOutputStream.setOpen(false);
response.setContent(
contentOutputStream.getByteArrayOutputStream().toByteArray());
contentOutputStream = null;
}
String method = request.getMethod();
final int statusCode = response.getStatusCode();
final int statusGroup = statusCode / 100;
// send the response
if (!resend2xxAllowed) { // don't create new transaction on resending
SipStack sipStack =
sipConnectionNotifierImpl.getStackConnector().getSipStack();
if (serverTransaction == null || sipStack.isDialogCreated(method)) {
try {
SipProvider sipProvider =
sipConnectionNotifierImpl.getSipProvider();
if (serverTransaction == null) {
serverTransaction =
sipProvider.getNewServerTransaction(request);
} else {
/*
* 12.1 Creation of a Dialog
* Dialogs are created through the generation of
* non-failure responses to requests with specific
* methods. Within this specification, only 2xx and
* 101-199 responses with a To tag, where the request
* was INVITE, will establish a dialog.
*/
if (statusCode > 100 && statusCode < 300) {
// Equip a dialog in case of dialog is null
// or its state is INITIALIZED only prevent
// changing contact property of dialog by
// sending 200 OK response for INVITE
// after sending 200 OK for UPDATE
boolean equipDialog = false;
if (sipDialog == null) {
equipDialog = true;
} else if (sipDialog.getState() ==
Dialog.INITIAL_STATE) {
equipDialog = true;
}
if (equipDialog) {
sipProvider.equipADialogForTransaction(
serverTransaction, request);
}
} else if ((sipDialog != null) &&
(statusGroup > 2 && statusGroup < 7)) {
// JSR180, p.42
// Terminated state: error response (3xx-6xx)
// received (or sent).
((SipDialogImpl)sipDialog).setState
(SipDialog.TERMINATED);
}
}
} catch (TransactionAlreadyExistsException taee) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"Exception in SSC.send(): " + taee);
taee.printStackTrace();
}
// return;
} catch (TransactionUnavailableException tue) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"Exception in SSC.send(): " + tue);
tue.printStackTrace();
}
// return;
}
}
}
SipConnectionNotifierImpl newNotifier = sipConnectionNotifierImpl;
if (sipDialog != null) {
Dialog dialog = ((SipDialogImpl)sipDialog).getDialog();
if (dialog != null &&
(statusCode > 199 && statusCode < 300)) {
if (method.equals(Request.UPDATE)) {
// processing UPDATE - change dialog contact property
dialog.addRoute(serverTransaction.getOriginalRequest());
}
newNotifier = findNotifier();
Transaction transaction = (Transaction)
dialog.getFirstTransaction();
if (transaction instanceof ServerTransaction) {
transaction.setApplicationData(newNotifier);
}
if (newNotifier != null) {
sipConnectionNotifierImpl = newNotifier;
}
}
}
// Set the application data so that when the request comes in,
// it will retrieve this SipConnectionNotifier
serverTransaction.setApplicationData(newNotifier);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"response to send : " + response);
}
// May throw IOException and SipException
serverTransaction.sendResponse(response);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"response sent");
}
// Change the state of SipServerConnection
int responseClass = response.getStatusCode()/100;
if (responseClass == 1) { // 1xx responses
state = REQUEST_RECEIVED;
} else {
state = COMPLETED;
if (responseClass == 2) {
// Allow 2xx response to be resent
resend2xxAllowed = true;
}
}
// Change the dialog state
changeDialogState(serverTransaction);
}
/**
* Finds a connection notifier that listens on the port given in the
* Contact header of the response. When a 2xx response contains a new
* Contact with the port that is different from one that is used by the
* connection notifier associated with this server connection object
* (sipConnectionNotifierImpl), the notifier must be changed accordingly.
* @returns SipConnectionNotifier listening on the new Contact or
* null if such notifier was not found.
*/
private SipConnectionNotifierImpl findNotifier() throws SipException {
// IMPL_NOTE: handle shared connections
int localPort = SIPConstants.DEFAULT_NONTLS_PORT;
String localTransport = SIPConstants.TRANSPORT_UDP;
ContactList contactList = response.getContactHeaders();
if (contactList != null) {
ContactHeader contact = (ContactHeader)contactList.getFirst();
HostPort hp = contact.getHostPort();
if (hp != null) {
localPort = hp.getPort();
}
URI uri = contact.getAddress().getURI();
String transport = uri.isSipURI() ?
((SipURI)uri).getTransportParam() : null;
if (transport != null) {
localTransport = transport;
}
}
// Find a SipConnectionNotifier listening on the given port.
StackConnector stackConnector =
sipConnectionNotifierImpl.getStackConnector();
SipConnectionNotifierImpl newNotifier = (SipConnectionNotifierImpl)
stackConnector.getSipConnectionNotifier(localPort,
sipConnectionNotifierImpl.getMIMEType());
return newNotifier;
}
/**
* Sets header value in SIP message. If the header does not exist
* it will be added to the message, otherwise the existing header is
* overwritten. If multiple header field values exist the topmost is
* overwritten. The implementations MAY restrict the access to some headers
* according to RFC 3261.
* @param name - name of the header, either in full or compact form.
* RFC 3261 p.32
* @param value - the header value
* @throws SipException - INVALID_STATE if header can not be set in
* this state. <br> INVALID_OPERATION if the system does not allow to set
* this header.
* @throws IllegalArgumentException - MAY be thrown if the header or
* value is invalid
*/
public void setHeader(String name, String value)
throws SipException, IllegalArgumentException {
if (state != INITIALIZED)
throw new SipException("the Header can not be set,"
+ " because of wrong state.",
SipException.INVALID_STATE);
if (name == null)
throw new
IllegalArgumentException("The header name can not be null");
if (value == null)
throw new
IllegalArgumentException("The header value can not be null");
Header header = null;
try {
header = StackConnector.headerFactory.createHeader(name, value);
} catch (ParseException pe) {
throw new IllegalArgumentException(pe.getMessage());
}
if (header == null)
throw new IllegalArgumentException("null header!");
// response.attachHeader(header, false, true);
Message currentMessage = useResponse ? (Message)response :
(Message)request;
if (currentMessage == null) {
throw new SipException("Failure in setHeader(),"
+ " associated request or response is null",
SipException.INVALID_MESSAGE);
}
currentMessage.attachHeader(header, true, true);
}
/**
* Adds a header to the SIP message. If multiple header field values exist
* the header value is added topmost of this type of headers.
* The implementations MAY restrict the access to some headers
* according to RFC 3261.
* @param name - name of the header, either in full or compact form.
* RFC 3261 p.32
* @param value - the header value
* @throws SipException - INVALID_STATE if header can not be added in
* this state. <br> INVALID_OPERATION if the system does not allow to add
* this header.
* @throws IllegalArgumentException - MAY be thrown if the header or
* value is invalid
*/
public void addHeader(String name, String value)
throws SipException, IllegalArgumentException {
if (state != INITIALIZED)
throw new SipException("the Header can not be add,"
+ " because of wrong state.",
SipException.INVALID_STATE);
if (name == null)
throw new
IllegalArgumentException("The header name can not be null");
if (value == null)
throw new
IllegalArgumentException("The header value can not be null");
Header header = null;
try {
header = StackConnector.headerFactory.createHeader(name, value);
} catch (ParseException pe) {
throw new IllegalArgumentException("The header can not be created,"
+ " check if it is correct");
}
Message currentMessage = useResponse ? (Message)response :
(Message)request;
if (currentMessage == null) {
throw new SipException("Failure in addHeader(),"
+ " associated request or response is null",
SipException.INVALID_MESSAGE);
}
currentMessage.addHeader(header);
}
/**
* Removes header from the SIP message. If multiple header field
* values exist the topmost is removed.
* The implementations MAY restrict the access to some headers
* according to RFC 3261.
* If the named header is not found this method does nothing.
* @param name - name of the header to be removed, either int
* full or compact form RFC 3261 p.32.
* @throws SipException - INVALID_STATE if header can not be removed in
* this state. <br> INVALID_OPERATION if the system does not allow to remove
* this header.
*/
public void removeHeader(String name)
throws SipException, IllegalArgumentException {
if (state != INITIALIZED) {
throw new SipException("the Header can not be removed,"
+ " because of wrong state.",
SipException.INVALID_STATE);
}
if (name == null) {
throw new
IllegalArgumentException("The header name can not be null");
}
Message currentMessage = useResponse ? (Message)response :
(Message)request;
if (currentMessage == null) {
throw new SipException("Failure in removeHeader(),"
+ " associated request or response is null",
SipException.INVALID_MESSAGE);
}
currentMessage.removeHeader(name, true);
}
/**
* Gets the header field value(s) of specified header type
* @param name - name of the header, either in full or compact form.
* RFC 3261 p.32
* @return array of header field values (topmost first), or null if the
* current message does not have such a header or the header is for other
* reason not available (e.g. message not initialized).
*/
public String[] getHeaders(String name) {
Message currentMessage = useResponse ? (Message)response :
(Message)request;
// Return null if associated request or response is null
if (currentMessage == null) {
return null;
}
HeaderList nameList = currentMessage.getHeaderList(name);
if (nameList == null) {
return null;
}
int size = nameList.size();
if (size < 1) {
return null;
}
String[] headerValues = new String[size];
for (int count = 0; count < size; count++) {
headerValues[count] =
((Header)nameList.elementAt(count)).getHeaderValue();
}
return headerValues;
}
/**
* Gets the header field value of specified header type.
* @param name - name of the header type, either in full or compact form.
* RFC 3261 p.32
* @return topmost header field value, or null if the
* current message does not have such a header or the header is for other
* reason not available (e.g. message not initialized).
*/
public String getHeader(String name) {
Message currentMessage = useResponse ? (Message)response :
(Message)request;
// Return null if associated request or response is null
if (currentMessage == null) {
return null;
}
Header header = currentMessage.getHeader(name);
if (header == null) {
return null;
}
return header.getHeaderValue();
}
/**
* Gets the SIP method. Applicable when a message has been
* initialized or received.
* @return SIP method name REGISTER, INVITE, NOTIFY, etc. Returns null if
* the method is not available.
*/
public String getMethod() {
if (TERMINATED == state) {
return null;
} else {
return request.getMethod();
}
}
/**
* Gets Request-URI. Available when SipClientConnection is in Initialized
* state or when SipServerConnection is in Request Received state.
* Built from the original URI given in Connector.open().
* See RFC 3261 p.35 (8.1.1.1 Request-URI)
* @return Request-URI of the message. Returns null if the Request-URI
* is not available.
*/
public String getRequestURI() {
// from the JSR180 spec:
// "Returns null if the Request-URI is not available...
// Available when... SipServerConnection is in Request Received state."
// Most likely, because only in this case there's no ambiguity whether
// the request or the response was meant.
if (REQUEST_RECEIVED != state) {
return null;
} else {
return request.getRequestURI().toString();
}
}
/**
* Gets SIP response status code. Available when SipClientConnection is in
* Proceeding or Completed state or when SipServerConnection is in
* Initialized state.
* @return status code 1xx, 2xx, 3xx, 4xx, ... Returns 0 if the status code
* is not available.
*/
public int getStatusCode() {
if (state != INITIALIZED || response == null) {
return 0;
} else {
return response.getStatusCode();
}
}
/**
* Gets SIP response reason phrase. Available when SipClientConnection is in
* Proceeding or Completed state or when SipServerConnection is in
* Initialized state.
* @return reason phrase. Returns null if the reason phrase is
* not available.
*/
public String getReasonPhrase() {
if (state != INITIALIZED || response == null) {
return null;
} else {
return response.getReasonPhrase();
}
}
/**
* Returns the current SIP dialog. This is available when the SipConnection
* belongs to a created SipDialog and the system has received (or sent)
* provisional (101-199) or final response (200).
* @return SipDialog object if this connection belongs to a dialog,
* otherwise returns null.
*/
public SipDialog getDialog() {
if (sipDialog != null) {
byte dialogState = this.sipDialog.getState();
if ((dialogState != SipDialog.EARLY) &&
(dialogState != SipDialog.CONFIRMED)) {
return null;
}
}
return sipDialog;
}
/**
* Returns InputStream to read SIP message body content.
* @return InputStream to read body content
* @throws java.io.IOException - if the InputStream can not be opened,
* because of an I/O error occurred.
* @throws SipException - INVALID_STATE the InputStream can not be opened
* in this state (e.g. no message received).
*/
public InputStream openContentInputStream()
throws IOException, SipException {
if (state != REQUEST_RECEIVED)
throw new SipException("the content input stream can not be open,"
+ " because of wrong state: " + state,
SipException.INVALID_STATE);
if (request == null) {
throw new IOException("Request is null.");
}
ContentLengthHeader contentLengthHeader =
request.getContentLengthHeader();
if (contentLengthHeader == null) {
throw new
IOException("Request contains no content length header.");
}
int bodyLength = contentLengthHeader.getContentLength();
if (bodyLength == 0) {
throw new IOException("Request's body has zero length.");
}
byte[] buf = request.getRawContent();
if (buf == null) { // body is empty
throw new IOException("Body of SIP request is empty.");
}
contentInputStream = new ByteArrayInputStream(buf);
return contentInputStream;
}
/**
* Returns OutputStream to fill the SIP message body content.
* When calling close() on OutputStream the message will be sent
* to the network. So it is equivalent to call send(). Again send() must
* not be called after closing the OutputStream, since it will throw
* Exception because of calling the method in wrong state.
* Before opening OutputStream the Content-Length and Content-Type headers
* has to se set. If not SipException.UNKNOWN_LENGTH or
* SipException.UNKNOWN_TYPE will be thrown respectively.
* @return OutputStream to write body content
* @throws IOException if the OutputStream can not be opened,
* because of an I/O error occurred.
* @throws SipException INVALID_STATE the OutputStream can not be opened
* in this state (e.g. no message initialized).
* UNKNOWN_LENGTH Content-Length header not set.
* UNKNOWN_TYPE Content-Type header not set.
*/
public OutputStream openContentOutputStream()
throws IOException, SipException {
if (state != INITIALIZED)
throw new SipException("the content output strean can not be open,"
+ " because of wrong state.",
SipException.INVALID_STATE);
if (state == TERMINATED) {
throw new IOException("can not open content output stream"
+ " because SipServerConnection is TERMINATED");
}
if (response.getHeader(Header.CONTENT_TYPE) == null)
throw new SipException("Content-Type unknown, set the"
+ " content-type header first",
SipException.UNKNOWN_TYPE);
if (response.getHeader(Header.CONTENT_LENGTH) == null)
throw new SipException("Content-Length unknown, set the "
+ "content-length header first",
SipException.UNKNOWN_LENGTH);
contentOutputStream = new SDPOutputStream(this);
state = STREAM_OPEN;
return contentOutputStream;
}
/**
* Closes the connection.
* @exception IOException if an I/O error occurs
* @see javax.microedition.io.Connection#close()
*/
public void close() throws IOException {
state = TERMINATED;
}
/**
* Change the state of the dialog after sending a response.
* @param serverTransaction current transaction
*/
private void changeDialogState(ServerTransaction serverTransaction) {
// System.out.println(">>> SERVER: changing state, " +
// response.getCSeqHeader().getMethod());
int statusCode = response.getStatusCode();
if (statusCode == 100 || sipDialog == null) {
return;
}
String cseqMethod = response.getCSeqHeader().getMethod();
SipDialogImpl sipDialogImpl = (SipDialogImpl)sipDialog;
if (cseqMethod.equals(Request.NOTIFY)) {
// System.out.println(">>> SERVER: NOTIFY!!!");
sipDialogImpl.handleNotify(request,
serverTransaction.getDialog(), null);
return;
}
// Default is to not create a dialog.
// The dialog only is created for the methods that are known
// to establish a dialog.
int statusGroup = statusCode / 100;
if (sipConnectionNotifierImpl.getStackConnector().getSipStack().
isDialogCreated(cseqMethod) || cseqMethod.equals(Request.BYE)) {
if (statusGroup == 2) {
// RFC 3261, section 13.2.2.4:
// If the dialog identifier in the 2xx response matches the
// dialog identifier of an existing dialog, the dialog MUST
// be transitioned to the "confirmed" state.
sipDialogImpl.setDialog(serverTransaction.getDialog());
sipDialogImpl.setState(SipDialog.CONFIRMED);
if (statusCode == 200) {
if (cseqMethod.equals(Request.SUBSCRIBE) ||
cseqMethod.equals(Request.REFER)) {
sipDialogImpl.addSubscription(new Subscription(
sipDialogImpl.getDialog(), request));
} else if (cseqMethod.equals(Request.BYE)) {
sipDialogImpl.setWaitForBye(false);
sipDialogImpl.terminateIfNoSubscriptions();
}
}
} else if (statusGroup == 1) {
// provisional response
if (sipDialog.getState() == SipDialogImpl.INITIALIZED) {
// switch to EARLY state
sipDialogImpl.setState(SipDialog.EARLY);
}
} else { // another response code - switch to TERMINATED state
sipDialogImpl.terminateIfNoSubscriptions();
}
// set dialog ID if need
if (sipDialogImpl.getDialogID() == null) {
int state = sipDialog.getState();
if ((state == SipDialog.EARLY) ||
(state == SipDialog.CONFIRMED)) {
sipDialogImpl.setDialog(serverTransaction.getDialog());
sipDialogImpl.setDialogID(response.getDialogId(true));
}
}
}
}
/**
* Return the state of SIP server connection
*
* @return state of the SIP Server Connection
*/
public int getState() {
return state;
}
}
|