All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nist.javax.sip.stack.SIPServerTransaction Maven / Gradle / Ivy

/*
 * 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.*;
import gov.nist.javax.sip.message.*;
import gov.nist.javax.sip.header.*;
import gov.nist.javax.sip.ListeningPointImpl;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.ServerTransactionExt;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.Utils;

import javax.sip.address.Hop;
import javax.sip.header.*;
import javax.sip.message.*;
import javax.sip.*;

import java.text.ParseException;
import java.io.IOException;

import java.util.TimerTask;

/*
 * 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.
 * 
 */

/**
 * 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.99 $ $Date: 2008/04/06 23:02:28 $ * @author M. Ranganathan * */ public class SIPServerTransaction extends SIPTransaction implements ServerRequestInterface, javax.sip.ServerTransaction , ServerTransactionExt { // force the listener to see transaction private int rseqNumber; // private LinkedList pendingRequests; // Real RequestInterface to pass messages to private ServerRequestInterface requestOf; private SIPDialog dialog; // the unacknowledged SIPResponse private SIPResponse pendingReliableResponse; // The pending reliable Response Timer private ProvisionalResponseTask provisionalResponseTask; private boolean retransmissionAlertEnabled; private RetransmissionAlertTimerTask retransmissionAlertTimerTask; protected boolean isAckSeen; private SIPClientTransaction pendingSubscribeTransaction; private SIPServerTransaction inviteTransaction; /** * 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 = SIPTransaction.T1; this.ticksLeft = this.ticks; } protected void runTask() { SIPServerTransaction serverTransaction = SIPServerTransaction.this; ticksLeft--; if (ticksLeft == -1) { serverTransaction.fireRetransmissionTimer(); this.ticksLeft = 2 * ticks; } } } class ProvisionalResponseTask extends SIPStackTimerTask { int ticks; int ticksLeft; public ProvisionalResponseTask() { this.ticks = SIPTransaction.T1; this.ticksLeft = this.ticks; } protected void runTask() { SIPServerTransaction serverTransaction = SIPServerTransaction.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. */ // If the transaction has terminated, if (serverTransaction.isTerminated()) { this.cancel(); } else { ticksLeft--; if (ticksLeft == -1) { serverTransaction.fireReliableResponseRetransmissionTimer(); this.ticksLeft = 2 * ticks; } } } } /** * This timer task will terminate the transaction if the listener does not * respond in a pre-determined time period. This helps prevent buggy * listeners (who fail to respond) from causing memory leaks. This allows a * container to protect itself from buggy code ( that fails to respond to a * server transaction). * */ class ListenerExecutionMaxTimer extends SIPStackTimerTask { SIPServerTransaction serverTransaction = SIPServerTransaction.this; ListenerExecutionMaxTimer() { } protected void runTask() { try { if (serverTransaction.getState() == null) { serverTransaction.terminate(); SIPTransactionStack sipStack = serverTransaction .getSIPStack(); sipStack.removePendingTransaction(serverTransaction); sipStack.removeTransaction(serverTransaction); } } catch (Exception ex) { sipStack.getLogWriter().logError("unexpected exception", ex); } } } /** * 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 (sipStack.isLoggingEnabled()) sipStack.logWriter.logDebug("scheduled timer for " + SIPServerTransaction.this); } protected void runTask() { SIPServerTransaction serverTransaction = SIPServerTransaction.this; TransactionState realState = serverTransaction.getRealState(); if (realState == null || TransactionState.TRYING == realState) { if (sipStack.isLoggingEnabled()) sipStack.logWriter .logDebug(" sending Trying current state = " + serverTransaction.getRealState()); try { serverTransaction .sendMessage(serverTransaction.getOriginalRequest() .createResponse(100, "Trying")); if (serverTransaction.sipStack.isLoggingEnabled()) sipStack.logWriter.logDebug(" trying sent " + serverTransaction.getRealState()); } catch (IOException ex) { if (serverTransaction.sipStack.isLoggingEnabled()) sipStack.logWriter.logError("IO error sending TRYING"); } } } } class TransactionTimer extends SIPStackTimerTask { public TransactionTimer() { if (sipStack.logWriter.isLoggingEnabled()) { sipStack.logWriter.logDebug("TransactionTimer() : " + getTransactionId()); } } protected 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 { this.cancel(); } 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 TimerTask myTimer = new LingerTimer(); sipStack.getTimer().schedule(myTimer, SIPTransactionStack.CONNECTION_LINGER_TIME * 1000); } else { // Add to the fire list -- needs to be moved // outside the synchronized block to prevent // deadlock. fireTimer(); } } } /** * Send a response. * * @param transactionResponse -- * the response to send * */ private void sendResponse(SIPResponse transactionResponse) throws IOException { 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); // TODO If that connection attempt fails, the server SHOULD // use SRV 3263 procedures // for servers in order to determine the IP address // and port to open the connection and send the response to. } 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); else throw new IOException( "Could not create a message channel for " + hop); } } finally { this.startTransactionTimer(); } } /** * Creates a new server transaction. * * @param sipStack * Transaction stack this transaction belongs to. * @param newChannelToUse * Channel to encapsulate. */ protected SIPServerTransaction(SIPTransactionStack sipStack, MessageChannel newChannelToUse) { super(sipStack, newChannelToUse); if (sipStack.maxListenerResponseTime != -1) { sipStack.getTimer().schedule(new ListenerExecutionMaxTimer(), sipStack.maxListenerResponseTime * 1000); } this.rseqNumber = (int) (Math.random() * 1000); // Only one outstanding request for a given server tx. if (sipStack.isLoggingEnabled()) { sipStack.logWriter.logDebug("Creating Server Transaction" + this.getBranchId()); sipStack.logWriter.logStackTrace(); } } /** * Sets the real RequestInterface this transaction encapsulates. * * @param newRequestOf * RequestInterface to send messages to. */ public void setRequestInterface(ServerRequestInterface newRequestOf) { requestOf = newRequestOf; } /** * Returns this transaction. */ public MessageChannel getResponseChannel() { return this; } /** * Deterines if the message is a part of this transaction. * * @param messageToTest * Message to check if it is part of this transaction. * * @return True if the message is part of this transaction, false if not. */ public boolean isMessagePartOfTransaction(SIPMessage messageToTest) { // List of Via headers in the message to test ViaList viaHeaders; // Topmost Via header in the list Via topViaHeader; // Branch code in the topmost Via header String messageBranch; // Flags whether the select message is part of this transaction boolean transactionMatches; transactionMatches = false; String method = messageToTest.getCSeq().getMethod(); // 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 ((method.equals(Request.INVITE) || !isTerminated())) { // Get the topmost Via header and its branch parameter viaHeaders = messageToTest.getViaHeaders(); if (viaHeaders != null) { topViaHeader = (Via) viaHeaders.getFirst(); 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( ((Via) getOriginalRequest() .getViaHeaders().getFirst()) .getSentBy()); } else { // Matching server side transaction with only the // branch parameter. transactionMatches = getBranch().equalsIgnoreCase( messageBranch) && topViaHeader.getSentBy().equals( ((Via) getOriginalRequest() .getViaHeaders().getFirst()) .getSentBy()); } } else { // 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 = super.fromTag; String thisFromTag = messageToTest.getFrom().getTag(); boolean skipFrom = (originalFromTag == null || thisFromTag == null); String originalToTag = super.toTag; 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) && !getOriginalRequest().getCSeq().getMethod() .equalsIgnoreCase(Request.CANCEL)) { transactionMatches = false; } else if ((isResponse || getOriginalRequest() .getRequestURI().equals( ((SIPRequest) messageToTest) .getRequestURI())) && (skipFrom || originalFromTag .equalsIgnoreCase(thisFromTag)) && (skipTo || originalToTag .equalsIgnoreCase(thisToTag)) && getOriginalRequest().getCallId().getCallId() .equalsIgnoreCase( messageToTest.getCallId() .getCallId()) && getOriginalRequest().getCSeq().getSeqNumber() == messageToTest .getCSeq().getSeqNumber() && ((!messageToTest.getCSeq().getMethod().equals( Request.CANCEL)) || getOriginalRequest() .getMethod() .equals(messageToTest.getCSeq().getMethod())) && topViaHeader.equals(getOriginalRequest() .getViaHeaders().getFirst())) { transactionMatches = true; } } } } return transactionMatches; } /** * Send out a trying response (only happens when the transaction is mapped). * Otherwise the transaction is not known to the stack. */ protected void map() { // note that TRYING is a pseudo-state for invite transactions TransactionState realState = getRealState(); if (realState == null || realState == TransactionState.TRYING) { // JvB: Removed the condition 'dialog!=null'. Trying should also // be // sent by intermediate proxies. This fixes some TCK tests // null check added as the stack may be stopped. 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); } /** * Return true if the transaction is known to stack. */ public boolean isTransactionMapped() { return this.isMapped; } /** * Process a new request message through this transaction. If necessary, * this message will also be passed onto the TU. * * @param transactionRequest * Request to process. * @param sourceChannel * Channel that received this message. */ 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 (sipStack.logWriter.isLoggingEnabled()) { sipStack.logWriter.logDebug("processRequest: " + transactionRequest.getFirstLine()); sipStack.logWriter.logDebug("tx state = " + this.getRealState()); } try { // If this is the first request for this transaction, if (getRealState() == null) { // 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(TIMER_I); } 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, this); } else { // According to RFC3261 Application should not Ack in // CONFIRMED state if (sipStack.logWriter.isLoggingEnabled()) { sipStack.logWriter .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( getOriginalRequest().getMethod())) { if (TransactionState.PROCEEDING == getRealState() || TransactionState.COMPLETED == getRealState()) { this.semRelease(); // Resend the last response to // the client if (lastResponse != null) { // Send the message to the client super.sendMessage(lastResponse); } } 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, this); else this.semRelease(); } sipStack.logWriter .logDebug("completed processing retransmitted request : " + transactionRequest.getFirstLine() + this + " txState = " + this.getState() + " lastResponse = " + this.getLastResponse()); return; } // Pass message to the TU if (TransactionState.COMPLETED != getRealState() && TransactionState.TERMINATED != getRealState() && requestOf != null) { if (getOriginalRequest().getMethod().equals( transactionRequest.getMethod())) { // Only send original request to TU once! if (toTu) { requestOf.processRequest(transactionRequest, this); } else this.semRelease(); } else { if (requestOf != null) requestOf.processRequest(transactionRequest, this); else this.semRelease(); } } else { // This seems like a common bug so I am allowing it through! if (((SIPTransactionStack) getSIPStack()) .isDialogCreated(getOriginalRequest().getMethod()) && getRealState() == TransactionState.TERMINATED && transactionRequest.getMethod().equals(Request.ACK) && requestOf != null) { SIPDialog thisDialog = (SIPDialog) this.dialog; if (thisDialog == null || !thisDialog.ackProcessed) { // Filter out duplicate acks if (thisDialog != null) { thisDialog.ackReceived(transactionRequest); thisDialog.ackProcessed = true; } requestOf.processRequest(transactionRequest, this); } else { this.semRelease(); } } else if (transactionRequest.getMethod() .equals(Request.CANCEL)) { if (sipStack.isLoggingEnabled()) sipStack.logWriter .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. } } sipStack.logWriter.logDebug("Dropping request " + getRealState()); } } catch (IOException e) { this.semRelease(); this.raiseIOExceptionEvent(); } } /** * Send a response message through this transactionand onto the client. The * response drives the state machine. * * @param messageToSend * Response to process and send. */ public void sendMessage(SIPMessage messageToSend) throws IOException { try { // Message typecast as a response SIPResponse transactionResponse; // Status code of the response being sent to the client int statusCode; // Get the status code from the response transactionResponse = (SIPResponse) messageToSend; statusCode = transactionResponse.getStatusCode(); try { // Provided we have set the banch id for this we set the BID for // the // outgoing via. if (this.getOriginalRequest().getTopmostVia().getBranch() != null) transactionResponse.getTopmostVia().setBranch( this.getBranch()); else transactionResponse.getTopmostVia().removeParameter( ParameterNames.BRANCH); // Make the topmost via headers match identically for the // transaction rsponse. if (!this.getOriginalRequest().getTopmostVia().hasPort()) transactionResponse.getTopmostVia().removePort(); } catch (ParseException ex) { ex.printStackTrace(); } // Method of the response does not match the request used to // create the transaction - transaction state does not change. if (!transactionResponse.getCSeq().getMethod().equals( getOriginalRequest().getMethod())) { sendResponse(transactionResponse); return; } // 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()) { // 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. */ enableTimeoutTimer(TIMER_J); } else { this.setState(TransactionState.TERMINATED); } } 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; this.setState(TransactionState.TERMINATED); if (this.dialog != null) this.dialog.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(); } 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; this.setState(TransactionState.TERMINATED); if (this.dialog != null) this.dialog.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(); } 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); } else { this.setState(TransactionState.TERMINATED); } } // If the transaction has already completed, } else if (TransactionState.COMPLETED == this.getRealState()) { return; } try { // Send the message to the client. // Record the last message sent out. if (sipStack.getLogWriter().isLoggingEnabled()) { sipStack.getLogWriter().logDebug( "sendMessage : tx = " + this + " getState = " + this.getState()); } lastResponse = transactionResponse; this.sendResponse(transactionResponse); } catch (IOException e) { this.setState(TransactionState.TERMINATED); this.collectionTime = 0; throw e; } } finally { this.startTransactionTimer(); } } public String getViaHost() { return getMessageChannel().getViaHost(); } public int getViaPort() { return getMessageChannel().getViaPort(); } /** * Called by the transaction stack when a retransmission timer fires. This * retransmits the last response when the retransmission filter is enabled. */ protected void fireRetransmissionTimer() { try { if (sipStack.getLogWriter().isLoggingEnabled()) { sipStack.getLogWriter().logDebug( "fireRetransmissionTimer() -- "); } // Resend the last response sent by this transaction if (isInviteTransaction() && lastResponse != null) { // null can happen if this is terminating when the timer fires. if (!this.retransmissionAlertEnabled) { // Retransmit last response until ack. if (lastResponse.getStatusCode() / 100 > 2) super.sendMessage(lastResponse); } 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 (sipStack.isLoggingEnabled()) sipStack.logWriter.logException(e); raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); } } private void fireReliableResponseRetransmissionTimer() { try { super.sendMessage(this.pendingReliableResponse); } catch (IOException e) { if (sipStack.isLoggingEnabled()) sipStack.logWriter.logException(e); this.setState(TransactionState.TERMINATED); raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); } } /** * Called by the transaction stack when a timeout timer fires. */ protected void fireTimeoutTimer() { if (sipStack.isLoggingEnabled()) sipStack.logWriter .logDebug("SIPServerTransaction.fireTimeoutTimer this = " + this + " current state = " + this.getRealState() + " method = " + this.getOriginalRequest().getMethod()); SIPDialog dialog = (SIPDialog) this.dialog; if (((SIPTransactionStack) getSIPStack()).isDialogCreated(this .getOriginalRequest().getMethod()) && (TransactionState.CALLING == this.getRealState() || TransactionState.TRYING == this .getRealState())) { dialog.setState(SIPDialog.TERMINATED_STATE); } else if (getOriginalRequest().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); 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); if (dialog != null) dialog.setState(SIPDialog.TERMINATED_STATE); } } /** * Get the last response. */ public SIPResponse getLastResponse() { return this.lastResponse; } /** * Set the original request. */ public void setOriginalRequest(SIPRequest originalRequest) { super.setOriginalRequest(originalRequest); } /* * (non-Javadoc) * * @see javax.sip.ServerTransaction#sendResponse(javax.sip.message.Response) */ public void sendResponse(Response response) throws SipException { SIPResponse sipResponse = (SIPResponse) response; SIPDialog dialog = this.dialog; if (response == null) throw new NullPointerException("null response"); try { sipResponse.checkHeaders(); } catch (ParseException ex) { throw new SipException(ex.getMessage()); } // check for meaningful response. if (!sipResponse.getCSeq().getMethod().equals(this.getMethod())) { throw new SipException( "CSeq method does not match Request method of request that created the tx."); } /* * 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. */ if (this.getMethod().equals(Request.SUBSCRIBE) && response.getStatusCode() / 100 == 2) { if (response.getHeader(ExpiresHeader.NAME) == null) { throw new SipException( "Expires header is mandatory in 2xx response of SUBSCRIBE"); } 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.getExpires() > requestExpires .getExpires()) { throw new SipException( "Response Expires time exceeds request Expires time : See RFC 3265 3.1.1"); } } } // Check for mandatory header. if (sipResponse.getStatusCode() == 200 && sipResponse.getCSeq().getMethod().equals(Request.INVITE) && sipResponse.getHeader(ContactHeader.NAME) == null) throw new SipException( "Contact Header is mandatory for the OK to the INVITE"); 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 { /* * TODO Check this. 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. */ if (this.pendingReliableResponse != null && response.getStatusCode() / 100 == 2 && this.pendingReliableResponse.getContentTypeHeader() .getContentType().equalsIgnoreCase("application") && this.pendingReliableResponse.getContentTypeHeader() .getContentSubType().equalsIgnoreCase("sdp")) { throw new SipException( "cannot send response -- unacked povisional"); } else { // Sending the final response cancels the // pending response task. if (this.pendingReliableResponse != null && sipResponse.isFinalResponse()) { this.provisionalResponseTask.cancel(); this.provisionalResponseTask = null; } } // Dialog checks. These make sure that the response // being sent makes sense. if (dialog != null) { if (sipResponse.getStatusCode() / 100 == 2 && sipStack.isDialogCreated(sipResponse.getCSeq() .getMethod())) { if (dialog.getLocalTag() == null && sipResponse.getTo().getTag() == 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.generateTag()); } else if (dialog.getLocalTag() != null && sipResponse.getToTag() == null) { 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!"); } } // If sending the response within an established dialog, then // set up the tags appropriately. Should probably throw exception // here if tags do not match instead of setting the tags?? // lets be nice and set the tag if the user forgot to do it. if (dialog != null && dialog.getLocalTag() != null && sipResponse.getTo().getTag() == null && sipResponse.getStatusCode() != 100) sipResponse.getTo().setTag(dialog.getLocalTag()); // Backward compatibility slippery slope.... // Only set the from tag in the response when the // incoming request has a from tag. String fromTag = ((SIPRequest) this.getRequest()).getFrom() .getTag(); if (fromTag != null && sipResponse.getFromTag() != null && !sipResponse.getFromTag().equals(fromTag)) { throw new SipException( "From tag of response does not match sipResponse from tag"); } else if (fromTag != null) { sipResponse.getFrom().setTag(fromTag); } else { if (sipStack.isLoggingEnabled()) sipStack.logWriter .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 && response.getStatusCode() != 100) { if (!dialog.checkResponseTags(sipResponse)) throw new SipException( "Response tags dont match with Dialog tags"); 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 && this.getMethod().equals(Request.INVITE) && 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().schedule(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); } catch (IOException ex) { if (sipStack.isLoggingEnabled()) sipStack.logWriter.logException(ex); this.setState(TransactionState.TERMINATED); raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); throw new SipException(ex.getMessage()); } catch (java.text.ParseException ex1) { if (sipStack.isLoggingEnabled()) sipStack.logWriter.logException(ex1); this.setState(TransactionState.TERMINATED); throw new SipException(ex1.getMessage()); } } /** * Return the book-keeping information that we actually use. */ private TransactionState getRealState() { return super.getState(); } /** * Return the current transaction state according to the RFC 3261 * transaction state machine. Invite transactions do not have a trying * state. We just use this as a pseudo state for processing requests. * * @return the state of the transaction. */ public TransactionState getState() { // Trying is a pseudo state for INVITE transactions. if (this.isInviteTransaction() && TransactionState.TRYING == super.getState()) return TransactionState.PROCEEDING; else return super.getState(); } /** * Sets a timeout after which the connection is closed (provided the server * does not use the connection for outgoing requests in this time period) * and calls the superclass to set state. */ public void setState(TransactionState 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; } super.setState(newState); } /** * Start the timer task. */ protected synchronized void startTransactionTimer() { if (this.transactionTimerStarted) return; if (sipStack.getTimer() != null) { // The timer is set to null when the Stack is // shutting down. this.transactionTimerStarted = true; TimerTask myTimer = new TransactionTimer(); sipStack.getTimer().schedule(myTimer, BASE_TIMER_INTERVAL, BASE_TIMER_INTERVAL); } } public boolean equals(Object other) { 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() */ public Dialog getDialog() { return this.dialog; } /* * (non-Javadoc) * * @see gov.nist.javax.sip.stack.SIPTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, * gov.nist.javax.sip.message.SIPMessage) */ public void setDialog(SIPDialog sipDialog, String dialogId) { if (sipStack.logWriter.isLoggingEnabled()) this.sipStack.logWriter.logDebug("setDialog " + this + " dialog = " + sipDialog); this.dialog = sipDialog; if (dialogId != null) this.dialog.setAssigned(); if (this.retransmissionAlertEnabled && this.retransmissionAlertTimerTask != null) { this.retransmissionAlertTimerTask.cancel(); this.retransmissionAlertTimerTask = null; sipStack.retransmissionAlertTransactions .remove(this.retransmissionAlertTimerTask.dialogId); } this.retransmissionAlertEnabled = false; } /* * (non-Javadoc) * * @see javax.sip.Transaction#terminate() */ public void terminate() throws ObjectInUseException { this.setState(TransactionState.TERMINATED); if (this.retransmissionAlertTimerTask != null) { this.retransmissionAlertTimerTask.cancel(); this.retransmissionAlertTimerTask = null; this.sipStack.retransmissionAlertTransactions .remove(retransmissionAlertTimerTask.dialogId); } } protected 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.pendingReliableResponse != null) { throw new SipException("Unacknowledged response"); } else this.pendingReliableResponse = (SIPResponse) relResponse; /* * 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 { this.rseqNumber++; rseq.setSeqNumber(this.rseqNumber); // start the timer task which will retransmit the reliable response // until the PRACK is received this.lastResponse = (SIPResponse) relResponse; this.sendMessage((SIPMessage) relResponse); this.provisionalResponseTask = new ProvisionalResponseTask(); this.sipStack.getTimer().schedule(provisionalResponseTask, 0, SIPTransactionStack.BASE_TIMER_INTERVAL); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } } public SIPResponse getReliableProvisionalResponse() { return this.pendingReliableResponse; } /** * Cancel the retransmit timer for the provisional response task. * * @return true if the tx has seen the prack for the first time and false * otherwise. * */ public boolean prackRecieved() { if (this.pendingReliableResponse == null) return false; this.provisionalResponseTask.cancel(); this.pendingReliableResponse = null; return true; } /* * (non-Javadoc) * * @see javax.sip.ServerTransaction#enableRetransmissionAlerts() */ public void enableRetransmissionAlerts() throws SipException { if (this.getDialog() != null) throw new SipException("Dialog associated with tx"); else if (!this.getMethod().equals(Request.INVITE)) throw new SipException("Request Method must be INVITE"); this.retransmissionAlertEnabled = true; } public boolean isRetransmissionAlertEnabled() { return this.retransmissionAlertEnabled; } /** * Disable retransmission Alerts and cancel associated timers. * */ public void disableRetransmissionAlerts() { if (this.retransmissionAlertTimerTask != null && this.retransmissionAlertEnabled) { this.retransmissionAlertTimerTask.cancel(); this.retransmissionAlertEnabled = false; this.retransmissionAlertTimerTask = null; String dialogId = this.retransmissionAlertTimerTask.dialogId; sipStack.retransmissionAlertTransactions.remove(dialogId); } } /** * This is book-keeping for retransmission filter management. */ public void setAckSeen() { this.isAckSeen = true; } /** * This is book-keeping for retransmission filter management. */ public boolean ackSeen() { return this.isAckSeen; } public void setMapped(boolean b) { this.isMapped = true; } public void setPendingSubscribe( SIPClientTransaction pendingSubscribeClientTx) { this.pendingSubscribeTransaction = pendingSubscribeClientTx; } 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. */ 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. */ public void setInviteTransaction(SIPServerTransaction st) { this.inviteTransaction = st; } /** * TODO -- this method has to be added to the api. * @return */ public SIPServerTransaction getCanceledInviteTransaction() { return this.inviteTransaction; } 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(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy