diff --git a/src/gov/nist/javax/sip/SipStackImpl.java b/src/gov/nist/javax/sip/SipStackImpl.java index 033ab88df..8b45d9f78 100755 --- a/src/gov/nist/javax/sip/SipStackImpl.java +++ b/src/gov/nist/javax/sip/SipStackImpl.java @@ -1428,6 +1428,9 @@ public SipStackImpl(Properties configurationProperties) super.setPatchRport(Boolean.parseBoolean(configurationProperties.getProperty( "gov.nist.javax.sip.ALWAYS_ADD_RPORT", "false"))); + super.setPatchReceivedRport(Boolean.parseBoolean(configurationProperties.getProperty( + "gov.nist.javax.sip.NEVER_ADD_RECEIVED_RPORT", "false"))); + super.cancelClientTransactionChecked = configurationProperties .getProperty( "gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", diff --git a/src/gov/nist/javax/sip/stack/ConnectionOrientedMessageChannel.java b/src/gov/nist/javax/sip/stack/ConnectionOrientedMessageChannel.java index 98d7dceb1..8181da0d8 100644 --- a/src/gov/nist/javax/sip/stack/ConnectionOrientedMessageChannel.java +++ b/src/gov/nist/javax/sip/stack/ConnectionOrientedMessageChannel.java @@ -335,6 +335,7 @@ public void processMessage(SIPMessage sipMessage) throws Exception { } } + if (!sipStack.isPatchReceivedRport()) { try { if (mySock != null) { // selfrouting makes socket = null // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=297 @@ -374,6 +375,9 @@ public void processMessage(SIPMessage sipMessage) throws Exception { } catch (java.text.ParseException ex) { InternalErrorHandler.handleException(ex); } + } else { + logger.logInfo("We did not use recived and rport"); + } // Use this for outgoing messages as well. if (!this.isCached && mySock != null) { // self routing makes // mySock=null diff --git a/src/gov/nist/javax/sip/stack/SIPServerTransactionImpl.java b/src/gov/nist/javax/sip/stack/SIPServerTransactionImpl.java index 07ab09ef1..2f33fb68a 100644 --- a/src/gov/nist/javax/sip/stack/SIPServerTransactionImpl.java +++ b/src/gov/nist/javax/sip/stack/SIPServerTransactionImpl.java @@ -1,2168 +1,2170 @@ -/* - * Conditions Of Use - * - * This software was developed by employees of the National Institute of - * Standards and Technology (NIST), an agency of the Federal Government. - * Pursuant to title 15 Untied States Code Section 105, works of NIST - * employees are not subject to copyright protection in the United States - * and are considered to be in the public domain. As a result, a formal - * license is not needed to use the software. - * - * This software is provided by NIST as a service and is expressly - * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED - * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT - * AND DATA ACCURACY. NIST does not warrant or make any representations - * regarding the use of the software or the results thereof, including but - * not limited to the correctness, accuracy, reliability or usefulness of - * the software. - * - * Permission to use this software is contingent upon your acceptance - * of the terms of this agreement - * - * . - * - */ -package gov.nist.javax.sip.stack; - -import gov.nist.core.CommonLogger; -import gov.nist.core.HostPort; -import gov.nist.core.InternalErrorHandler; -import gov.nist.core.LogWriter; -import gov.nist.core.ServerLogger; -import gov.nist.core.StackLogger; -import gov.nist.javax.sip.ReleaseReferencesStrategy; -import gov.nist.javax.sip.SIPConstants; -import gov.nist.javax.sip.SipProviderImpl; -import gov.nist.javax.sip.Utils; -import gov.nist.javax.sip.header.Expires; -import gov.nist.javax.sip.header.ParameterNames; -import gov.nist.javax.sip.header.RSeq; -import gov.nist.javax.sip.header.Via; -import gov.nist.javax.sip.message.SIPMessage; -import gov.nist.javax.sip.message.SIPRequest; -import gov.nist.javax.sip.message.SIPResponse; -import gov.nist.javax.sip.stack.IllegalTransactionStateException.Reason; - -import java.io.IOException; -import java.net.InetAddress; -import java.text.ParseException; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import javax.sip.Dialog; -import javax.sip.DialogState; -import javax.sip.DialogTerminatedEvent; -import javax.sip.ObjectInUseException; -import javax.sip.SipException; -import javax.sip.Timeout; -import javax.sip.TimeoutEvent; -import javax.sip.TransactionState; -import javax.sip.address.Hop; -import javax.sip.header.ContactHeader; -import javax.sip.header.ContentTypeHeader; -import javax.sip.header.ExpiresHeader; -import javax.sip.header.RSeqHeader; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/* - * Bug fixes / enhancements:Emil Ivov, Antonis Karydas, Daniel J. Martinez Manzano, Daniel, Hagai - * Sela, Vazques-Illa, Bill Roome, Thomas Froment and Pierre De Rop, Christophe Anzille and Jeroen - * van Bemmel, Frank Reif. - * Carolyn Beeton ( Avaya ). - * - */ - -/** - * Represents a server transaction. Implements the following state machines. - * - *
- *
- *
- *
- *                                                                      |INVITE
- *                                                                      |pass INV to TU
- *                                                   INVITE             V send 100 if TU won't in 200ms
- *                                                   send response+-----------+
- *                                                       +--------|           |--------+101-199 from TU
- *                                                       |        | Proceeding|        |send response
- *                                                       +------->|           |<-------+
- *                                                                |           |          Transport Err.
- *                                                                |           |          Inform TU
- *                                                                |           |--------------->+
- *                                                                +-----------+                |
- *                                                   300-699 from TU |     |2xx from TU        |
- *                                                   send response   |     |send response      |
- *                                                                   |     +------------------>+
- *                                                                   |                         |
- *                                                   INVITE          V          Timer G fires  |
- *                                                   send response+-----------+ send response  |
- *                                                       +--------|           |--------+       |
- *                                                       |        | Completed |        |       |
- *                                                       +------->|           |<-------+       |
- *                                                                +-----------+                |
- *                                                                   |     |                   |
- *                                                               ACK |     |                   |
- *                                                               -   |     +------------------>+
- *                                                                   |        Timer H fires    |
- *                                                                   V        or Transport Err.|
- *                                                                +-----------+  Inform TU     |
- *                                                                |           |                |
- *                                                                | Confirmed |                |
- *                                                                |           |                |
- *                                                                +-----------+                |
- *                                                                      |                      |
- *                                                                      |Timer I fires         |
- *                                                                      |-                     |
- *                                                                      |                      |
- *                                                                      V                      |
- *                                                                +-----------+                |
- *                                                                |           |                |
- *                                                                | Terminated|<---------------+
- *                                                                |           |
- *                                                                +-----------+
- *
- *                                                     Figure 7: INVITE server transaction
- *                                                         Request received
- *                                                                         |pass to TU
- *
- *                                                                         V
- *                                                                   +-----------+
- *                                                                   |           |
- *                                                                   | Trying    |-------------+
- *                                                                   |           |             |
- *                                                                   +-----------+             |200-699 from TU
- *                                                                         |                   |send response
- *                                                                         |1xx from TU        |
- *                                                                         |send response      |
- *                                                                         |                   |
- *                                                      Request            V      1xx from TU  |
- *                                                      send response+-----------+send response|
- *                                                          +--------|           |--------+    |
- *                                                          |        | Proceeding|        |    |
- *                                                          +------->|           |<-------+    |
- *                                                   +<--------------|           |             |
- *                                                   |Trnsprt Err    +-----------+             |
- *                                                   |Inform TU            |                   |
- *                                                   |                     |                   |
- *                                                   |                     |200-699 from TU    |
- *                                                   |                     |send response      |
- *                                                   |  Request            V                   |
- *                                                   |  send response+-----------+             |
- *                                                   |      +--------|           |             |
- *                                                   |      |        | Completed |<------------+
- *                                                   |      +------->|           |
- *                                                   +<--------------|           |
- *                                                   |Trnsprt Err    +-----------+
- *                                                   |Inform TU            |
- *                                                   |                     |Timer J fires
- *                                                   |                     |-
- *                                                   |                     |
- *                                                   |                     V
- *                                                   |               +-----------+
- *                                                   |               |           |
- *                                                   +-------------->| Terminated|
- *                                                                   |           |
- *                                                                   +-----------+
- *
- *
- *
- *
- *
- * 
- * - * @version 1.2 $Revision: 1.150 $ $Date: 2010-12-02 22:04:15 $ - * @author M. Ranganathan - * - */ -public class SIPServerTransactionImpl extends SIPTransactionImpl implements SIPServerTransaction { - private static StackLogger logger = CommonLogger.getLogger(SIPServerTransaction.class); - private int rseqNumber = -1; - - // private LinkedList pendingRequests; - - // Real RequestInterface to pass messages to - private transient ServerRequestInterface requestOf; - - private SIPDialog dialog; - // jeand needed because we nullify the dialog ref early and keep only the dialogId to save on mem and help GC - protected String dialogId; - - // the unacknowledged SIPResponse - -// private SIPResponse pendingReliableResponse; - // wondering if the pendingReliableResponseAsBytes could be put into the lastResponseAsBytes - private byte[] pendingReliableResponseAsBytes; - private String pendingReliableResponseMethod; - private long pendingReliableCSeqNumber; - private long pendingReliableRSeqNumber; - private ContentTypeHeader pendingReliableResponseContentType; - - // The pending reliable Response Timer - private ProvisionalResponseTask provisionalResponseTask; - - private boolean retransmissionAlertEnabled; - - private RetransmissionAlertTimerTask retransmissionAlertTimerTask; - - protected boolean isAckSeen; - - private SIPClientTransaction pendingSubscribeTransaction; - - private SIPServerTransaction inviteTransaction; - - // Experimental. - private static boolean interlockProvisionalResponses = true; - - private Semaphore provisionalResponseSem = new Semaphore(1); - - private Semaphore terminationSemaphore = new Semaphore(0); - - // jeand we nullify the last response fast to save on mem and help GC, but we keep only the information needed - private byte[] lastResponseAsBytes; - private String lastResponseHost; - private int lastResponsePort; - private String lastResponseTransport; - - private int lastResponseStatusCode; - - private HostPort originalRequestSentBy; - private String originalRequestFromTag; - - - /** - * This timer task is used for alerting the application to send retransmission alerts. - * - * - */ - class RetransmissionAlertTimerTask extends SIPStackTimerTask { - - String dialogId; - - int ticks; - - int ticksLeft; - - public RetransmissionAlertTimerTask(String dialogId) { - - this.ticks = SIPTransactionImpl.T1; - this.ticksLeft = this.ticks; - // Fix from http://java.net/jira/browse/JSIP-443 - // by mitchell.c.ackerman - this.dialogId = dialogId; - } - - public void runTask() { - SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; - ticksLeft--; - if (ticksLeft == -1) { - serverTransaction.fireRetransmissionTimer(); - this.ticksLeft = 2 * ticks; - } - - } - - @Override - public Object getThreadHash() { - Request request = getRequest(); - if (request != null && request instanceof SIPRequest) { - return ((SIPRequest)request).getCallIdHeader().getCallId(); - } else { - return null; - } - } - - } - - class ProvisionalResponseTask extends SIPStackTimerTask { - - int ticks; - - int ticksLeft; - - public ProvisionalResponseTask() { - this.ticks = SIPTransactionImpl.T1; - this.ticksLeft = this.ticks; - } - - public void runTask() { - SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; - /* - * The reliable provisional response is passed to the transaction layer periodically - * with an interval that starts at T1 seconds and doubles for each retransmission (T1 - * is defined in Section 17 of RFC 3261). Once passed to the server transaction, it is - * added to an internal list of unacknowledged reliable provisional responses. The - * transaction layer will forward each retransmission passed from the UAS core. - * - * This differs from retransmissions of 2xx responses, whose intervals cap at T2 - * seconds. This is because retransmissions of ACK are triggered on receipt of a 2xx, - * but retransmissions of PRACK take place independently of reception of 1xx. - */ - // If the transaction has terminated, - if (serverTransaction.isTerminated()) { - - sipStack.getTimer().cancel(this); - - } else { - ticksLeft--; - if (ticksLeft == -1) { - serverTransaction.fireReliableResponseRetransmissionTimer(); - this.ticksLeft = 2 * ticks; - this.ticks = this.ticksLeft; - // timer H MUST be set to fire in 64*T1 seconds for all transports. Timer H - // determines when the server - // transaction abandons retransmitting the response - if (this.ticksLeft >= SIPTransactionImpl.TIMER_H) { - sipStack.getTimer().cancel(this); - setState(TransactionState._TERMINATED); - fireTimeoutTimer(); - } - } - - } - - } - - @Override - public Object getThreadHash() { - Request request = getRequest(); - if (request != null && request instanceof SIPRequest) { - return ((SIPRequest)request).getCallIdHeader().getCallId(); - } else { - return null; - } - } - - } - - /** - * This timer task is for INVITE server transactions. It will send a trying in 200 ms. if the - * TU does not do so. - * - */ - class SendTrying extends SIPStackTimerTask { - - protected SendTrying() { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("scheduled timer for " + SIPServerTransactionImpl.this); - - } - - public void runTask() { - SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; - - int realState = serverTransaction.getRealState(); - - if (realState < 0 || TransactionState._TRYING == realState) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug(" sending Trying current state = " - + serverTransaction.getRealState()); - try { - serverTransaction.sendMessage(serverTransaction.getOriginalRequest() - .createResponse(100, "Trying")); - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug(" trying sent " - + serverTransaction.getRealState()); - } catch (IOException ex) { - if (logger.isLoggingEnabled()) - logger.logError("IO error sending TRYING"); - } - } - - } - - @Override - public Object getThreadHash() { - Request request = getRequest(); - if (request != null && request instanceof SIPRequest) { - return ((SIPRequest)request).getCallIdHeader().getCallId(); - } else { - return null; - } - } - } - - class TransactionTimer extends SIPStackTimerTask { - - public TransactionTimer() { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("TransactionTimer() : " + getTransactionId()); - } - - } - - public void runTask() { - // If the transaction has terminated, - if (isTerminated()) { - // Keep the transaction hanging around in the transaction table - // to catch the incoming ACK -- this is needed for tcp only. - // Note that the transaction record is actually removed in - // the connection linger timer. - try { - sipStack.getTimer().cancel(this); - } catch (IllegalStateException ex) { - if (!sipStack.isAlive()) - return; - } - - - // Oneshot timer that garbage collects the SeverTransaction - // after a scheduled amount of time. The linger timer allows - // the client side of the tx to use the same connection to - // send an ACK and prevents a race condition for creation - // of new server tx - SIPStackTimerTask myTimer = new LingerTimer(); - - if(sipStack.getConnectionLingerTimer() != 0) { - sipStack.getTimer().schedule(myTimer, sipStack.getConnectionLingerTimer() * 1000); - } else { - myTimer.runTask(); - } - } else { - // Add to the fire list -- needs to be moved - // outside the synchronized block to prevent - // deadlock. - fireTimer(); - } - if(originalRequest != null) { - originalRequest.cleanUp(); - } - } - - @Override - public Object getThreadHash() { - Request request = getRequest(); - if (request != null && request instanceof SIPRequest) { - return ((SIPRequest)request).getCallIdHeader().getCallId(); - } else { - return null; - } - } - - } - - /** - * Send a response. - * - * @param transactionResponse -- the response to send - * - */ - - protected void sendResponse(SIPResponse transactionResponse) throws IOException { - if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("sipServerTransaction::sendResponse " + transactionResponse.getFirstLine()); - } - try { - // RFC18.2.2. Sending Responses - // The server transport uses the value of the top Via header field - // in - // order - // to determine where to send a response. - // It MUST follow the following process: - // If the "sent-protocol" is a reliable transport - // protocol such as TCP or SCTP, - // or TLS over those, the response MUST be - // sent using the existing connection - // to the source of the original request - // that created the transaction, if that connection is still open. - if (isReliable()) { - - getMessageChannel().sendMessage(transactionResponse); - - - } else { - Via via = transactionResponse.getTopmostVia(); - String transport = via.getTransport(); - if (transport == null) - throw new IOException("missing transport!"); - // @@@ hagai Symmetric NAT support - int port = via.getRPort(); - if (port == -1) - port = via.getPort(); - if (port == -1) { - if (transport.equalsIgnoreCase("TLS")) - port = 5061; - else - port = 5060; - } - - // Otherwise, if the Via header field value contains a - // "maddr" parameter, the response MUST be forwarded to - // the address listed there, using the port indicated in - // "sent-by", - // or port 5060 if none is present. If the address is a - // multicast - // address, the response SHOULD be sent using - // the TTL indicated in the "ttl" parameter, or with a - // TTL of 1 if that parameter is not present. - String host = null; - if (via.getMAddr() != null) { - host = via.getMAddr(); - } else { - // Otherwise (for unreliable unicast transports), - // if the top Via has a "received" parameter, the response - // MUST - // be sent to the - // address in the "received" parameter, using the port - // indicated - // in the - // "sent-by" value, or using port 5060 if none is specified - // explicitly. - host = via.getParameter(Via.RECEIVED); - if (host == null) { - // Otherwise, if it is not receiver-tagged, the response - // MUST be - // sent to the address indicated by the "sent-by" value, - // using the procedures in Section 5 - // RFC 3263 PROCEDURE TO BE DONE HERE - host = via.getHost(); - } - } - - Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(host, port, - transport)); - - MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack()) - .createRawMessageChannel(this.getSipProvider().getListeningPoint( - hop.getTransport()).getIPAddress(), this.getPort(), hop); - if (messageChannel != null) { - messageChannel.sendMessage(transactionResponse); - lastResponseHost = host; - lastResponsePort = port; - lastResponseTransport = transport; - } else { - throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+ - this.getSipProvider().getListeningPoint( - hop.getTransport()).getIPAddress() + ":" + this.getPort()); - } - - } - lastResponseAsBytes = transactionResponse.encodeAsBytes(this.getTransport()); - lastResponse = null; - } finally { - this.startTransactionTimer(); - } - } - - /** - * Creates a new server transaction. - * - * @param sipStack Transaction stack this transaction belongs to. - * @param newChannelToUse Channel to encapsulate. - */ - protected SIPServerTransactionImpl(SIPTransactionStack sipStack, MessageChannel newChannelToUse) { - - super(sipStack, newChannelToUse); - - // Only one outstanding request for a given server tx. - - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("Creating Server Transaction" + this.getBranchId()); - logger.logStackTrace(); - } - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setRequestInterface(gov.nist.javax.sip.stack.ServerRequestInterface) - */ - @Override - public void setRequestInterface(ServerRequestInterface newRequestOf) { - - requestOf = newRequestOf; - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getResponseChannel() - */ - @Override - public MessageChannel getResponseChannel() { - - return encapsulatedChannel; - - } - - - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#isMessagePartOfTransaction(gov.nist.javax.sip.message.SIPMessage) - */ - @Override - public boolean isMessagePartOfTransaction(SIPMessage messageToTest) { - - // List of Via headers in the message to test -// ViaList viaHeaders; - - // Flags whether the select message is part of this transaction - boolean transactionMatches = false; - final String method = messageToTest.getCSeq().getMethod(); - SIPRequest origRequest = getOriginalRequest(); - // Invite Server transactions linger in the terminated state in the - // transaction - // table and are matched to compensate for - // http://bugs.sipit.net/show_bug.cgi?id=769 - if (isInviteTransaction() || !isTerminated()) { - - // Get the topmost Via header and its branch parameter - final Via topViaHeader = messageToTest.getTopmostVia(); - if (topViaHeader != null) { - -// topViaHeader = (Via) viaHeaders.getFirst(); - // Branch code in the topmost Via header - String messageBranch = topViaHeader.getBranch(); - if (messageBranch != null) { - - // If the branch parameter exists but - // does not start with the magic cookie, - if (!messageBranch.toLowerCase().startsWith( - SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) { - - // Flags this as old - // (RFC2543-compatible) client - // version - messageBranch = null; - - } - - } - - // If a new branch parameter exists, - if (messageBranch != null && this.getBranch() != null) { - if (method.equals(Request.CANCEL)) { - // Cancel is handled as a special case because it - // shares the same same branch id of the invite - // that it is trying to cancel. - transactionMatches = this.getMethod().equals(Request.CANCEL) - && getBranch().equalsIgnoreCase(messageBranch) - && topViaHeader.getSentBy().equals( - origRequest.getTopmostVia() - .getSentBy()); - - } else { - // Matching server side transaction with only the - // branch parameter. - if(origRequest != null) { - transactionMatches = getBranch().equalsIgnoreCase(messageBranch) - && topViaHeader.getSentBy().equals( - origRequest.getTopmostVia() - .getSentBy()); - } else { - transactionMatches = getBranch().equalsIgnoreCase(messageBranch) - && topViaHeader.getSentBy().equals(originalRequestSentBy); - } - - } - - } else { - // force the reparsing only on non RFC 3261 messages - origRequest = (SIPRequest) getRequest(); - - // This is an RFC2543-compliant message; this code is here - // for backwards compatibility. - // It is a weak check. - // If RequestURI, To tag, From tag, CallID, CSeq number, and - // top Via headers are the same, the - // SIPMessage matches this transaction. An exception is for - // a CANCEL request, which is not deemed - // to be part of an otherwise-matching INVITE transaction. - String originalFromTag = origRequest.getFromTag(); - - String thisFromTag = messageToTest.getFrom().getTag(); - - boolean skipFrom = (originalFromTag == null || thisFromTag == null); - - String originalToTag = origRequest.getToTag(); - - String thisToTag = messageToTest.getTo().getTag(); - - boolean skipTo = (originalToTag == null || thisToTag == null); - boolean isResponse = (messageToTest instanceof SIPResponse); - // Issue #96: special case handling for a CANCEL request - - // the CSeq method of the original request must - // be CANCEL for it to have a chance at matching. - if (messageToTest.getCSeq().getMethod().equalsIgnoreCase(Request.CANCEL) - && !origRequest.getCSeq().getMethod().equalsIgnoreCase( - Request.CANCEL)) { - transactionMatches = false; - } else if ((isResponse || origRequest.getRequestURI().equals( - ((SIPRequest) messageToTest).getRequestURI())) - && (skipFrom || originalFromTag != null && originalFromTag.equalsIgnoreCase(thisFromTag)) - && (skipTo || originalToTag != null && originalToTag.equalsIgnoreCase(thisToTag)) - && origRequest.getCallId().getCallId().equalsIgnoreCase( - messageToTest.getCallId().getCallId()) - && origRequest.getCSeq().getSeqNumber() == messageToTest - .getCSeq().getSeqNumber() - && ((!messageToTest.getCSeq().getMethod().equals(Request.CANCEL)) || - getMethod().equals(messageToTest.getCSeq().getMethod())) - && topViaHeader.equals(origRequest.getTopmostVia())) { - - transactionMatches = true; - } - - } - - } - - } - return transactionMatches; - - } - - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#map() - */ - @Override - public void map() { - // note that TRYING is a pseudo-state for invite transactions - - int realState = getRealState(); - - if (realState < 0 || realState == TransactionState._TRYING) { - // Also sent by intermediate proxies. - // null check added as the stack may be stopped. TRYING is not sent by reliable transports. - if (isInviteTransaction() && !this.isMapped && sipStack.getTimer() != null ) { - this.isMapped = true; - // Schedule a timer to fire in 200 ms if the - // TU did not send a trying in that time. - sipStack.getTimer().schedule(new SendTrying(), 200); - - } else { - isMapped = true; - } - } - - // Pull it out of the pending transactions list. - sipStack.removePendingTransaction(this); - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#processRequest(gov.nist.javax.sip.message.SIPRequest, gov.nist.javax.sip.stack.MessageChannel) - */ - @Override - public void processRequest(SIPRequest transactionRequest, MessageChannel sourceChannel) { - boolean toTu = false; - - // Can only process a single request directed to the - // transaction at a time. For a given server transaction - // the listener sees only one event at a time. - - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("processRequest: " + transactionRequest.getFirstLine()); - logger.logDebug("tx state = " + this.getRealState()); - } - - try { - - // If this is the first request for this transaction, - if (getRealState() < 0) { - // Save this request as the one this - // transaction is handling - setOriginalRequest(transactionRequest); - this.setState(TransactionState._TRYING); - toTu = true; - this.setPassToListener(); - - // Rsends the TRYING on retransmission of the request. - if (isInviteTransaction() && this.isMapped) { - // JvB: also - // proxies need - // to do this - - // Has side-effect of setting - // state to "Proceeding" - sendMessage(transactionRequest.createResponse(100, "Trying")); - - } - // If an invite transaction is ACK'ed while in - // the completed state, - } else if (isInviteTransaction() && TransactionState._COMPLETED == getRealState() - && transactionRequest.getMethod().equals(Request.ACK)) { - - // @jvB bug fix - this.setState(TransactionState._CONFIRMED); - disableRetransmissionTimer(); - if (!isReliable()) { - enableTimeoutTimer(timerI); - - } else { - - this.setState(TransactionState._TERMINATED); - - } - - - // JvB: For the purpose of testing a TI, added a property to - // pass it anyway - if (sipStack.isNon2XXAckPassedToListener()) { - // This is useful for test applications that want to see - // all messages. - requestOf.processRequest(transactionRequest, encapsulatedChannel); - } else { - // According to RFC3261 Application should not Ack in - // CONFIRMED state - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("ACK received for server Tx " - + this.getTransactionId() + " not delivering to application!"); - - } - - this.semRelease(); - } - return; - - // If we receive a retransmission of the original - // request, - } else if (transactionRequest.getMethod().equals(getMethod())) { - - if (TransactionState._PROCEEDING == getRealState() - || TransactionState._COMPLETED == getRealState()) { - this.semRelease(); - // Resend the last response to - // the client - // Send the message to the client - resendLastResponseAsBytes(); - } else if (transactionRequest.getMethod().equals(Request.ACK)) { - // This is passed up to the TU to suppress - // retransmission of OK - if (requestOf != null) - requestOf.processRequest(transactionRequest, encapsulatedChannel); - else - this.semRelease(); - } else { - // none of the above? well release the lock anyhow! - this.semRelease(); - } - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("completed processing retransmitted request : " - + transactionRequest.getFirstLine() + this + " txState = " - + this.getState() + " lastResponse = " + this.lastResponseAsBytes); - return; - - } - - // Pass message to the TU - if (TransactionState._COMPLETED != getRealState() - && TransactionState._TERMINATED != getRealState() && requestOf != null) { - if (getMethod().equals(transactionRequest.getMethod())) { - // Only send original request to TU once! - if (toTu) { - requestOf.processRequest(transactionRequest, encapsulatedChannel); - } else - this.semRelease(); - } else { - if (requestOf != null) - requestOf.processRequest(transactionRequest, encapsulatedChannel); - else - this.semRelease(); - } - } else { - // This seems like a common bug so I am allowing it through! - if (SIPTransactionStack.isDialogCreated(getMethod()) - && getRealState() == TransactionState._TERMINATED - && transactionRequest.getMethod().equals(Request.ACK) - && requestOf != null) { - SIPDialog thisDialog = (SIPDialog) getDialog(); - - if (thisDialog == null || !thisDialog.ackProcessed) { - // Filter out duplicate acks - if (thisDialog != null) { - thisDialog.ackReceived(transactionRequest.getCSeq().getSeqNumber()); - thisDialog.ackProcessed = true; - } - requestOf.processRequest(transactionRequest, encapsulatedChannel); - } else { - this.semRelease(); - } - - } else if (transactionRequest.getMethod().equals(Request.CANCEL)) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("Too late to cancel Transaction"); - this.semRelease(); - // send OK and just ignore the CANCEL. - try { - this.sendMessage(transactionRequest.createResponse(Response.OK)); - } catch (IOException ex) { - // Transaction is already terminated - // just ignore the IOException. - } - } else { - // none of the above? well release the lock anyhow! - this.semRelease(); - } - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("Dropping request " + getRealState()); - } - - } catch (IOException e) { - if (logger.isLoggingEnabled()) - logger.logError("IOException " ,e); - this.semRelease(); - this.raiseIOExceptionEvent(); - } - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#sendMessage(gov.nist.javax.sip.message.SIPMessage) - */ - @Override - public void sendMessage(SIPMessage messageToSend) throws IOException { - if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("sipServerTransaction::sendMessage " + messageToSend.getFirstLine()); - } - // Message typecast as a response - final SIPResponse transactionResponse = (SIPResponse) messageToSend; - // Status code of the response being sent to the client - final int statusCode = transactionResponse.getStatusCode(); - try { - - try { - // Provided we have set the banch id for this we set the BID for - // the - // outgoing via. - if (originalRequestBranch != null) - transactionResponse.getTopmostVia().setBranch(this.getBranch()); - else - transactionResponse.getTopmostVia().removeParameter(ParameterNames.BRANCH); - - // Make the topmost via headers match identically for the - // transaction rsponse. - if (!originalRequestHasPort) - transactionResponse.getTopmostVia().removePort(); - } catch (ParseException ex) { - logger.logError("UnexpectedException",ex); - throw new IOException("Unexpected exception"); - } - - // Method of the response does not match the request used to - // create the transaction - transaction state does not change. - if (!transactionResponse.getCSeq().getMethod().equals( - getMethod())) { - sendResponse(transactionResponse); - return; - } - - if(!checkStateTimers(statusCode)) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("checkStateTimers returned false -- not sending message"); - } - return; - } - - try { - // Send the message to the client. - // Record the last message sent out. - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug( - "sendMessage : tx = " + this + " getState = " + this.getState()); - } - lastResponse = transactionResponse; - lastResponseStatusCode = transactionResponse.getStatusCode(); - - this.sendResponse(transactionResponse); - - } catch (IOException e) { - - this.setState(TransactionState._TERMINATED); - this.collectionTime = 0; - throw e; - - } - } finally { - this.startTransactionTimer(); - } - - } - - - private boolean checkStateTimers(int statusCode) { - // If the TU sends a provisional response while in the - // trying state, - - if (getRealState() == TransactionState._TRYING) { - if (statusCode / 100 == 1) { - this.setState(TransactionState._PROCEEDING); - } else if (200 <= statusCode && statusCode <= 699) { - // INVITE ST has TRYING as a Pseudo state - // (See issue 76). We are using the TRYING - // pseudo state invite Transactions - // to signal if the application - // has sent trying or not and hence this - // check is necessary. - if (!isInviteTransaction()) { - if (!isReliable() && getInternalState() != TransactionState._COMPLETED) { - // Linger in the completed state to catch - // retransmissions if the transport is not - // reliable. - this.setState(TransactionState._COMPLETED); - // Note that Timer J is only set for Unreliable - // transports -- see Issue 75. - /* - * From RFC 3261 Section 17.2.2 (non-invite server transaction) - * - * When the server transaction enters the "Completed" state, it MUST - * set Timer J to fire in 64*T1 seconds for unreliable transports, and - * zero seconds for reliable transports. While in the "Completed" - * state, the server transaction MUST pass the final response to the - * transport layer for retransmission whenever a retransmission of the - * request is received. Any other final responses passed by the TU to - * the server transaction MUST be discarded while in the "Completed" - * state. The server transaction remains in this state until Timer J - * fires, at which point it MUST transition to the "Terminated" state. - */ - startTransactionTimerJ(TIMER_J); - cleanUpOnTimer(); - } else { - cleanUpOnTimer(); - this.setState(TransactionState._TERMINATED); - startTransactionTimerJ(0); - } - } else { - // This is the case for INVITE server transactions. - // essentially, it duplicates the code in the - // PROCEEDING case below. There is no TRYING state for INVITE - // transactions in the RFC. We are using it to signal whether the - // application has sent a provisional response or not. Hence - // this is treated the same as as Proceeding. - if (statusCode / 100 == 2) { - // Status code is 2xx means that the - // transaction transitions to TERMINATED - // for both Reliable as well as unreliable - // transports. Note that the dialog layer - // takes care of retransmitting 2xx final - // responses. - /* - * RFC 3261 Section 13.3.1.4 Note, however, that the INVITE server - * transaction will be destroyed as soon as it receives this final - * response and passes it to the transport. Therefore, it is necessary - * to periodically pass the response directly to the transport until - * the ACK arrives. The 2xx response is passed to the transport with - * an interval that starts at T1 seconds and doubles for each - * retransmission until it reaches T2 seconds (T1 and T2 are defined - * in Section 17). Response retransmissions cease when an ACK request - * for the response is received. This is independent of whatever - * transport protocols are used to send the response. - */ - this.disableRetransmissionTimer(); - this.disableTimeoutTimer(); - this.collectionTime = TIMER_J; - cleanUpOnTimer(); - this.setState(TransactionState._TERMINATED); - if (this.getDialog() != null) - ((SIPDialog) this.getDialog()).setRetransmissionTicks(); - } else { - // This an error final response. - this.setState(TransactionState._COMPLETED); - if (!isReliable()) { - /* - * RFC 3261 - * - * While in the "Proceeding" state, if the TU passes a response - * with status code from 300 to 699 to the server transaction, the - * response MUST be passed to the transport layer for - * transmission, and the state machine MUST enter the "Completed" - * state. For unreliable transports, timer G is set to fire in T1 - * seconds, and is not set to fire for reliable transports. - */ - - enableRetransmissionTimer(); - - } - cleanUpOnTimer(); - enableTimeoutTimer(TIMER_H); - } - } - - } - - // If the transaction is in the proceeding state, - } else if (getRealState() == TransactionState._PROCEEDING) { - - if (isInviteTransaction()) { - - // If the response is a failure message, - if (statusCode / 100 == 2) { - // Set up to catch returning ACKs - // The transaction lingers in the - // terminated state for some time - // to catch retransmitted INVITEs - this.disableRetransmissionTimer(); - this.disableTimeoutTimer(); - this.collectionTime = TIMER_J; - cleanUpOnTimer(); - this.setState(TransactionState._TERMINATED); - if (this.getDialog() != null) - ((SIPDialog) this.getDialog()).setRetransmissionTicks(); - - } else if (300 <= statusCode && statusCode <= 699) { - - // Set up to catch returning ACKs - this.setState(TransactionState._COMPLETED); - if (!isReliable()) { - /* - * While in the "Proceeding" state, if the TU passes a response with - * status code from 300 to 699 to the server transaction, the response - * MUST be passed to the transport layer for transmission, and the - * state machine MUST enter the "Completed" state. For unreliable - * transports, timer G is set to fire in T1 seconds, and is not set to - * fire for reliable transports. - */ - enableRetransmissionTimer(); - } - cleanUpOnTimer(); - enableTimeoutTimer(TIMER_H); - } - // If the transaction is not an invite transaction - // and this is a final response, - } else if (200 <= statusCode && statusCode <= 699) { - // This is for Non-invite server transactions. - // Set up to retransmit this response, - // or terminate the transaction - this.setState(TransactionState._COMPLETED); - if (!isReliable()) { - - disableRetransmissionTimer(); -// enableTimeoutTimer(TIMER_J); - startTransactionTimerJ(TIMER_J); - } else { - this.setState(TransactionState._TERMINATED); - startTransactionTimerJ(0); - } - cleanUpOnTimer(); - } - // If the transaction has already completed, - } else if (TransactionState._COMPLETED == this.getRealState()) { - return false; - } - return true; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getViaHost() - */ - @Override - public String getViaHost() { - return super.getViaHost(); - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getViaPort() - */ - @Override - public int getViaPort() { - return super.getViaPort(); - } - - - /** - * @see gov.nist.javax.sip.stack.SIPTransactionImpl#fireRetransmissionTimer() - */ - @Override - public void fireRetransmissionTimer() { - - try { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("fireRetransmissionTimer() -- " + this + " state " + getState()); - } - // Resend the last response sent by this transaction - if (isInviteTransaction() && (lastResponse != null || lastResponseAsBytes != null)) { - // null can happen if this is terminating when the timer fires. - if (!this.retransmissionAlertEnabled || sipStack.isTransactionPendingAck(this) ) { - // Retransmit last response until ack. - if (lastResponseStatusCode / 100 >= 2 && !this.isAckSeen) { - resendLastResponseAsBytes(); - } - } else { - // alert the application to retransmit the last response - SipProviderImpl sipProvider = (SipProviderImpl) this.getSipProvider(); - TimeoutEvent txTimeout = new TimeoutEvent(sipProvider, this, - Timeout.RETRANSMIT); - sipProvider.handleEvent(txTimeout, this); - } - - } - } catch (IOException e) { - if (logger.isLoggingEnabled()) - logger.logException(e); - raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); - - } - - } - - // jeand we nullify the last response very fast to save on mem and help GC but we keep it as byte array - // so this method is used to resend the last response either as a response or byte array depending on if it has been nullified - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#resendLastResponseAsBytes() - */ - @Override - public void resendLastResponseAsBytes() throws IOException { - - if(lastResponse != null) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("resend last response " + lastResponse); - } - sendMessage(lastResponse); - } else if (lastResponseAsBytes != null) { - // Send the message to the client -// if(!checkStateTimers(lastResponseStatusCode)) { -// return; -// } - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("resend last response " + new String(lastResponseAsBytes)); - } - - if(isReliable()) { - if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) { - // Issue 343 : we have to log the retransmission - try { - SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null); - - lastReparsedResponse.setRemoteAddress( - this.getPeerInetAddress()); - lastReparsedResponse.setRemotePort(this.getPeerPort()); - lastReparsedResponse.setLocalPort( - getMessageChannel().getPort()); - lastReparsedResponse.setLocalAddress( - getMessageChannel() - .getMessageProcessor().getIpAddress()); - - getMessageChannel().logMessage(lastReparsedResponse, this.getPeerInetAddress(), this.getPeerPort(), System.currentTimeMillis()); - } catch (ParseException e) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes)); - } - } - } - getMessageChannel().sendMessage(lastResponseAsBytes, this.getPeerInetAddress(), this.getPeerPort(), false); - } else { - Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(lastResponseHost, lastResponsePort, - lastResponseTransport)); - - MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack()) - .createRawMessageChannel(this.getSipProvider().getListeningPoint( - hop.getTransport()).getIPAddress(), this.getPort(), hop); - if (messageChannel != null) { - if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) { - // Issue 343 : we have to log the retransmission - try { - SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null); - - lastReparsedResponse.setRemoteAddress(messageChannel.getPeerInetAddress()); - lastReparsedResponse.setRemotePort(messageChannel.getPeerPort()); - lastReparsedResponse.setLocalPort(messageChannel.getPort()); - lastReparsedResponse.setLocalAddress(messageChannel.getMessageProcessor().getIpAddress()); - - messageChannel.logMessage(lastReparsedResponse, messageChannel.getPeerInetAddress(), messageChannel.getPeerPort(), System.currentTimeMillis()); - } catch (ParseException e) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes)); - } - } - } - messageChannel.sendMessage(lastResponseAsBytes, InetAddress.getByName(hop.getHost()), hop.getPort(), false); - } else { - throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+ - this.getSipProvider().getListeningPoint( - hop.getTransport()).getIPAddress() + ":" + this.getPort()); - } - } - } - } - - private void fireReliableResponseRetransmissionTimer() { - try { - resendLastResponseAsBytes(); - } catch (IOException e) { - if (logger.isLoggingEnabled()) - logger.logException(e); - this.setState(TransactionState._TERMINATED); - raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); - - } - } - - - /** - * @see gov.nist.javax.sip.stack.SIPTransaction#fireTimeoutTimer() - */ - public void fireTimeoutTimer() { - - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("SIPServerTransaction.fireTimeoutTimer this = " + this - + " current state = " + this.getRealState() + " method = " - + this.getMethod()); - - if (isInviteTransaction() && sipStack.removeTransactionPendingAck(this) ) { - if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) ) { - logger.logDebug("Found tx pending ACK - timer H has kicked"); - } - - } - SIPDialog dialog = (SIPDialog) getDialog(); - - - if (SIPTransactionStack.isDialogCreated(getMethod()) - && (TransactionState._CALLING == this.getRealState() || TransactionState._TRYING == this - .getRealState())) { - dialog.setState(SIPDialog.TERMINATED_STATE); - } else if (getMethod().equals(Request.BYE)) { - if (dialog != null && dialog.isTerminatedOnBye()) - dialog.setState(SIPDialog.TERMINATED_STATE); - } - - if (TransactionState._COMPLETED == this.getRealState() && isInviteTransaction()) { - raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); - this.setState(TransactionState._TERMINATED); - sipStack.removeTransaction(this); - - } else if (TransactionState._COMPLETED == this.getRealState() && !isInviteTransaction()) { - this.setState(TransactionState._TERMINATED); - if(!getMethod().equals(Request.CANCEL)) { - cleanUp(); - } else { - sipStack.removeTransaction(this); - } - - } else if (TransactionState._CONFIRMED == this.getRealState() && isInviteTransaction()) { - // TIMER_I should not generate a timeout - // exception to the application when the - // Invite transaction is in Confirmed state. - // Just transition to Terminated state. - this.setState(TransactionState._TERMINATED); - sipStack.removeTransaction(this); - } else if (!isInviteTransaction() - && (TransactionState._COMPLETED == this.getRealState() || TransactionState._CONFIRMED == this - .getRealState())) { - this.setState(TransactionState._TERMINATED); - } else if (isInviteTransaction() && TransactionState._TERMINATED == this.getRealState()) { - // This state could be reached when retransmitting - - raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); - // TODO -- check this. This does not look right. - if (dialog != null) - dialog.setState(SIPDialog.TERMINATED_STATE); - } - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getLastResponseStatusCode() - */ - @Override - public int getLastResponseStatusCode() { - return this.lastResponseStatusCode; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setOriginalRequest(gov.nist.javax.sip.message.SIPRequest) - */ - @Override - public void setOriginalRequest(SIPRequest originalRequest) { - super.setOriginalRequest(originalRequest); - - } - - /* - * (non-Javadoc) - * - * @see javax.sip.ServerTransaction#sendResponse(javax.sip.message.Response) - */ - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#sendResponse(javax.sip.message.Response) - */ - @Override - public void sendResponse(Response response) throws SipException { - SIPResponse sipResponse = (SIPResponse) response; - - SIPDialog dialog = (SIPDialog) getDialog(); - if (response == null) - throw new NullPointerException("null response"); - - try { - sipResponse.checkHeaders(); - } catch (ParseException ex) { - throw new IllegalTransactionStateException(ex.getMessage(), Reason.MissingRequiredHeader); - } - - // check for meaningful response. - final String responseMethod = sipResponse.getCSeq().getMethod(); - if (!responseMethod.equals(this.getMethod())) { - throw new IllegalTransactionStateException( - "CSeq method does not match Request method of request that created the tx.", Reason.UnmatchingCSeq); - } - - /* - * 200-class responses to SUBSCRIBE requests also MUST contain an "Expires" header. The - * period of time in the response MAY be shorter but MUST NOT be longer than specified in - * the request. - */ - final int statusCode = response.getStatusCode(); - if (this.getMethod().equals(Request.SUBSCRIBE) && statusCode / 100 == 2) { - - if (response.getHeader(ExpiresHeader.NAME) == null) { - throw new IllegalTransactionStateException("Expires header is mandatory in 2xx response of SUBSCRIBE", Reason.ExpiresHeaderMandatory); - } else { - Expires requestExpires = (Expires) this.getOriginalRequest().getExpires(); - Expires responseExpires = (Expires) response.getExpires(); - /* - * If no "Expires" header is present in a SUBSCRIBE request, the implied default - * is defined by the event package being used. - */ - if (requestExpires != null - && responseExpires.getExpiresLong() > requestExpires.getExpiresLong()) { - throw new SipException( - "Response Expires time exceeds request Expires time : See RFC 3265 3.1.1"); - } - } - - } - - // Check for mandatory header. - if (statusCode == 200 - && responseMethod.equals(Request.INVITE) - && sipResponse.getHeader(ContactHeader.NAME) == null) - throw new IllegalTransactionStateException("Contact Header is mandatory for the OK to the INVITE", Reason.ContactHeaderMandatory); - - if (!this.isMessagePartOfTransaction((SIPMessage) response)) { - throw new SipException("Response does not belong to this transaction."); - } - - // Fix up the response if the dialog has already been established. - try { - /* - * The UAS MAY send a final response to the initial request before - * having received PRACKs for all unacknowledged reliable provisional responses, - * unless the final response is 2xx and any of the unacknowledged reliable provisional - * responses contained a session description. In that case, it MUST NOT send a final - * response until those provisional responses are acknowledged. - */ - final ContentTypeHeader contentTypeHeader = this.pendingReliableResponseContentType; - if (this.pendingReliableResponseAsBytes != null - && this.getDialog() != null - && this.getInternalState() != TransactionState._TERMINATED - && statusCode / 100 == 2 - && contentTypeHeader != null - && contentTypeHeader.getContentType() - .equalsIgnoreCase(CONTENT_TYPE_APPLICATION) - && contentTypeHeader.getContentSubType() - .equalsIgnoreCase(CONTENT_SUBTYPE_SDP)) { - if (!interlockProvisionalResponses ) { - throw new SipException("cannot send response -- unacked provisional"); - } else { - try { - boolean acquired = this.provisionalResponseSem.tryAcquire(1,TimeUnit.SECONDS); - if (!acquired ) { - throw new SipException("cannot send response -- unacked provisional"); - } - } catch (InterruptedException ex) { - logger.logError ("Interrupted acuqiring PRACK sem"); - throw new SipException("Cannot aquire PRACK sem"); - } - - } - } else { - // Sending the final response cancels the - // pending response task. - if (this.pendingReliableResponseAsBytes != null && sipResponse.isFinalResponse()) { - sipStack.getTimer().cancel(provisionalResponseTask); - this.provisionalResponseTask = null; - } - } - - // Dialog checks. These make sure that the response - // being sent makes sense. - if (dialog != null) { - if (statusCode / 100 == 2 - && SIPTransactionStack.isDialogCreated(responseMethod)) { - if (dialog.getLocalTag() == null && sipResponse.getToTag() == null) { - // Trying to send final response and user forgot to set - // to - // tag on the response -- be nice and assign the tag for - // the user. - sipResponse.getTo().setTag(Utils.getInstance().generateTag()); - } else if (dialog.getLocalTag() != null && sipResponse.getToTag() == null) { - if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("assigning toTag : serverTransaction = " + this + " dialog " - + dialog + " tag = " + dialog.getLocalTag()); - } - sipResponse.setToTag(dialog.getLocalTag()); - } else if (dialog.getLocalTag() != null && sipResponse.getToTag() != null - && !dialog.getLocalTag().equals(sipResponse.getToTag())) { - throw new SipException("Tag mismatch dialogTag is " - + dialog.getLocalTag() + " responseTag is " - + sipResponse.getToTag()); - } - } - - if (!sipResponse.getCallId().getCallId().equals(dialog.getCallId().getCallId())) { - throw new SipException("Dialog mismatch!"); - } - } - - - - // Backward compatibility slippery slope.... - // Only set the from tag in the response when the - // incoming request has a from tag. - String fromTag = originalRequestFromTag; - if(getRequest() != null) { - fromTag = ((SIPRequest) this.getRequest()).getFromTag(); - } - if (fromTag != null && sipResponse.getFromTag() != null - && !sipResponse.getFromTag().equals(fromTag)) { - throw new SipException("From tag of request does not match response from tag"); - } else if (fromTag != null) { - sipResponse.getFrom().setTag(fromTag); - } else { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("WARNING -- Null From tag in request!!"); - } - // See if the dialog needs to be inserted into the dialog table - // or if the state of the dialog needs to be changed. - if (dialog != null && statusCode != 100) { - dialog.setResponseTags(sipResponse); - DialogState oldState = dialog.getState(); - dialog.setLastResponse(this, (SIPResponse) response); - if (oldState == null && dialog.getState() == DialogState.TERMINATED) { - DialogTerminatedEvent event = new DialogTerminatedEvent(dialog - .getSipProvider(), dialog); - - // Provide notification to the listener that the dialog has - // ended. - dialog.getSipProvider().handleEvent(event, this); - - } - - } else if (dialog == null && isInviteTransaction() - && this.retransmissionAlertEnabled - && this.retransmissionAlertTimerTask == null - && response.getStatusCode() / 100 == 2) { - String dialogId = ((SIPResponse) response).getDialogId(true); - - this.retransmissionAlertTimerTask = new RetransmissionAlertTimerTask(dialogId); - sipStack.retransmissionAlertTransactions.put(dialogId, this); - sipStack.getTimer().scheduleWithFixedDelay(this.retransmissionAlertTimerTask, 0, - SIPTransactionStack.BASE_TIMER_INTERVAL); - - } - - // Send message after possibly inserting the Dialog - // into the dialog table to avoid a possible race condition. - - - this.sendMessage((SIPResponse) response); - - - - if ( dialog != null ) { - dialog.startRetransmitTimer(this, (SIPResponse)response); - } - - } catch (IOException ex) { - if (logger.isLoggingEnabled()) - logger.logException(ex); - this.setState(TransactionState._TERMINATED); - raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); - throw new SipException(ex.getMessage(), ex); - } catch (java.text.ParseException ex1) { - if (logger.isLoggingEnabled()) - logger.logException(ex1); - this.setState(TransactionState._TERMINATED); - throw new SipException(ex1.getMessage(), ex1); - } - } - - /** - * Return the book-keeping information that we actually use. - */ - protected int getRealState() { - return super.getInternalState(); - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getState() - */ - @Override - public TransactionState getState() { - // Trying is a pseudo state for INVITE transactions. - if (this.isInviteTransaction() && TransactionState._TRYING == super.getInternalState()) - return TransactionState.PROCEEDING; - else - return super.getState(); - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setState(int) - */ - @Override - public void setState(int newState) { - // Set this timer for connection caching - // of incoming connections. - if (newState == TransactionState._TERMINATED && this.isReliable() - && (!getSIPStack().cacheServerConnections)) { - // Set a time after which the connection - // is closed. - this.collectionTime = TIMER_J; - this.terminationSemaphore.release(); - } - - super.setState(newState); - - } - - /** - * @see gov.nist.javax.sip.stack.SIPTransaction#startTransactionTimer() - */ - @Override - public void startTransactionTimer() { - if(getMethod().equalsIgnoreCase(Request.INVITE) || getMethod().equalsIgnoreCase(Request.CANCEL) || getMethod().equalsIgnoreCase(Request.ACK)) { - if (this.transactionTimerStarted.compareAndSet(false, true)) { - if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { - // The timer is set to null when the Stack is - // shutting down. - SIPStackTimerTask myTimer = new TransactionTimer(); - // Do not schedule when the stack is not alive. - if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { - sipStack.getTimer().scheduleWithFixedDelay(myTimer, baseTimerInterval, baseTimerInterval); - } - myTimer = null; - } - } - } - } - - /** - * Start the timer task. - */ - protected void startTransactionTimerJ(long time) { - if (this.transactionTimerStarted.compareAndSet(false, true)) { - if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("starting TransactionTimerJ() : " + getTransactionId() + " time " + time); - } - // The timer is set to null when the Stack is - // shutting down. - SIPStackTimerTask task = new SIPStackTimerTask () { - - public void runTask() { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("executing TransactionTimerJ() : " + getTransactionId()); - } - fireTimeoutTimer(); - cleanUp(); - if(originalRequest != null) { - originalRequest.cleanUp(); - } - } - - @Override - public Object getThreadHash() { - Request request = getRequest(); - if (request != null && request instanceof SIPRequest) { - return ((SIPRequest)request).getCallIdHeader().getCallId(); - } else { - return null; - } - } - }; - if(time > 0) { - sipStack.getTimer().schedule(task, time * T1 * baseTimerInterval); - } else { - task.runTask(); - } - } - } - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#equals(java.lang.Object) - */ - @Override - public boolean equals(Object other) { - if(other == null) return false; - if (!other.getClass().equals(this.getClass())) { - return false; - } - SIPServerTransaction sst = (SIPServerTransaction) other; - return this.getBranch().equalsIgnoreCase(sst.getBranch()); - } - - /* - * (non-Javadoc) - * - * @see gov.nist.javax.sip.stack.SIPTransaction#getDialog() - */ - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getDialog() - */ - @Override - public Dialog getDialog() { - if(dialog == null && dialogId != null) { - return sipStack.getDialog(dialogId); - } - return dialog; - } - - /* - * (non-Javadoc) - * - * @see gov.nist.javax.sip.stack.SIPTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, - * gov.nist.javax.sip.message.SIPMessage) - */ - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, java.lang.String) - */ - @Override - public void setDialog(SIPDialog sipDialog, String dialogId) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("setDialog " + this + " dialog = " + sipDialog); - this.dialog = sipDialog; - this.dialogId = dialogId; - if (dialogId != null) - sipDialog.setAssigned(); - if (this.retransmissionAlertEnabled && this.retransmissionAlertTimerTask != null) { - sipStack.getTimer().cancel(retransmissionAlertTimerTask); - if (this.retransmissionAlertTimerTask.dialogId != null) { - sipStack.retransmissionAlertTransactions - .remove(this.retransmissionAlertTimerTask.dialogId); - } - this.retransmissionAlertTimerTask = null; - } - - this.retransmissionAlertEnabled = false; - - } - - /* - * (non-Javadoc) - * - * @see javax.sip.Transaction#terminate() - */ - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#terminate() - */ - @Override - public void terminate() throws ObjectInUseException { - this.setState(TransactionState._TERMINATED); - if (this.retransmissionAlertTimerTask != null) { - sipStack.getTimer().cancel(retransmissionAlertTimerTask); - if (retransmissionAlertTimerTask.dialogId != null) { - this.sipStack.retransmissionAlertTransactions - .remove(retransmissionAlertTimerTask.dialogId); - } - this.retransmissionAlertTimerTask = null; - - } - if(!transactionTimerStarted.get()) { - // if no transaction timer was started just remove the tx without firing a transaction terminated event - testAndSetTransactionTerminatedEvent(); - sipStack.removeTransaction(this); - } - } - - @Override - public void sendReliableProvisionalResponse(Response relResponse) throws SipException { - - /* - * After the first reliable provisional response for a request has been acknowledged, the - * UAS MAY send additional reliable provisional responses. The UAS MUST NOT send a second - * reliable provisional response until the first is acknowledged. - */ - if (this.pendingReliableResponseAsBytes != null) { - throw new SipException("Unacknowledged response"); - - } else { - SIPResponse reliableResponse = (SIPResponse) relResponse; - this.pendingReliableResponseAsBytes = reliableResponse.encodeAsBytes(this.getTransport()); - this.pendingReliableResponseMethod = reliableResponse.getCSeq().getMethod(); - this.pendingReliableCSeqNumber = reliableResponse.getCSeq().getSeqNumber(); - this.pendingReliableResponseContentType = reliableResponse.getContentTypeHeader(); - } - /* - * In addition, it MUST contain a Require header field containing the option tag 100rel, - * and MUST include an RSeq header field. - */ - RSeq rseq = (RSeq) relResponse.getHeader(RSeqHeader.NAME); - if (relResponse.getHeader(RSeqHeader.NAME) == null) { - rseq = new RSeq(); - relResponse.setHeader(rseq); - } - - try { - if(rseqNumber < 0) { - this.rseqNumber = (int) (Math.random() * 1000); - } - this.rseqNumber++; - rseq.setSeqNumber(this.rseqNumber); - this.pendingReliableRSeqNumber = rseq.getSeqNumber(); - - // start the timer task which will retransmit the reliable response - // until the PRACK is received. Cannot send a second provisional. - this.lastResponse = (SIPResponse) relResponse; - if ( this.getDialog() != null && interlockProvisionalResponses ) { - boolean acquired = this.provisionalResponseSem.tryAcquire(1, TimeUnit.SECONDS); - if (!acquired) { - throw new SipException("Unacknowledged reliable response"); - } - } - //moved the task scheduling before the sending of the message to overcome - // Issue 265 : https://jain-sip.dev.java.net/issues/show_bug.cgi?id=265 - this.provisionalResponseTask = new ProvisionalResponseTask(); - this.sipStack.getTimer().scheduleWithFixedDelay(provisionalResponseTask, 0, - SIPTransactionStack.BASE_TIMER_INTERVAL); - this.sendMessage((SIPMessage) relResponse); - } catch (Exception ex) { - InternalErrorHandler.handleException(ex); - } - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getReliableProvisionalResponse() - */ - @Override - public byte[] getReliableProvisionalResponse() { - - return this.pendingReliableResponseAsBytes; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#prackRecieved() - */ - @Override - public boolean prackRecieved() { - - if (this.pendingReliableResponseAsBytes == null) - return false; - if(provisionalResponseTask != null) { - sipStack.getTimer().cancel(provisionalResponseTask); - this.provisionalResponseTask = null; - } - - this.pendingReliableResponseAsBytes = null; - this.pendingReliableResponseContentType = null; - if ( interlockProvisionalResponses && getDialog() != null ) { - this.provisionalResponseSem.release(); - } - return true; - } - - /* - * (non-Javadoc) - * - * @see javax.sip.ServerTransaction#enableRetransmissionAlerts() - */ - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#enableRetransmissionAlerts() - */ - @Override - public void enableRetransmissionAlerts() throws SipException { - if (this.getDialog() != null) - throw new SipException("Dialog associated with tx"); - - else if (!isInviteTransaction()) - throw new SipException("Request Method must be INVITE"); - - this.retransmissionAlertEnabled = true; - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#isRetransmissionAlertEnabled() - */ - @Override - public boolean isRetransmissionAlertEnabled() { - return this.retransmissionAlertEnabled; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#disableRetransmissionAlerts() - */ - @Override - public void disableRetransmissionAlerts() { - if (this.retransmissionAlertTimerTask != null && this.retransmissionAlertEnabled) { - sipStack.getTimer().cancel(retransmissionAlertTimerTask); - this.retransmissionAlertEnabled = false; - - String dialogId = this.retransmissionAlertTimerTask.dialogId; - if (dialogId != null) { - sipStack.retransmissionAlertTransactions.remove(dialogId); - } - this.retransmissionAlertTimerTask = null; - } - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setAckSeen() - */ - @Override - public void setAckSeen() { - this.isAckSeen = true; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#ackSeen() - */ - @Override - public boolean ackSeen() { - return this.isAckSeen; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setMapped(boolean) - */ - @Override - public void setMapped(boolean b) { - this.isMapped = true; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#setPendingSubscribe(gov.nist.javax.sip.stack.SIPClientTransaction) - */ - @Override - public void setPendingSubscribe(SIPClientTransaction pendingSubscribeClientTx) { - this.pendingSubscribeTransaction = pendingSubscribeClientTx; - - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#releaseSem() - */ - @Override - public void releaseSem() { - if (this.pendingSubscribeTransaction != null) { - /* - * When a notify is being processed we take a lock on the subscribe to avoid racing - * with the OK of the subscribe. - */ - if (! sipStack.isDeliverUnsolicitedNotify()) { - pendingSubscribeTransaction.releaseSem(); - } - } else if (this.inviteTransaction != null && this.getMethod().equals(Request.CANCEL)) { - /* - * When a CANCEL is being processed we take a nested lock on the associated INVITE - * server tx. - */ - this.inviteTransaction.releaseSem(); - } - super.releaseSem(); - } - - /** - * The INVITE Server Transaction corresponding to a CANCEL Server Transaction. - * - * @param st -- the invite server tx corresponding to the cancel server transaction. - */ - @Override - public void setInviteTransaction(SIPServerTransaction st) { - this.inviteTransaction = st; - - } - - /** - * TODO -- this method has to be added to the api. - * - * @return - */ - @Override - public SIPServerTransaction getCanceledInviteTransaction() { - return this.inviteTransaction; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#scheduleAckRemoval() - */ - @Override - public void scheduleAckRemoval() throws IllegalStateException { - if (this.getMethod() == null || !this.getMethod().equals(Request.ACK)) { - throw new IllegalStateException("Method is null[" + (getMethod() == null) - + "] or method is not ACK[" + this.getMethod() + "]"); - } - - this.startTransactionTimer(); - } - - // jeand cleanup the state of the stx to help GC - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#cleanUp() - */ - @Override - public void cleanUp() { - // Remove it from the set - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) - logger.logDebug("removing" + this); - - if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { - - // release the connection associated with this transaction. - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("cleanup : " - + getTransactionId()); - } - // we keep the request in a byte array to be able to recreate it - // no matter what to keep API backward compatibility - if(originalRequest == null && originalRequestBytes != null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { - try { - originalRequest = (SIPRequest) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(originalRequestBytes, true, false, null); -// originalRequestBytes = null; - } catch (ParseException e) { - logger.logError("message " + originalRequestBytes + "could not be reparsed !"); - } - } else if (originalRequest != null && originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { - originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport()); - } - // http://java.net/jira/browse/JSIP-429 - // store the merge id from the tx to avoid reparsing of request on aggressive cleanup - if (originalRequest != null && originalRequestBytes == null) { - super.mergeId = ((SIPRequest)originalRequest).getMergeId(); - } - sipStack.removeTransaction(this); - cleanUpOnTimer(); - // commented out because the application can hold on a ref to the tx - // after it has been removed from the stack - // and want to get the request or branch from it -// originalRequestBytes = null; -// originalRequestBranch = null; - originalRequestFromTag = null; - originalRequestSentBy = null; - // it should be available in the processTxTerminatedEvent, so we can nullify it only here - if(originalRequest != null) { - // originalRequestSentBy = originalRequest.getTopmostVia().getSentBy(); - // originalRequestFromTag = originalRequest.getFromTag(); - originalRequest = null; - } - if(!isReliable() && inviteTransaction != null) { - inviteTransaction = null; - } - // Application Data has to be cleared by the application - // applicationData = null; - lastResponse = null; - // Issue 318 : (https://jain-sip.dev.java.net/issues/show_bug.cgi?id=318) - // Re-transmission of 200 to INVITE terminates prematurely : - // don't nullify since the transaction may be terminated - // but the ack not received so the 200 retransmissions should continue -// lastResponseAsBytes = null; - - // don't clean up because on sending 200 OK to CANCEL otherwise we try to start the transaction timer - // but due to timer J it has already been cleaned up -// transactionTimerStarted = null; - } else { - sipStack.removeTransaction(this); - } - - // Uncache the server tx - if ((!sipStack.cacheServerConnections) && isReliable() - && --getMessageChannel().useCount <= 0) { - // Close the encapsulated socket if stack is configured - close(); - } else { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) - && (!sipStack.cacheServerConnections) - && isReliable()) { - int useCount = getMessageChannel().useCount; - logger.logDebug("Use Count = " + useCount); - } - } - - } - - // clean up the state of the stx when it goes to completed or terminated to help GC - protected void cleanUpOnTimer() { - if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { - if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { - logger.logDebug("cleanup on timer : " - + getTransactionId()); - } - if(dialog != null && getMethod().equals(Request.CANCEL)) { - // used to deal with getting the dialog on cancel tx after the 200 OK to CANCEL has been sent - dialogId = dialog.getDialogId(); - } - dialog = null; - // we don't nullify the inviteTx for CANCEL since the app can get it from getCanceledInviteTransaction - if(inviteTransaction != null && !getMethod().equals(Request.CANCEL)) { - // we release the semaphore for Cancel processing - inviteTransaction.releaseSem(); - inviteTransaction = null; - } - if(originalRequest != null) { - // http://java.net/jira/browse/JSIP-429 - // store the merge id from the tx to avoid reparsing of request on aggressive cleanup - super.mergeId = ((SIPRequest)originalRequest).getMergeId(); - originalRequest.setTransaction(null); - originalRequest.setInviteTransaction(null); - if(!getMethod().equalsIgnoreCase(Request.INVITE)) { - if(originalRequestSentBy == null) { - originalRequestSentBy = originalRequest.getTopmostVia().getSentBy(); - } - if(originalRequestFromTag == null) { - originalRequestFromTag = originalRequest.getFromTag(); - } - } - // we keep the request in a byte array to be able to recreate it - // no matter what to keep API backward compatibility - if(originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { - originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport()); - } - if(!getMethod().equalsIgnoreCase(Request.INVITE) && !getMethod().equalsIgnoreCase(Request.CANCEL)) { - originalRequest = null; - } - } - if(lastResponse != null) { - if(getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { - lastResponseAsBytes = lastResponse.encodeAsBytes(this.getTransport()); - } - lastResponse = null; - } - pendingReliableResponseAsBytes = null; - pendingReliableResponseContentType = null; - pendingReliableResponseMethod = null; - pendingSubscribeTransaction = null; - provisionalResponseSem = null; - retransmissionAlertTimerTask = null; - requestOf = null; - } - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableResponseMethod() - */ - @Override - public String getPendingReliableResponseMethod() { - return pendingReliableResponseMethod; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableCSeqNumber() - */ - @Override - public long getPendingReliableCSeqNumber() { - return pendingReliableCSeqNumber; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableRSeqNumber() - */ - @Override - public long getPendingReliableRSeqNumber() { - return pendingReliableRSeqNumber; - } - - /** - * @see gov.nist.javax.sip.stack.SIPServerTransaction#waitForTermination() - */ - @Override - public void waitForTermination() { - - try { - this.terminationSemaphore.acquire(); - } catch (InterruptedException e) { - - } - } - -} +/* + * Conditions Of Use + * + * This software was developed by employees of the National Institute of + * Standards and Technology (NIST), an agency of the Federal Government. + * Pursuant to title 15 Untied States Code Section 105, works of NIST + * employees are not subject to copyright protection in the United States + * and are considered to be in the public domain. As a result, a formal + * license is not needed to use the software. + * + * This software is provided by NIST as a service and is expressly + * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED + * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT + * AND DATA ACCURACY. NIST does not warrant or make any representations + * regarding the use of the software or the results thereof, including but + * not limited to the correctness, accuracy, reliability or usefulness of + * the software. + * + * Permission to use this software is contingent upon your acceptance + * of the terms of this agreement + * + * . + * + */ +package gov.nist.javax.sip.stack; + +import gov.nist.core.CommonLogger; +import gov.nist.core.HostPort; +import gov.nist.core.InternalErrorHandler; +import gov.nist.core.LogWriter; +import gov.nist.core.ServerLogger; +import gov.nist.core.StackLogger; +import gov.nist.javax.sip.ReleaseReferencesStrategy; +import gov.nist.javax.sip.SIPConstants; +import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.Utils; +import gov.nist.javax.sip.header.Expires; +import gov.nist.javax.sip.header.ParameterNames; +import gov.nist.javax.sip.header.RSeq; +import gov.nist.javax.sip.header.Via; +import gov.nist.javax.sip.message.SIPMessage; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; +import gov.nist.javax.sip.stack.IllegalTransactionStateException.Reason; + +import java.io.IOException; +import java.net.InetAddress; +import java.text.ParseException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import javax.sip.Dialog; +import javax.sip.DialogState; +import javax.sip.DialogTerminatedEvent; +import javax.sip.ObjectInUseException; +import javax.sip.SipException; +import javax.sip.Timeout; +import javax.sip.TimeoutEvent; +import javax.sip.TransactionState; +import javax.sip.address.Hop; +import javax.sip.header.ContactHeader; +import javax.sip.header.ContentTypeHeader; +import javax.sip.header.ExpiresHeader; +import javax.sip.header.RSeqHeader; +import javax.sip.message.Request; +import javax.sip.message.Response; + +/* + * Bug fixes / enhancements:Emil Ivov, Antonis Karydas, Daniel J. Martinez Manzano, Daniel, Hagai + * Sela, Vazques-Illa, Bill Roome, Thomas Froment and Pierre De Rop, Christophe Anzille and Jeroen + * van Bemmel, Frank Reif. + * Carolyn Beeton ( Avaya ). + * + */ + +/** + * Represents a server transaction. Implements the following state machines. + * + *
+ *
+ *
+ *
+ *                                                                      |INVITE
+ *                                                                      |pass INV to TU
+ *                                                   INVITE             V send 100 if TU won't in 200ms
+ *                                                   send response+-----------+
+ *                                                       +--------|           |--------+101-199 from TU
+ *                                                       |        | Proceeding|        |send response
+ *                                                       +------->|           |<-------+
+ *                                                                |           |          Transport Err.
+ *                                                                |           |          Inform TU
+ *                                                                |           |--------------->+
+ *                                                                +-----------+                |
+ *                                                   300-699 from TU |     |2xx from TU        |
+ *                                                   send response   |     |send response      |
+ *                                                                   |     +------------------>+
+ *                                                                   |                         |
+ *                                                   INVITE          V          Timer G fires  |
+ *                                                   send response+-----------+ send response  |
+ *                                                       +--------|           |--------+       |
+ *                                                       |        | Completed |        |       |
+ *                                                       +------->|           |<-------+       |
+ *                                                                +-----------+                |
+ *                                                                   |     |                   |
+ *                                                               ACK |     |                   |
+ *                                                               -   |     +------------------>+
+ *                                                                   |        Timer H fires    |
+ *                                                                   V        or Transport Err.|
+ *                                                                +-----------+  Inform TU     |
+ *                                                                |           |                |
+ *                                                                | Confirmed |                |
+ *                                                                |           |                |
+ *                                                                +-----------+                |
+ *                                                                      |                      |
+ *                                                                      |Timer I fires         |
+ *                                                                      |-                     |
+ *                                                                      |                      |
+ *                                                                      V                      |
+ *                                                                +-----------+                |
+ *                                                                |           |                |
+ *                                                                | Terminated|<---------------+
+ *                                                                |           |
+ *                                                                +-----------+
+ *
+ *                                                     Figure 7: INVITE server transaction
+ *                                                         Request received
+ *                                                                         |pass to TU
+ *
+ *                                                                         V
+ *                                                                   +-----------+
+ *                                                                   |           |
+ *                                                                   | Trying    |-------------+
+ *                                                                   |           |             |
+ *                                                                   +-----------+             |200-699 from TU
+ *                                                                         |                   |send response
+ *                                                                         |1xx from TU        |
+ *                                                                         |send response      |
+ *                                                                         |                   |
+ *                                                      Request            V      1xx from TU  |
+ *                                                      send response+-----------+send response|
+ *                                                          +--------|           |--------+    |
+ *                                                          |        | Proceeding|        |    |
+ *                                                          +------->|           |<-------+    |
+ *                                                   +<--------------|           |             |
+ *                                                   |Trnsprt Err    +-----------+             |
+ *                                                   |Inform TU            |                   |
+ *                                                   |                     |                   |
+ *                                                   |                     |200-699 from TU    |
+ *                                                   |                     |send response      |
+ *                                                   |  Request            V                   |
+ *                                                   |  send response+-----------+             |
+ *                                                   |      +--------|           |             |
+ *                                                   |      |        | Completed |<------------+
+ *                                                   |      +------->|           |
+ *                                                   +<--------------|           |
+ *                                                   |Trnsprt Err    +-----------+
+ *                                                   |Inform TU            |
+ *                                                   |                     |Timer J fires
+ *                                                   |                     |-
+ *                                                   |                     |
+ *                                                   |                     V
+ *                                                   |               +-----------+
+ *                                                   |               |           |
+ *                                                   +-------------->| Terminated|
+ *                                                                   |           |
+ *                                                                   +-----------+
+ *
+ *
+ *
+ *
+ *
+ * 
+ * + * @version 1.2 $Revision: 1.150 $ $Date: 2010-12-02 22:04:15 $ + * @author M. Ranganathan + * + */ +public class SIPServerTransactionImpl extends SIPTransactionImpl implements SIPServerTransaction { + private static StackLogger logger = CommonLogger.getLogger(SIPServerTransaction.class); + private int rseqNumber = -1; + + // private LinkedList pendingRequests; + + // Real RequestInterface to pass messages to + private transient ServerRequestInterface requestOf; + + private SIPDialog dialog; + // jeand needed because we nullify the dialog ref early and keep only the dialogId to save on mem and help GC + protected String dialogId; + + // the unacknowledged SIPResponse + +// private SIPResponse pendingReliableResponse; + // wondering if the pendingReliableResponseAsBytes could be put into the lastResponseAsBytes + private byte[] pendingReliableResponseAsBytes; + private String pendingReliableResponseMethod; + private long pendingReliableCSeqNumber; + private long pendingReliableRSeqNumber; + private ContentTypeHeader pendingReliableResponseContentType; + + // The pending reliable Response Timer + private ProvisionalResponseTask provisionalResponseTask; + + private boolean retransmissionAlertEnabled; + + private RetransmissionAlertTimerTask retransmissionAlertTimerTask; + + protected boolean isAckSeen; + + private SIPClientTransaction pendingSubscribeTransaction; + + private SIPServerTransaction inviteTransaction; + + // Experimental. + private static boolean interlockProvisionalResponses = true; + + private Semaphore provisionalResponseSem = new Semaphore(1); + + private Semaphore terminationSemaphore = new Semaphore(0); + + // jeand we nullify the last response fast to save on mem and help GC, but we keep only the information needed + private byte[] lastResponseAsBytes; + private String lastResponseHost; + private int lastResponsePort; + private String lastResponseTransport; + + private int lastResponseStatusCode; + + private HostPort originalRequestSentBy; + private String originalRequestFromTag; + + + /** + * This timer task is used for alerting the application to send retransmission alerts. + * + * + */ + class RetransmissionAlertTimerTask extends SIPStackTimerTask { + + String dialogId; + + int ticks; + + int ticksLeft; + + public RetransmissionAlertTimerTask(String dialogId) { + + this.ticks = SIPTransactionImpl.T1; + this.ticksLeft = this.ticks; + // Fix from http://java.net/jira/browse/JSIP-443 + // by mitchell.c.ackerman + this.dialogId = dialogId; + } + + public void runTask() { + SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; + ticksLeft--; + if (ticksLeft == -1) { + serverTransaction.fireRetransmissionTimer(); + this.ticksLeft = 2 * ticks; + } + + } + + @Override + public Object getThreadHash() { + Request request = getRequest(); + if (request != null && request instanceof SIPRequest) { + return ((SIPRequest)request).getCallIdHeader().getCallId(); + } else { + return null; + } + } + + } + + class ProvisionalResponseTask extends SIPStackTimerTask { + + int ticks; + + int ticksLeft; + + public ProvisionalResponseTask() { + this.ticks = SIPTransactionImpl.T1; + this.ticksLeft = this.ticks; + } + + public void runTask() { + SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; + /* + * The reliable provisional response is passed to the transaction layer periodically + * with an interval that starts at T1 seconds and doubles for each retransmission (T1 + * is defined in Section 17 of RFC 3261). Once passed to the server transaction, it is + * added to an internal list of unacknowledged reliable provisional responses. The + * transaction layer will forward each retransmission passed from the UAS core. + * + * This differs from retransmissions of 2xx responses, whose intervals cap at T2 + * seconds. This is because retransmissions of ACK are triggered on receipt of a 2xx, + * but retransmissions of PRACK take place independently of reception of 1xx. + */ + // If the transaction has terminated, + if (serverTransaction.isTerminated()) { + + sipStack.getTimer().cancel(this); + + } else { + ticksLeft--; + if (ticksLeft == -1) { + serverTransaction.fireReliableResponseRetransmissionTimer(); + this.ticksLeft = 2 * ticks; + this.ticks = this.ticksLeft; + // timer H MUST be set to fire in 64*T1 seconds for all transports. Timer H + // determines when the server + // transaction abandons retransmitting the response + if (this.ticksLeft >= SIPTransactionImpl.TIMER_H) { + sipStack.getTimer().cancel(this); + setState(TransactionState._TERMINATED); + fireTimeoutTimer(); + } + } + + } + + } + + @Override + public Object getThreadHash() { + Request request = getRequest(); + if (request != null && request instanceof SIPRequest) { + return ((SIPRequest)request).getCallIdHeader().getCallId(); + } else { + return null; + } + } + + } + + /** + * This timer task is for INVITE server transactions. It will send a trying in 200 ms. if the + * TU does not do so. + * + */ + class SendTrying extends SIPStackTimerTask { + + protected SendTrying() { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("scheduled timer for " + SIPServerTransactionImpl.this); + + } + + public void runTask() { + SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this; + + int realState = serverTransaction.getRealState(); + + if (realState < 0 || TransactionState._TRYING == realState) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug(" sending Trying current state = " + + serverTransaction.getRealState()); + try { + serverTransaction.sendMessage(serverTransaction.getOriginalRequest() + .createResponse(100, "Trying")); + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug(" trying sent " + + serverTransaction.getRealState()); + } catch (IOException ex) { + if (logger.isLoggingEnabled()) + logger.logError("IO error sending TRYING"); + } + } + + } + + @Override + public Object getThreadHash() { + Request request = getRequest(); + if (request != null && request instanceof SIPRequest) { + return ((SIPRequest)request).getCallIdHeader().getCallId(); + } else { + return null; + } + } + } + + class TransactionTimer extends SIPStackTimerTask { + + public TransactionTimer() { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("TransactionTimer() : " + getTransactionId()); + } + + } + + public void runTask() { + // If the transaction has terminated, + if (isTerminated()) { + // Keep the transaction hanging around in the transaction table + // to catch the incoming ACK -- this is needed for tcp only. + // Note that the transaction record is actually removed in + // the connection linger timer. + try { + sipStack.getTimer().cancel(this); + } catch (IllegalStateException ex) { + if (!sipStack.isAlive()) + return; + } + + + // Oneshot timer that garbage collects the SeverTransaction + // after a scheduled amount of time. The linger timer allows + // the client side of the tx to use the same connection to + // send an ACK and prevents a race condition for creation + // of new server tx + SIPStackTimerTask myTimer = new LingerTimer(); + + if(sipStack.getConnectionLingerTimer() != 0) { + sipStack.getTimer().schedule(myTimer, sipStack.getConnectionLingerTimer() * 1000); + } else { + myTimer.runTask(); + } + } else { + // Add to the fire list -- needs to be moved + // outside the synchronized block to prevent + // deadlock. + fireTimer(); + } + if(originalRequest != null) { + originalRequest.cleanUp(); + } + } + + @Override + public Object getThreadHash() { + Request request = getRequest(); + if (request != null && request instanceof SIPRequest) { + return ((SIPRequest)request).getCallIdHeader().getCallId(); + } else { + return null; + } + } + + } + + /** + * Send a response. + * + * @param transactionResponse -- the response to send + * + */ + + protected void sendResponse(SIPResponse transactionResponse) throws IOException { + if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("sipServerTransaction::sendResponse " + transactionResponse.getFirstLine()); + } + try { + // RFC18.2.2. Sending Responses + // The server transport uses the value of the top Via header field + // in + // order + // to determine where to send a response. + // It MUST follow the following process: + // If the "sent-protocol" is a reliable transport + // protocol such as TCP or SCTP, + // or TLS over those, the response MUST be + // sent using the existing connection + // to the source of the original request + // that created the transaction, if that connection is still open. + if (isReliable() && !sipStack.isPatchReceivedRport()) { + //https://github.com/RestComm/load-balancer/issues/59 + //we want possibility use Active-Active mode for LB, if one from LBs + //will be shutdown than we can't use + //existing channel instead of we + //should open new channel based on via header + getMessageChannel().sendMessage(transactionResponse); + } else { + Via via = transactionResponse.getTopmostVia(); + String transport = via.getTransport(); + if (transport == null) + throw new IOException("missing transport!"); + // @@@ hagai Symmetric NAT support + int port = via.getRPort(); + if (port == -1) + port = via.getPort(); + if (port == -1) { + if (transport.equalsIgnoreCase("TLS")) + port = 5061; + else + port = 5060; + } + + // Otherwise, if the Via header field value contains a + // "maddr" parameter, the response MUST be forwarded to + // the address listed there, using the port indicated in + // "sent-by", + // or port 5060 if none is present. If the address is a + // multicast + // address, the response SHOULD be sent using + // the TTL indicated in the "ttl" parameter, or with a + // TTL of 1 if that parameter is not present. + String host = null; + if (via.getMAddr() != null) { + host = via.getMAddr(); + } else { + // Otherwise (for unreliable unicast transports), + // if the top Via has a "received" parameter, the response + // MUST + // be sent to the + // address in the "received" parameter, using the port + // indicated + // in the + // "sent-by" value, or using port 5060 if none is specified + // explicitly. + host = via.getParameter(Via.RECEIVED); + if (host == null) { + // Otherwise, if it is not receiver-tagged, the response + // MUST be + // sent to the address indicated by the "sent-by" value, + // using the procedures in Section 5 + // RFC 3263 PROCEDURE TO BE DONE HERE + host = via.getHost(); + } + } + + Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(host, port, + transport)); + + MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack()) + .createRawMessageChannel(this.getSipProvider().getListeningPoint( + hop.getTransport()).getIPAddress(), this.getPort(), hop); + if (messageChannel != null) { + messageChannel.sendMessage(transactionResponse); + lastResponseHost = host; + lastResponsePort = port; + lastResponseTransport = transport; + } else { + throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+ + this.getSipProvider().getListeningPoint( + hop.getTransport()).getIPAddress() + ":" + this.getPort()); + } + + } + lastResponseAsBytes = transactionResponse.encodeAsBytes(this.getTransport()); + lastResponse = null; + } finally { + this.startTransactionTimer(); + } + } + + /** + * Creates a new server transaction. + * + * @param sipStack Transaction stack this transaction belongs to. + * @param newChannelToUse Channel to encapsulate. + */ + protected SIPServerTransactionImpl(SIPTransactionStack sipStack, MessageChannel newChannelToUse) { + + super(sipStack, newChannelToUse); + + // Only one outstanding request for a given server tx. + + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("Creating Server Transaction" + this.getBranchId()); + logger.logStackTrace(); + } + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setRequestInterface(gov.nist.javax.sip.stack.ServerRequestInterface) + */ + @Override + public void setRequestInterface(ServerRequestInterface newRequestOf) { + + requestOf = newRequestOf; + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getResponseChannel() + */ + @Override + public MessageChannel getResponseChannel() { + + return encapsulatedChannel; + + } + + + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#isMessagePartOfTransaction(gov.nist.javax.sip.message.SIPMessage) + */ + @Override + public boolean isMessagePartOfTransaction(SIPMessage messageToTest) { + + // List of Via headers in the message to test +// ViaList viaHeaders; + + // Flags whether the select message is part of this transaction + boolean transactionMatches = false; + final String method = messageToTest.getCSeq().getMethod(); + SIPRequest origRequest = getOriginalRequest(); + // Invite Server transactions linger in the terminated state in the + // transaction + // table and are matched to compensate for + // http://bugs.sipit.net/show_bug.cgi?id=769 + if (isInviteTransaction() || !isTerminated()) { + + // Get the topmost Via header and its branch parameter + final Via topViaHeader = messageToTest.getTopmostVia(); + if (topViaHeader != null) { + +// topViaHeader = (Via) viaHeaders.getFirst(); + // Branch code in the topmost Via header + String messageBranch = topViaHeader.getBranch(); + if (messageBranch != null) { + + // If the branch parameter exists but + // does not start with the magic cookie, + if (!messageBranch.toLowerCase().startsWith( + SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) { + + // Flags this as old + // (RFC2543-compatible) client + // version + messageBranch = null; + + } + + } + + // If a new branch parameter exists, + if (messageBranch != null && this.getBranch() != null) { + if (method.equals(Request.CANCEL)) { + // Cancel is handled as a special case because it + // shares the same same branch id of the invite + // that it is trying to cancel. + transactionMatches = this.getMethod().equals(Request.CANCEL) + && getBranch().equalsIgnoreCase(messageBranch) + && topViaHeader.getSentBy().equals( + origRequest.getTopmostVia() + .getSentBy()); + + } else { + // Matching server side transaction with only the + // branch parameter. + if(origRequest != null) { + transactionMatches = getBranch().equalsIgnoreCase(messageBranch) + && topViaHeader.getSentBy().equals( + origRequest.getTopmostVia() + .getSentBy()); + } else { + transactionMatches = getBranch().equalsIgnoreCase(messageBranch) + && topViaHeader.getSentBy().equals(originalRequestSentBy); + } + + } + + } else { + // force the reparsing only on non RFC 3261 messages + origRequest = (SIPRequest) getRequest(); + + // This is an RFC2543-compliant message; this code is here + // for backwards compatibility. + // It is a weak check. + // If RequestURI, To tag, From tag, CallID, CSeq number, and + // top Via headers are the same, the + // SIPMessage matches this transaction. An exception is for + // a CANCEL request, which is not deemed + // to be part of an otherwise-matching INVITE transaction. + String originalFromTag = origRequest.getFromTag(); + + String thisFromTag = messageToTest.getFrom().getTag(); + + boolean skipFrom = (originalFromTag == null || thisFromTag == null); + + String originalToTag = origRequest.getToTag(); + + String thisToTag = messageToTest.getTo().getTag(); + + boolean skipTo = (originalToTag == null || thisToTag == null); + boolean isResponse = (messageToTest instanceof SIPResponse); + // Issue #96: special case handling for a CANCEL request - + // the CSeq method of the original request must + // be CANCEL for it to have a chance at matching. + if (messageToTest.getCSeq().getMethod().equalsIgnoreCase(Request.CANCEL) + && !origRequest.getCSeq().getMethod().equalsIgnoreCase( + Request.CANCEL)) { + transactionMatches = false; + } else if ((isResponse || origRequest.getRequestURI().equals( + ((SIPRequest) messageToTest).getRequestURI())) + && (skipFrom || originalFromTag != null && originalFromTag.equalsIgnoreCase(thisFromTag)) + && (skipTo || originalToTag != null && originalToTag.equalsIgnoreCase(thisToTag)) + && origRequest.getCallId().getCallId().equalsIgnoreCase( + messageToTest.getCallId().getCallId()) + && origRequest.getCSeq().getSeqNumber() == messageToTest + .getCSeq().getSeqNumber() + && ((!messageToTest.getCSeq().getMethod().equals(Request.CANCEL)) || + getMethod().equals(messageToTest.getCSeq().getMethod())) + && topViaHeader.equals(origRequest.getTopmostVia())) { + + transactionMatches = true; + } + + } + + } + + } + return transactionMatches; + + } + + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#map() + */ + @Override + public void map() { + // note that TRYING is a pseudo-state for invite transactions + + int realState = getRealState(); + + if (realState < 0 || realState == TransactionState._TRYING) { + // Also sent by intermediate proxies. + // null check added as the stack may be stopped. TRYING is not sent by reliable transports. + if (isInviteTransaction() && !this.isMapped && sipStack.getTimer() != null ) { + this.isMapped = true; + // Schedule a timer to fire in 200 ms if the + // TU did not send a trying in that time. + sipStack.getTimer().schedule(new SendTrying(), 200); + + } else { + isMapped = true; + } + } + + // Pull it out of the pending transactions list. + sipStack.removePendingTransaction(this); + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#processRequest(gov.nist.javax.sip.message.SIPRequest, gov.nist.javax.sip.stack.MessageChannel) + */ + @Override + public void processRequest(SIPRequest transactionRequest, MessageChannel sourceChannel) { + boolean toTu = false; + + // Can only process a single request directed to the + // transaction at a time. For a given server transaction + // the listener sees only one event at a time. + + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("processRequest: " + transactionRequest.getFirstLine()); + logger.logDebug("tx state = " + this.getRealState()); + } + + try { + + // If this is the first request for this transaction, + if (getRealState() < 0) { + // Save this request as the one this + // transaction is handling + setOriginalRequest(transactionRequest); + this.setState(TransactionState._TRYING); + toTu = true; + this.setPassToListener(); + + // Rsends the TRYING on retransmission of the request. + if (isInviteTransaction() && this.isMapped) { + // JvB: also + // proxies need + // to do this + + // Has side-effect of setting + // state to "Proceeding" + sendMessage(transactionRequest.createResponse(100, "Trying")); + + } + // If an invite transaction is ACK'ed while in + // the completed state, + } else if (isInviteTransaction() && TransactionState._COMPLETED == getRealState() + && transactionRequest.getMethod().equals(Request.ACK)) { + + // @jvB bug fix + this.setState(TransactionState._CONFIRMED); + disableRetransmissionTimer(); + if (!isReliable()) { + enableTimeoutTimer(timerI); + + } else { + + this.setState(TransactionState._TERMINATED); + + } + + + // JvB: For the purpose of testing a TI, added a property to + // pass it anyway + if (sipStack.isNon2XXAckPassedToListener()) { + // This is useful for test applications that want to see + // all messages. + requestOf.processRequest(transactionRequest, encapsulatedChannel); + } else { + // According to RFC3261 Application should not Ack in + // CONFIRMED state + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("ACK received for server Tx " + + this.getTransactionId() + " not delivering to application!"); + + } + + this.semRelease(); + } + return; + + // If we receive a retransmission of the original + // request, + } else if (transactionRequest.getMethod().equals(getMethod())) { + + if (TransactionState._PROCEEDING == getRealState() + || TransactionState._COMPLETED == getRealState()) { + this.semRelease(); + // Resend the last response to + // the client + // Send the message to the client + resendLastResponseAsBytes(); + } else if (transactionRequest.getMethod().equals(Request.ACK)) { + // This is passed up to the TU to suppress + // retransmission of OK + if (requestOf != null) + requestOf.processRequest(transactionRequest, encapsulatedChannel); + else + this.semRelease(); + } else { + // none of the above? well release the lock anyhow! + this.semRelease(); + } + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("completed processing retransmitted request : " + + transactionRequest.getFirstLine() + this + " txState = " + + this.getState() + " lastResponse = " + this.lastResponseAsBytes); + return; + + } + + // Pass message to the TU + if (TransactionState._COMPLETED != getRealState() + && TransactionState._TERMINATED != getRealState() && requestOf != null) { + if (getMethod().equals(transactionRequest.getMethod())) { + // Only send original request to TU once! + if (toTu) { + requestOf.processRequest(transactionRequest, encapsulatedChannel); + } else + this.semRelease(); + } else { + if (requestOf != null) + requestOf.processRequest(transactionRequest, encapsulatedChannel); + else + this.semRelease(); + } + } else { + // This seems like a common bug so I am allowing it through! + if (SIPTransactionStack.isDialogCreated(getMethod()) + && getRealState() == TransactionState._TERMINATED + && transactionRequest.getMethod().equals(Request.ACK) + && requestOf != null) { + SIPDialog thisDialog = (SIPDialog) getDialog(); + + if (thisDialog == null || !thisDialog.ackProcessed) { + // Filter out duplicate acks + if (thisDialog != null) { + thisDialog.ackReceived(transactionRequest.getCSeq().getSeqNumber()); + thisDialog.ackProcessed = true; + } + requestOf.processRequest(transactionRequest, encapsulatedChannel); + } else { + this.semRelease(); + } + + } else if (transactionRequest.getMethod().equals(Request.CANCEL)) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("Too late to cancel Transaction"); + this.semRelease(); + // send OK and just ignore the CANCEL. + try { + this.sendMessage(transactionRequest.createResponse(Response.OK)); + } catch (IOException ex) { + // Transaction is already terminated + // just ignore the IOException. + } + } else { + // none of the above? well release the lock anyhow! + this.semRelease(); + } + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("Dropping request " + getRealState()); + } + + } catch (IOException e) { + if (logger.isLoggingEnabled()) + logger.logError("IOException " ,e); + this.semRelease(); + this.raiseIOExceptionEvent(); + } + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#sendMessage(gov.nist.javax.sip.message.SIPMessage) + */ + @Override + public void sendMessage(SIPMessage messageToSend) throws IOException { + if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("sipServerTransaction::sendMessage " + messageToSend.getFirstLine()); + } + // Message typecast as a response + final SIPResponse transactionResponse = (SIPResponse) messageToSend; + // Status code of the response being sent to the client + final int statusCode = transactionResponse.getStatusCode(); + try { + + try { + // Provided we have set the banch id for this we set the BID for + // the + // outgoing via. + if (originalRequestBranch != null) + transactionResponse.getTopmostVia().setBranch(this.getBranch()); + else + transactionResponse.getTopmostVia().removeParameter(ParameterNames.BRANCH); + + // Make the topmost via headers match identically for the + // transaction rsponse. + if (!originalRequestHasPort) + transactionResponse.getTopmostVia().removePort(); + } catch (ParseException ex) { + logger.logError("UnexpectedException",ex); + throw new IOException("Unexpected exception"); + } + + // Method of the response does not match the request used to + // create the transaction - transaction state does not change. + if (!transactionResponse.getCSeq().getMethod().equals( + getMethod())) { + sendResponse(transactionResponse); + return; + } + + if(!checkStateTimers(statusCode)) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("checkStateTimers returned false -- not sending message"); + } + return; + } + + try { + // Send the message to the client. + // Record the last message sent out. + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug( + "sendMessage : tx = " + this + " getState = " + this.getState()); + } + lastResponse = transactionResponse; + lastResponseStatusCode = transactionResponse.getStatusCode(); + + this.sendResponse(transactionResponse); + + } catch (IOException e) { + + this.setState(TransactionState._TERMINATED); + this.collectionTime = 0; + throw e; + + } + } finally { + this.startTransactionTimer(); + } + + } + + + private boolean checkStateTimers(int statusCode) { + // If the TU sends a provisional response while in the + // trying state, + + if (getRealState() == TransactionState._TRYING) { + if (statusCode / 100 == 1) { + this.setState(TransactionState._PROCEEDING); + } else if (200 <= statusCode && statusCode <= 699) { + // INVITE ST has TRYING as a Pseudo state + // (See issue 76). We are using the TRYING + // pseudo state invite Transactions + // to signal if the application + // has sent trying or not and hence this + // check is necessary. + if (!isInviteTransaction()) { + if (!isReliable() && getInternalState() != TransactionState._COMPLETED) { + // Linger in the completed state to catch + // retransmissions if the transport is not + // reliable. + this.setState(TransactionState._COMPLETED); + // Note that Timer J is only set for Unreliable + // transports -- see Issue 75. + /* + * From RFC 3261 Section 17.2.2 (non-invite server transaction) + * + * When the server transaction enters the "Completed" state, it MUST + * set Timer J to fire in 64*T1 seconds for unreliable transports, and + * zero seconds for reliable transports. While in the "Completed" + * state, the server transaction MUST pass the final response to the + * transport layer for retransmission whenever a retransmission of the + * request is received. Any other final responses passed by the TU to + * the server transaction MUST be discarded while in the "Completed" + * state. The server transaction remains in this state until Timer J + * fires, at which point it MUST transition to the "Terminated" state. + */ + startTransactionTimerJ(TIMER_J); + cleanUpOnTimer(); + } else { + cleanUpOnTimer(); + this.setState(TransactionState._TERMINATED); + startTransactionTimerJ(0); + } + } else { + // This is the case for INVITE server transactions. + // essentially, it duplicates the code in the + // PROCEEDING case below. There is no TRYING state for INVITE + // transactions in the RFC. We are using it to signal whether the + // application has sent a provisional response or not. Hence + // this is treated the same as as Proceeding. + if (statusCode / 100 == 2) { + // Status code is 2xx means that the + // transaction transitions to TERMINATED + // for both Reliable as well as unreliable + // transports. Note that the dialog layer + // takes care of retransmitting 2xx final + // responses. + /* + * RFC 3261 Section 13.3.1.4 Note, however, that the INVITE server + * transaction will be destroyed as soon as it receives this final + * response and passes it to the transport. Therefore, it is necessary + * to periodically pass the response directly to the transport until + * the ACK arrives. The 2xx response is passed to the transport with + * an interval that starts at T1 seconds and doubles for each + * retransmission until it reaches T2 seconds (T1 and T2 are defined + * in Section 17). Response retransmissions cease when an ACK request + * for the response is received. This is independent of whatever + * transport protocols are used to send the response. + */ + this.disableRetransmissionTimer(); + this.disableTimeoutTimer(); + this.collectionTime = TIMER_J; + cleanUpOnTimer(); + this.setState(TransactionState._TERMINATED); + if (this.getDialog() != null) + ((SIPDialog) this.getDialog()).setRetransmissionTicks(); + } else { + // This an error final response. + this.setState(TransactionState._COMPLETED); + if (!isReliable()) { + /* + * RFC 3261 + * + * While in the "Proceeding" state, if the TU passes a response + * with status code from 300 to 699 to the server transaction, the + * response MUST be passed to the transport layer for + * transmission, and the state machine MUST enter the "Completed" + * state. For unreliable transports, timer G is set to fire in T1 + * seconds, and is not set to fire for reliable transports. + */ + + enableRetransmissionTimer(); + + } + cleanUpOnTimer(); + enableTimeoutTimer(TIMER_H); + } + } + + } + + // If the transaction is in the proceeding state, + } else if (getRealState() == TransactionState._PROCEEDING) { + + if (isInviteTransaction()) { + + // If the response is a failure message, + if (statusCode / 100 == 2) { + // Set up to catch returning ACKs + // The transaction lingers in the + // terminated state for some time + // to catch retransmitted INVITEs + this.disableRetransmissionTimer(); + this.disableTimeoutTimer(); + this.collectionTime = TIMER_J; + cleanUpOnTimer(); + this.setState(TransactionState._TERMINATED); + if (this.getDialog() != null) + ((SIPDialog) this.getDialog()).setRetransmissionTicks(); + + } else if (300 <= statusCode && statusCode <= 699) { + + // Set up to catch returning ACKs + this.setState(TransactionState._COMPLETED); + if (!isReliable()) { + /* + * While in the "Proceeding" state, if the TU passes a response with + * status code from 300 to 699 to the server transaction, the response + * MUST be passed to the transport layer for transmission, and the + * state machine MUST enter the "Completed" state. For unreliable + * transports, timer G is set to fire in T1 seconds, and is not set to + * fire for reliable transports. + */ + enableRetransmissionTimer(); + } + cleanUpOnTimer(); + enableTimeoutTimer(TIMER_H); + } + // If the transaction is not an invite transaction + // and this is a final response, + } else if (200 <= statusCode && statusCode <= 699) { + // This is for Non-invite server transactions. + // Set up to retransmit this response, + // or terminate the transaction + this.setState(TransactionState._COMPLETED); + if (!isReliable()) { + + disableRetransmissionTimer(); +// enableTimeoutTimer(TIMER_J); + startTransactionTimerJ(TIMER_J); + } else { + this.setState(TransactionState._TERMINATED); + startTransactionTimerJ(0); + } + cleanUpOnTimer(); + } + // If the transaction has already completed, + } else if (TransactionState._COMPLETED == this.getRealState()) { + return false; + } + return true; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getViaHost() + */ + @Override + public String getViaHost() { + return super.getViaHost(); + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getViaPort() + */ + @Override + public int getViaPort() { + return super.getViaPort(); + } + + + /** + * @see gov.nist.javax.sip.stack.SIPTransactionImpl#fireRetransmissionTimer() + */ + @Override + public void fireRetransmissionTimer() { + + try { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("fireRetransmissionTimer() -- " + this + " state " + getState()); + } + // Resend the last response sent by this transaction + if (isInviteTransaction() && (lastResponse != null || lastResponseAsBytes != null)) { + // null can happen if this is terminating when the timer fires. + if (!this.retransmissionAlertEnabled || sipStack.isTransactionPendingAck(this) ) { + // Retransmit last response until ack. + if (lastResponseStatusCode / 100 >= 2 && !this.isAckSeen) { + resendLastResponseAsBytes(); + } + } else { + // alert the application to retransmit the last response + SipProviderImpl sipProvider = (SipProviderImpl) this.getSipProvider(); + TimeoutEvent txTimeout = new TimeoutEvent(sipProvider, this, + Timeout.RETRANSMIT); + sipProvider.handleEvent(txTimeout, this); + } + + } + } catch (IOException e) { + if (logger.isLoggingEnabled()) + logger.logException(e); + raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); + + } + + } + + // jeand we nullify the last response very fast to save on mem and help GC but we keep it as byte array + // so this method is used to resend the last response either as a response or byte array depending on if it has been nullified + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#resendLastResponseAsBytes() + */ + @Override + public void resendLastResponseAsBytes() throws IOException { + + if(lastResponse != null) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("resend last response " + lastResponse); + } + sendMessage(lastResponse); + } else if (lastResponseAsBytes != null) { + // Send the message to the client +// if(!checkStateTimers(lastResponseStatusCode)) { +// return; +// } + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("resend last response " + new String(lastResponseAsBytes)); + } + + if(isReliable()) { + if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) { + // Issue 343 : we have to log the retransmission + try { + SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null); + + lastReparsedResponse.setRemoteAddress( + this.getPeerInetAddress()); + lastReparsedResponse.setRemotePort(this.getPeerPort()); + lastReparsedResponse.setLocalPort( + getMessageChannel().getPort()); + lastReparsedResponse.setLocalAddress( + getMessageChannel() + .getMessageProcessor().getIpAddress()); + + getMessageChannel().logMessage(lastReparsedResponse, this.getPeerInetAddress(), this.getPeerPort(), System.currentTimeMillis()); + } catch (ParseException e) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes)); + } + } + } + getMessageChannel().sendMessage(lastResponseAsBytes, this.getPeerInetAddress(), this.getPeerPort(), false); + } else { + Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(lastResponseHost, lastResponsePort, + lastResponseTransport)); + + MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack()) + .createRawMessageChannel(this.getSipProvider().getListeningPoint( + hop.getTransport()).getIPAddress(), this.getPort(), hop); + if (messageChannel != null) { + if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) { + // Issue 343 : we have to log the retransmission + try { + SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null); + + lastReparsedResponse.setRemoteAddress(messageChannel.getPeerInetAddress()); + lastReparsedResponse.setRemotePort(messageChannel.getPeerPort()); + lastReparsedResponse.setLocalPort(messageChannel.getPort()); + lastReparsedResponse.setLocalAddress(messageChannel.getMessageProcessor().getIpAddress()); + + messageChannel.logMessage(lastReparsedResponse, messageChannel.getPeerInetAddress(), messageChannel.getPeerPort(), System.currentTimeMillis()); + } catch (ParseException e) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes)); + } + } + } + messageChannel.sendMessage(lastResponseAsBytes, InetAddress.getByName(hop.getHost()), hop.getPort(), false); + } else { + throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+ + this.getSipProvider().getListeningPoint( + hop.getTransport()).getIPAddress() + ":" + this.getPort()); + } + } + } + } + + private void fireReliableResponseRetransmissionTimer() { + try { + resendLastResponseAsBytes(); + } catch (IOException e) { + if (logger.isLoggingEnabled()) + logger.logException(e); + this.setState(TransactionState._TERMINATED); + raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); + + } + } + + + /** + * @see gov.nist.javax.sip.stack.SIPTransaction#fireTimeoutTimer() + */ + public void fireTimeoutTimer() { + + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("SIPServerTransaction.fireTimeoutTimer this = " + this + + " current state = " + this.getRealState() + " method = " + + this.getMethod()); + + if (isInviteTransaction() && sipStack.removeTransactionPendingAck(this) ) { + if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) ) { + logger.logDebug("Found tx pending ACK - timer H has kicked"); + } + + } + SIPDialog dialog = (SIPDialog) getDialog(); + + + if (SIPTransactionStack.isDialogCreated(getMethod()) + && (TransactionState._CALLING == this.getRealState() || TransactionState._TRYING == this + .getRealState())) { + dialog.setState(SIPDialog.TERMINATED_STATE); + } else if (getMethod().equals(Request.BYE)) { + if (dialog != null && dialog.isTerminatedOnBye()) + dialog.setState(SIPDialog.TERMINATED_STATE); + } + + if (TransactionState._COMPLETED == this.getRealState() && isInviteTransaction()) { + raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); + this.setState(TransactionState._TERMINATED); + sipStack.removeTransaction(this); + + } else if (TransactionState._COMPLETED == this.getRealState() && !isInviteTransaction()) { + this.setState(TransactionState._TERMINATED); + if(!getMethod().equals(Request.CANCEL)) { + cleanUp(); + } else { + sipStack.removeTransaction(this); + } + + } else if (TransactionState._CONFIRMED == this.getRealState() && isInviteTransaction()) { + // TIMER_I should not generate a timeout + // exception to the application when the + // Invite transaction is in Confirmed state. + // Just transition to Terminated state. + this.setState(TransactionState._TERMINATED); + sipStack.removeTransaction(this); + } else if (!isInviteTransaction() + && (TransactionState._COMPLETED == this.getRealState() || TransactionState._CONFIRMED == this + .getRealState())) { + this.setState(TransactionState._TERMINATED); + } else if (isInviteTransaction() && TransactionState._TERMINATED == this.getRealState()) { + // This state could be reached when retransmitting + + raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); + // TODO -- check this. This does not look right. + if (dialog != null) + dialog.setState(SIPDialog.TERMINATED_STATE); + } + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getLastResponseStatusCode() + */ + @Override + public int getLastResponseStatusCode() { + return this.lastResponseStatusCode; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setOriginalRequest(gov.nist.javax.sip.message.SIPRequest) + */ + @Override + public void setOriginalRequest(SIPRequest originalRequest) { + super.setOriginalRequest(originalRequest); + + } + + /* + * (non-Javadoc) + * + * @see javax.sip.ServerTransaction#sendResponse(javax.sip.message.Response) + */ + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#sendResponse(javax.sip.message.Response) + */ + @Override + public void sendResponse(Response response) throws SipException { + SIPResponse sipResponse = (SIPResponse) response; + + SIPDialog dialog = (SIPDialog) getDialog(); + if (response == null) + throw new NullPointerException("null response"); + + try { + sipResponse.checkHeaders(); + } catch (ParseException ex) { + throw new IllegalTransactionStateException(ex.getMessage(), Reason.MissingRequiredHeader); + } + + // check for meaningful response. + final String responseMethod = sipResponse.getCSeq().getMethod(); + if (!responseMethod.equals(this.getMethod())) { + throw new IllegalTransactionStateException( + "CSeq method does not match Request method of request that created the tx.", Reason.UnmatchingCSeq); + } + + /* + * 200-class responses to SUBSCRIBE requests also MUST contain an "Expires" header. The + * period of time in the response MAY be shorter but MUST NOT be longer than specified in + * the request. + */ + final int statusCode = response.getStatusCode(); + if (this.getMethod().equals(Request.SUBSCRIBE) && statusCode / 100 == 2) { + + if (response.getHeader(ExpiresHeader.NAME) == null) { + throw new IllegalTransactionStateException("Expires header is mandatory in 2xx response of SUBSCRIBE", Reason.ExpiresHeaderMandatory); + } else { + Expires requestExpires = (Expires) this.getOriginalRequest().getExpires(); + Expires responseExpires = (Expires) response.getExpires(); + /* + * If no "Expires" header is present in a SUBSCRIBE request, the implied default + * is defined by the event package being used. + */ + if (requestExpires != null + && responseExpires.getExpiresLong() > requestExpires.getExpiresLong()) { + throw new SipException( + "Response Expires time exceeds request Expires time : See RFC 3265 3.1.1"); + } + } + + } + + // Check for mandatory header. + if (statusCode == 200 + && responseMethod.equals(Request.INVITE) + && sipResponse.getHeader(ContactHeader.NAME) == null) + throw new IllegalTransactionStateException("Contact Header is mandatory for the OK to the INVITE", Reason.ContactHeaderMandatory); + + if (!this.isMessagePartOfTransaction((SIPMessage) response)) { + throw new SipException("Response does not belong to this transaction."); + } + + // Fix up the response if the dialog has already been established. + try { + /* + * The UAS MAY send a final response to the initial request before + * having received PRACKs for all unacknowledged reliable provisional responses, + * unless the final response is 2xx and any of the unacknowledged reliable provisional + * responses contained a session description. In that case, it MUST NOT send a final + * response until those provisional responses are acknowledged. + */ + final ContentTypeHeader contentTypeHeader = this.pendingReliableResponseContentType; + if (this.pendingReliableResponseAsBytes != null + && this.getDialog() != null + && this.getInternalState() != TransactionState._TERMINATED + && statusCode / 100 == 2 + && contentTypeHeader != null + && contentTypeHeader.getContentType() + .equalsIgnoreCase(CONTENT_TYPE_APPLICATION) + && contentTypeHeader.getContentSubType() + .equalsIgnoreCase(CONTENT_SUBTYPE_SDP)) { + if (!interlockProvisionalResponses ) { + throw new SipException("cannot send response -- unacked provisional"); + } else { + try { + boolean acquired = this.provisionalResponseSem.tryAcquire(1,TimeUnit.SECONDS); + if (!acquired ) { + throw new SipException("cannot send response -- unacked provisional"); + } + } catch (InterruptedException ex) { + logger.logError ("Interrupted acuqiring PRACK sem"); + throw new SipException("Cannot aquire PRACK sem"); + } + + } + } else { + // Sending the final response cancels the + // pending response task. + if (this.pendingReliableResponseAsBytes != null && sipResponse.isFinalResponse()) { + sipStack.getTimer().cancel(provisionalResponseTask); + this.provisionalResponseTask = null; + } + } + + // Dialog checks. These make sure that the response + // being sent makes sense. + if (dialog != null) { + if (statusCode / 100 == 2 + && SIPTransactionStack.isDialogCreated(responseMethod)) { + if (dialog.getLocalTag() == null && sipResponse.getToTag() == null) { + // Trying to send final response and user forgot to set + // to + // tag on the response -- be nice and assign the tag for + // the user. + sipResponse.getTo().setTag(Utils.getInstance().generateTag()); + } else if (dialog.getLocalTag() != null && sipResponse.getToTag() == null) { + if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("assigning toTag : serverTransaction = " + this + " dialog " + + dialog + " tag = " + dialog.getLocalTag()); + } + sipResponse.setToTag(dialog.getLocalTag()); + } else if (dialog.getLocalTag() != null && sipResponse.getToTag() != null + && !dialog.getLocalTag().equals(sipResponse.getToTag())) { + throw new SipException("Tag mismatch dialogTag is " + + dialog.getLocalTag() + " responseTag is " + + sipResponse.getToTag()); + } + } + + if (!sipResponse.getCallId().getCallId().equals(dialog.getCallId().getCallId())) { + throw new SipException("Dialog mismatch!"); + } + } + + + + // Backward compatibility slippery slope.... + // Only set the from tag in the response when the + // incoming request has a from tag. + String fromTag = originalRequestFromTag; + if(getRequest() != null) { + fromTag = ((SIPRequest) this.getRequest()).getFromTag(); + } + if (fromTag != null && sipResponse.getFromTag() != null + && !sipResponse.getFromTag().equals(fromTag)) { + throw new SipException("From tag of request does not match response from tag"); + } else if (fromTag != null) { + sipResponse.getFrom().setTag(fromTag); + } else { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("WARNING -- Null From tag in request!!"); + } + // See if the dialog needs to be inserted into the dialog table + // or if the state of the dialog needs to be changed. + if (dialog != null && statusCode != 100) { + dialog.setResponseTags(sipResponse); + DialogState oldState = dialog.getState(); + dialog.setLastResponse(this, (SIPResponse) response); + if (oldState == null && dialog.getState() == DialogState.TERMINATED) { + DialogTerminatedEvent event = new DialogTerminatedEvent(dialog + .getSipProvider(), dialog); + + // Provide notification to the listener that the dialog has + // ended. + dialog.getSipProvider().handleEvent(event, this); + + } + + } else if (dialog == null && isInviteTransaction() + && this.retransmissionAlertEnabled + && this.retransmissionAlertTimerTask == null + && response.getStatusCode() / 100 == 2) { + String dialogId = ((SIPResponse) response).getDialogId(true); + + this.retransmissionAlertTimerTask = new RetransmissionAlertTimerTask(dialogId); + sipStack.retransmissionAlertTransactions.put(dialogId, this); + sipStack.getTimer().scheduleWithFixedDelay(this.retransmissionAlertTimerTask, 0, + SIPTransactionStack.BASE_TIMER_INTERVAL); + + } + + // Send message after possibly inserting the Dialog + // into the dialog table to avoid a possible race condition. + + + this.sendMessage((SIPResponse) response); + + + + if ( dialog != null ) { + dialog.startRetransmitTimer(this, (SIPResponse)response); + } + + } catch (IOException ex) { + if (logger.isLoggingEnabled()) + logger.logException(ex); + this.setState(TransactionState._TERMINATED); + raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); + throw new SipException(ex.getMessage(), ex); + } catch (java.text.ParseException ex1) { + if (logger.isLoggingEnabled()) + logger.logException(ex1); + this.setState(TransactionState._TERMINATED); + throw new SipException(ex1.getMessage(), ex1); + } + } + + /** + * Return the book-keeping information that we actually use. + */ + protected int getRealState() { + return super.getInternalState(); + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getState() + */ + @Override + public TransactionState getState() { + // Trying is a pseudo state for INVITE transactions. + if (this.isInviteTransaction() && TransactionState._TRYING == super.getInternalState()) + return TransactionState.PROCEEDING; + else + return super.getState(); + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setState(int) + */ + @Override + public void setState(int newState) { + // Set this timer for connection caching + // of incoming connections. + if (newState == TransactionState._TERMINATED && this.isReliable() + && (!getSIPStack().cacheServerConnections)) { + // Set a time after which the connection + // is closed. + this.collectionTime = TIMER_J; + this.terminationSemaphore.release(); + } + + super.setState(newState); + + } + + /** + * @see gov.nist.javax.sip.stack.SIPTransaction#startTransactionTimer() + */ + @Override + public void startTransactionTimer() { + if(getMethod().equalsIgnoreCase(Request.INVITE) || getMethod().equalsIgnoreCase(Request.CANCEL) || getMethod().equalsIgnoreCase(Request.ACK)) { + if (this.transactionTimerStarted.compareAndSet(false, true)) { + if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { + // The timer is set to null when the Stack is + // shutting down. + SIPStackTimerTask myTimer = new TransactionTimer(); + // Do not schedule when the stack is not alive. + if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { + sipStack.getTimer().scheduleWithFixedDelay(myTimer, baseTimerInterval, baseTimerInterval); + } + myTimer = null; + } + } + } + } + + /** + * Start the timer task. + */ + protected void startTransactionTimerJ(long time) { + if (this.transactionTimerStarted.compareAndSet(false, true)) { + if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("starting TransactionTimerJ() : " + getTransactionId() + " time " + time); + } + // The timer is set to null when the Stack is + // shutting down. + SIPStackTimerTask task = new SIPStackTimerTask () { + + public void runTask() { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("executing TransactionTimerJ() : " + getTransactionId()); + } + fireTimeoutTimer(); + cleanUp(); + if(originalRequest != null) { + originalRequest.cleanUp(); + } + } + + @Override + public Object getThreadHash() { + Request request = getRequest(); + if (request != null && request instanceof SIPRequest) { + return ((SIPRequest)request).getCallIdHeader().getCallId(); + } else { + return null; + } + } + }; + if(time > 0) { + sipStack.getTimer().schedule(task, time * T1 * baseTimerInterval); + } else { + task.runTask(); + } + } + } + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#equals(java.lang.Object) + */ + @Override + public boolean equals(Object other) { + if(other == null) return false; + if (!other.getClass().equals(this.getClass())) { + return false; + } + SIPServerTransaction sst = (SIPServerTransaction) other; + return this.getBranch().equalsIgnoreCase(sst.getBranch()); + } + + /* + * (non-Javadoc) + * + * @see gov.nist.javax.sip.stack.SIPTransaction#getDialog() + */ + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getDialog() + */ + @Override + public Dialog getDialog() { + if(dialog == null && dialogId != null) { + return sipStack.getDialog(dialogId); + } + return dialog; + } + + /* + * (non-Javadoc) + * + * @see gov.nist.javax.sip.stack.SIPTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, + * gov.nist.javax.sip.message.SIPMessage) + */ + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, java.lang.String) + */ + @Override + public void setDialog(SIPDialog sipDialog, String dialogId) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("setDialog " + this + " dialog = " + sipDialog); + this.dialog = sipDialog; + this.dialogId = dialogId; + if (dialogId != null) + sipDialog.setAssigned(); + if (this.retransmissionAlertEnabled && this.retransmissionAlertTimerTask != null) { + sipStack.getTimer().cancel(retransmissionAlertTimerTask); + if (this.retransmissionAlertTimerTask.dialogId != null) { + sipStack.retransmissionAlertTransactions + .remove(this.retransmissionAlertTimerTask.dialogId); + } + this.retransmissionAlertTimerTask = null; + } + + this.retransmissionAlertEnabled = false; + + } + + /* + * (non-Javadoc) + * + * @see javax.sip.Transaction#terminate() + */ + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#terminate() + */ + @Override + public void terminate() throws ObjectInUseException { + this.setState(TransactionState._TERMINATED); + if (this.retransmissionAlertTimerTask != null) { + sipStack.getTimer().cancel(retransmissionAlertTimerTask); + if (retransmissionAlertTimerTask.dialogId != null) { + this.sipStack.retransmissionAlertTransactions + .remove(retransmissionAlertTimerTask.dialogId); + } + this.retransmissionAlertTimerTask = null; + + } + if(!transactionTimerStarted.get()) { + // if no transaction timer was started just remove the tx without firing a transaction terminated event + testAndSetTransactionTerminatedEvent(); + sipStack.removeTransaction(this); + } + } + + @Override + public void sendReliableProvisionalResponse(Response relResponse) throws SipException { + + /* + * After the first reliable provisional response for a request has been acknowledged, the + * UAS MAY send additional reliable provisional responses. The UAS MUST NOT send a second + * reliable provisional response until the first is acknowledged. + */ + if (this.pendingReliableResponseAsBytes != null) { + throw new SipException("Unacknowledged response"); + + } else { + SIPResponse reliableResponse = (SIPResponse) relResponse; + this.pendingReliableResponseAsBytes = reliableResponse.encodeAsBytes(this.getTransport()); + this.pendingReliableResponseMethod = reliableResponse.getCSeq().getMethod(); + this.pendingReliableCSeqNumber = reliableResponse.getCSeq().getSeqNumber(); + this.pendingReliableResponseContentType = reliableResponse.getContentTypeHeader(); + } + /* + * In addition, it MUST contain a Require header field containing the option tag 100rel, + * and MUST include an RSeq header field. + */ + RSeq rseq = (RSeq) relResponse.getHeader(RSeqHeader.NAME); + if (relResponse.getHeader(RSeqHeader.NAME) == null) { + rseq = new RSeq(); + relResponse.setHeader(rseq); + } + + try { + if(rseqNumber < 0) { + this.rseqNumber = (int) (Math.random() * 1000); + } + this.rseqNumber++; + rseq.setSeqNumber(this.rseqNumber); + this.pendingReliableRSeqNumber = rseq.getSeqNumber(); + + // start the timer task which will retransmit the reliable response + // until the PRACK is received. Cannot send a second provisional. + this.lastResponse = (SIPResponse) relResponse; + if ( this.getDialog() != null && interlockProvisionalResponses ) { + boolean acquired = this.provisionalResponseSem.tryAcquire(1, TimeUnit.SECONDS); + if (!acquired) { + throw new SipException("Unacknowledged reliable response"); + } + } + //moved the task scheduling before the sending of the message to overcome + // Issue 265 : https://jain-sip.dev.java.net/issues/show_bug.cgi?id=265 + this.provisionalResponseTask = new ProvisionalResponseTask(); + this.sipStack.getTimer().scheduleWithFixedDelay(provisionalResponseTask, 0, + SIPTransactionStack.BASE_TIMER_INTERVAL); + this.sendMessage((SIPMessage) relResponse); + } catch (Exception ex) { + InternalErrorHandler.handleException(ex); + } + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getReliableProvisionalResponse() + */ + @Override + public byte[] getReliableProvisionalResponse() { + + return this.pendingReliableResponseAsBytes; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#prackRecieved() + */ + @Override + public boolean prackRecieved() { + + if (this.pendingReliableResponseAsBytes == null) + return false; + if(provisionalResponseTask != null) { + sipStack.getTimer().cancel(provisionalResponseTask); + this.provisionalResponseTask = null; + } + + this.pendingReliableResponseAsBytes = null; + this.pendingReliableResponseContentType = null; + if ( interlockProvisionalResponses && getDialog() != null ) { + this.provisionalResponseSem.release(); + } + return true; + } + + /* + * (non-Javadoc) + * + * @see javax.sip.ServerTransaction#enableRetransmissionAlerts() + */ + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#enableRetransmissionAlerts() + */ + @Override + public void enableRetransmissionAlerts() throws SipException { + if (this.getDialog() != null) + throw new SipException("Dialog associated with tx"); + + else if (!isInviteTransaction()) + throw new SipException("Request Method must be INVITE"); + + this.retransmissionAlertEnabled = true; + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#isRetransmissionAlertEnabled() + */ + @Override + public boolean isRetransmissionAlertEnabled() { + return this.retransmissionAlertEnabled; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#disableRetransmissionAlerts() + */ + @Override + public void disableRetransmissionAlerts() { + if (this.retransmissionAlertTimerTask != null && this.retransmissionAlertEnabled) { + sipStack.getTimer().cancel(retransmissionAlertTimerTask); + this.retransmissionAlertEnabled = false; + + String dialogId = this.retransmissionAlertTimerTask.dialogId; + if (dialogId != null) { + sipStack.retransmissionAlertTransactions.remove(dialogId); + } + this.retransmissionAlertTimerTask = null; + } + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setAckSeen() + */ + @Override + public void setAckSeen() { + this.isAckSeen = true; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#ackSeen() + */ + @Override + public boolean ackSeen() { + return this.isAckSeen; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setMapped(boolean) + */ + @Override + public void setMapped(boolean b) { + this.isMapped = true; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#setPendingSubscribe(gov.nist.javax.sip.stack.SIPClientTransaction) + */ + @Override + public void setPendingSubscribe(SIPClientTransaction pendingSubscribeClientTx) { + this.pendingSubscribeTransaction = pendingSubscribeClientTx; + + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#releaseSem() + */ + @Override + public void releaseSem() { + if (this.pendingSubscribeTransaction != null) { + /* + * When a notify is being processed we take a lock on the subscribe to avoid racing + * with the OK of the subscribe. + */ + if (! sipStack.isDeliverUnsolicitedNotify()) { + pendingSubscribeTransaction.releaseSem(); + } + } else if (this.inviteTransaction != null && this.getMethod().equals(Request.CANCEL)) { + /* + * When a CANCEL is being processed we take a nested lock on the associated INVITE + * server tx. + */ + this.inviteTransaction.releaseSem(); + } + super.releaseSem(); + } + + /** + * The INVITE Server Transaction corresponding to a CANCEL Server Transaction. + * + * @param st -- the invite server tx corresponding to the cancel server transaction. + */ + @Override + public void setInviteTransaction(SIPServerTransaction st) { + this.inviteTransaction = st; + + } + + /** + * TODO -- this method has to be added to the api. + * + * @return + */ + @Override + public SIPServerTransaction getCanceledInviteTransaction() { + return this.inviteTransaction; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#scheduleAckRemoval() + */ + @Override + public void scheduleAckRemoval() throws IllegalStateException { + if (this.getMethod() == null || !this.getMethod().equals(Request.ACK)) { + throw new IllegalStateException("Method is null[" + (getMethod() == null) + + "] or method is not ACK[" + this.getMethod() + "]"); + } + + this.startTransactionTimer(); + } + + // jeand cleanup the state of the stx to help GC + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#cleanUp() + */ + @Override + public void cleanUp() { + // Remove it from the set + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) + logger.logDebug("removing" + this); + + if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { + + // release the connection associated with this transaction. + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("cleanup : " + + getTransactionId()); + } + // we keep the request in a byte array to be able to recreate it + // no matter what to keep API backward compatibility + if(originalRequest == null && originalRequestBytes != null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { + try { + originalRequest = (SIPRequest) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(originalRequestBytes, true, false, null); +// originalRequestBytes = null; + } catch (ParseException e) { + logger.logError("message " + originalRequestBytes + "could not be reparsed !"); + } + } else if (originalRequest != null && originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { + originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport()); + } + // http://java.net/jira/browse/JSIP-429 + // store the merge id from the tx to avoid reparsing of request on aggressive cleanup + if (originalRequest != null && originalRequestBytes == null) { + super.mergeId = ((SIPRequest)originalRequest).getMergeId(); + } + sipStack.removeTransaction(this); + cleanUpOnTimer(); + // commented out because the application can hold on a ref to the tx + // after it has been removed from the stack + // and want to get the request or branch from it +// originalRequestBytes = null; +// originalRequestBranch = null; + originalRequestFromTag = null; + originalRequestSentBy = null; + // it should be available in the processTxTerminatedEvent, so we can nullify it only here + if(originalRequest != null) { + // originalRequestSentBy = originalRequest.getTopmostVia().getSentBy(); + // originalRequestFromTag = originalRequest.getFromTag(); + originalRequest = null; + } + if(!isReliable() && inviteTransaction != null) { + inviteTransaction = null; + } + // Application Data has to be cleared by the application + // applicationData = null; + lastResponse = null; + // Issue 318 : (https://jain-sip.dev.java.net/issues/show_bug.cgi?id=318) + // Re-transmission of 200 to INVITE terminates prematurely : + // don't nullify since the transaction may be terminated + // but the ack not received so the 200 retransmissions should continue +// lastResponseAsBytes = null; + + // don't clean up because on sending 200 OK to CANCEL otherwise we try to start the transaction timer + // but due to timer J it has already been cleaned up +// transactionTimerStarted = null; + } else { + sipStack.removeTransaction(this); + } + + // Uncache the server tx + if ((!sipStack.cacheServerConnections) && isReliable() + && --getMessageChannel().useCount <= 0) { + // Close the encapsulated socket if stack is configured + close(); + } else { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) + && (!sipStack.cacheServerConnections) + && isReliable()) { + int useCount = getMessageChannel().useCount; + logger.logDebug("Use Count = " + useCount); + } + } + + } + + // clean up the state of the stx when it goes to completed or terminated to help GC + protected void cleanUpOnTimer() { + if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) { + if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { + logger.logDebug("cleanup on timer : " + + getTransactionId()); + } + if(dialog != null && getMethod().equals(Request.CANCEL)) { + // used to deal with getting the dialog on cancel tx after the 200 OK to CANCEL has been sent + dialogId = dialog.getDialogId(); + } + dialog = null; + // we don't nullify the inviteTx for CANCEL since the app can get it from getCanceledInviteTransaction + if(inviteTransaction != null && !getMethod().equals(Request.CANCEL)) { + // we release the semaphore for Cancel processing + inviteTransaction.releaseSem(); + inviteTransaction = null; + } + if(originalRequest != null) { + // http://java.net/jira/browse/JSIP-429 + // store the merge id from the tx to avoid reparsing of request on aggressive cleanup + super.mergeId = ((SIPRequest)originalRequest).getMergeId(); + originalRequest.setTransaction(null); + originalRequest.setInviteTransaction(null); + if(!getMethod().equalsIgnoreCase(Request.INVITE)) { + if(originalRequestSentBy == null) { + originalRequestSentBy = originalRequest.getTopmostVia().getSentBy(); + } + if(originalRequestFromTag == null) { + originalRequestFromTag = originalRequest.getFromTag(); + } + } + // we keep the request in a byte array to be able to recreate it + // no matter what to keep API backward compatibility + if(originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { + originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport()); + } + if(!getMethod().equalsIgnoreCase(Request.INVITE) && !getMethod().equalsIgnoreCase(Request.CANCEL)) { + originalRequest = null; + } + } + if(lastResponse != null) { + if(getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) { + lastResponseAsBytes = lastResponse.encodeAsBytes(this.getTransport()); + } + lastResponse = null; + } + pendingReliableResponseAsBytes = null; + pendingReliableResponseContentType = null; + pendingReliableResponseMethod = null; + pendingSubscribeTransaction = null; + provisionalResponseSem = null; + retransmissionAlertTimerTask = null; + requestOf = null; + } + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableResponseMethod() + */ + @Override + public String getPendingReliableResponseMethod() { + return pendingReliableResponseMethod; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableCSeqNumber() + */ + @Override + public long getPendingReliableCSeqNumber() { + return pendingReliableCSeqNumber; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableRSeqNumber() + */ + @Override + public long getPendingReliableRSeqNumber() { + return pendingReliableRSeqNumber; + } + + /** + * @see gov.nist.javax.sip.stack.SIPServerTransaction#waitForTermination() + */ + @Override + public void waitForTermination() { + + try { + this.terminationSemaphore.acquire(); + } catch (InterruptedException e) { + + } + } + +} diff --git a/src/gov/nist/javax/sip/stack/SIPTransactionStack.java b/src/gov/nist/javax/sip/stack/SIPTransactionStack.java index f22b14832..0100b8973 100755 --- a/src/gov/nist/javax/sip/stack/SIPTransactionStack.java +++ b/src/gov/nist/javax/sip/stack/SIPTransactionStack.java @@ -377,6 +377,8 @@ public abstract class SIPTransactionStack implements protected boolean patchRport = false; + protected boolean patchReceivedRport; + protected ClientAuthType clientAuth = ClientAuthType.Default; // ThreadPool when parsed SIP messages are processed. Affects the case when many TCP calls use single socket. @@ -3099,6 +3101,14 @@ public boolean isPatchRport() { return patchRport; } + public void setPatchReceivedRport(boolean patchReceivedRport) { + this.patchReceivedRport = patchReceivedRport; + } + + public boolean isPatchReceivedRport() { + return patchReceivedRport; + } + /** * @param maxForkTime * the maxForkTime to set