/* * 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.InternalErrorHandler; import gov.nist.core.NameValueList; import gov.nist.javax.sip.SIPConstants; import gov.nist.javax.sip.Utils; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.header.Contact; import gov.nist.javax.sip.header.RecordRoute; import gov.nist.javax.sip.header.RecordRouteList; import gov.nist.javax.sip.header.Route; import gov.nist.javax.sip.header.RouteList; import gov.nist.javax.sip.header.TimeStamp; import gov.nist.javax.sip.header.To; import gov.nist.javax.sip.header.Via; import gov.nist.javax.sip.header.ViaList; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import java.io.IOException; import java.security.cert.X509Certificate; import java.text.ParseException; import java.util.ListIterator; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import javax.net.ssl.SSLPeerUnverifiedException; import javax.sip.Dialog; import javax.sip.DialogState; import javax.sip.InvalidArgumentException; 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.address.SipURI; import javax.sip.header.ExpiresHeader; import javax.sip.header.RouteHeader; import javax.sip.header.TimeStampHeader; import javax.sip.message.Request; /* * Jeff Keyser -- initial. Daniel J. Martinez Manzano --Added support for TLS message channel. * Emil Ivov -- bug fixes. Chris Beardshear -- bug fix. Andreas Bystrom -- bug fixes. Matt Keller * (Motorolla) -- bug fix. */ /** * Represents a client transaction. Implements the following state machines. (From RFC 3261) * *
 *                   
 *                    
 *                     
 *                      
 *                      
 *                      
 *                                                     |INVITE from TU
 *                                   Timer A fires     |INVITE sent
 *                                   Reset A,          V                      Timer B fires
 *                                   INVITE sent +-----------+                or Transport Err.
 *                                     +---------|           |---------------+inform TU
 *                                     |         |  Calling  |               |
 *                                     +-------->|           |-------------->|
 *                                               +-----------+ 2xx           |
 *                                                  |  |       2xx to TU     |
 *                                                  |  |1xx                  |
 *                          300-699 +---------------+  |1xx to TU            |
 *                         ACK sent |                  |                     |
 *                      resp. to TU |  1xx             V                     |
 *                                  |  1xx to TU  -----------+               |
 *                                  |  +---------|           |               |
 *                                  |  |         |Proceeding |-------------->|
 *                                  |  +-------->|           | 2xx           |
 *                                  |            +-----------+ 2xx to TU     |
 *                                  |       300-699    |                     |
 *                                  |       ACK sent,  |                     |
 *                                  |       resp. to TU|                     |
 *                                  |                  |                     |      NOTE:
 *                                  |  300-699         V                     |
 *                                  |  ACK sent  +-----------+Transport Err. |  transitions
 *                                  |  +---------|           |Inform TU      |  labeled with
 *                                  |  |         | Completed |-------------->|  the event
 *                                  |  +-------->|           |               |  over the action
 *                                  |            +-----------+               |  to take
 *                                  |              ˆ   |                     |
 *                                  |              |   | Timer D fires       |
 *                                  +--------------+   | -                   |
 *                                                     |                     |
 *                                                     V                     |
 *                                               +-----------+               |
 *                                               |           |               |
 *                                               | Terminated|<--------------+
 *                                               |           |
 *                                               +-----------+
 *                      
 *                                       Figure 5: INVITE client transaction
 *                      
 *                      
 *                                                         |Request from TU
 *                                                         |send request
 *                                     Timer E             V
 *                                     send request  +-----------+
 *                                         +---------|           |-------------------+
 *                                         |         |  Trying   |  Timer F          |
 *                                         +-------->|           |  or Transport Err.|
 *                                                   +-----------+  inform TU        |
 *                                      200-699         |  |                         |
 *                                      resp. to TU     |  |1xx                      |
 *                                      +---------------+  |resp. to TU              |
 *                                      |                  |                         |
 *                                      |   Timer E        V       Timer F           |
 *                                      |   send req +-----------+ or Transport Err. |
 *                                      |  +---------|           | inform TU         |
 *                                      |  |         |Proceeding |------------------>|
 *                                      |  +-------->|           |-----+             |
 *                                      |            +-----------+     |1xx          |
 *                                      |              |      ˆ        |resp to TU   |
 *                                      | 200-699      |      +--------+             |
 *                                      | resp. to TU  |                             |
 *                                      |              |                             |
 *                                      |              V                             |
 *                                      |            +-----------+                   |
 *                                      |            |           |                   |
 *                                      |            | Completed |                   |
 *                                      |            |           |                   |
 *                                      |            +-----------+                   |
 *                                      |              ˆ   |                         |
 *                                      |              |   | Timer K                 |
 *                                      +--------------+   | -                       |
 *                                                         |                         |
 *                                                         V                         |
 *                                   NOTE:           +-----------+                   |
 *                                                   |           |                   |
 *                               transitions         | Terminated|<------------------+
 *                               labeled with        |           |
 *                               the event           +-----------+
 *                               over the action
 *                               to take
 *                      
 *                                       Figure 6: non-INVITE client transaction
 *                      
 *                      
 *                      
 *                      
 *                     
 *                    
 * 
* * * @author M. Ranganathan * * @version 1.2 $Revision: 1.122 $ $Date: 2009/12/17 23:33:52 $ */ public class SIPClientTransaction extends SIPTransaction implements ServerResponseInterface, javax.sip.ClientTransaction, gov.nist.javax.sip.ClientTransactionExt { // a SIP Client transaction may belong simultaneously to multiple // dialogs in the early state. These dialogs all have // the same call ID and same From tag but different to tags. private ConcurrentHashMap sipDialogs; private SIPRequest lastRequest; private int viaPort; private String viaHost; // Real ResponseInterface to pass messages to private transient ServerResponseInterface respondTo; private SIPDialog defaultDialog; private Hop nextHop; private boolean notifyOnRetransmit; private boolean timeoutIfStillInCallingState; private int callingStateTimeoutCount; public class TransactionTimer extends SIPStackTimerTask { public TransactionTimer() { } protected void runTask() { SIPClientTransaction clientTransaction; SIPTransactionStack sipStack; clientTransaction = SIPClientTransaction.this; sipStack = clientTransaction.sipStack; // If the transaction has terminated, if (clientTransaction.isTerminated()) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "removing = " + clientTransaction + " isReliable " + clientTransaction.isReliable()); } sipStack.removeTransaction(clientTransaction); try { this.cancel(); } catch (IllegalStateException ex) { if (!sipStack.isAlive()) return; } // Client transaction terminated. Kill connection if // this is a TCP after the linger timer has expired. // The linger timer is needed to allow any pending requests to // return responses. if ((!sipStack.cacheClientConnections) && clientTransaction.isReliable()) { int newUseCount = --clientTransaction.getMessageChannel().useCount; if (newUseCount <= 0) { // Let the connection linger for a while and then close // it. TimerTask myTimer = new LingerTimer(); sipStack.getTimer().schedule(myTimer, SIPTransactionStack.CONNECTION_LINGER_TIME * 1000); } } else { // Cache the client connections so dont close the // connection. This keeps the connection open permanently // until the client disconnects. if (sipStack.isLoggingEnabled() && clientTransaction.isReliable()) { int useCount = clientTransaction.getMessageChannel().useCount; if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Client Use Count = " + useCount); } } } else { // If this transaction has not // terminated, // Fire the transaction timer. clientTransaction.fireTimer(); } } } /** * Creates a new client transaction. * * @param newSIPStack Transaction stack this transaction belongs to. * @param newChannelToUse Channel to encapsulate. * @return the created client transaction. */ protected SIPClientTransaction(SIPTransactionStack newSIPStack, MessageChannel newChannelToUse) { super(newSIPStack, newChannelToUse); // Create a random branch parameter for this transaction // setBranch( SIPConstants.BRANCH_MAGIC_COOKIE + // Integer.toHexString( hashCode( ) ) ); setBranch(Utils.getInstance().generateBranchId()); this.messageProcessor = newChannelToUse.messageProcessor; this.setEncapsulatedChannel(newChannelToUse); this.notifyOnRetransmit = false; this.timeoutIfStillInCallingState = false; // This semaphore guards the listener from being // re-entered for this transaction. That is // for a give tx, the listener is called at most // once with an outstanding request. if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("Creating clientTransaction " + this); sipStack.getStackLogger().logStackTrace(); } // this.startTransactionTimer(); this.sipDialogs = new ConcurrentHashMap(); } /** * Sets the real ResponseInterface this transaction encapsulates. * * @param newRespondTo ResponseInterface to send messages to. */ public void setResponseInterface(ServerResponseInterface newRespondTo) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "Setting response interface for " + this + " to " + newRespondTo); if (newRespondTo == null) { sipStack.getStackLogger().logStackTrace(); sipStack.getStackLogger().logDebug("WARNING -- setting to null!"); } } respondTo = newRespondTo; } /** * Returns this transaction. */ public MessageChannel getRequestChannel() { 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 = messageToTest.getViaHeaders(); // Flags whether the select message is part of this transaction boolean transactionMatches; String messageBranch = ((Via) viaHeaders.getFirst()).getBranch(); boolean rfc3261Compliant = getBranch() != null && messageBranch != null && getBranch().toLowerCase().startsWith( SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE) && messageBranch.toLowerCase().startsWith( SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE); transactionMatches = false; if (TransactionState.COMPLETED == this.getState()) { if (rfc3261Compliant) { transactionMatches = getBranch().equalsIgnoreCase( ((Via) viaHeaders.getFirst()).getBranch()) && getMethod().equals(messageToTest.getCSeq().getMethod()); } else { transactionMatches = getBranch().equals(messageToTest.getTransactionId()); } } else if (!isTerminated()) { if (rfc3261Compliant) { if (viaHeaders != null) { // If the branch parameter is the // same as this transaction and the method is the same, if (getBranch().equalsIgnoreCase(((Via) viaHeaders.getFirst()).getBranch())) { transactionMatches = getOriginalRequest().getCSeq().getMethod().equals( messageToTest.getCSeq().getMethod()); } } } else { // not RFC 3261 compliant. if (getBranch() != null) { transactionMatches = getBranch().equalsIgnoreCase( messageToTest.getTransactionId()); } else { transactionMatches = getOriginalRequest().getTransactionId() .equalsIgnoreCase(messageToTest.getTransactionId()); } } } return transactionMatches; } /** * Send a request message through this transaction and onto the client. * * @param messageToSend Request to process and send. */ public void sendMessage(SIPMessage messageToSend) throws IOException { try { // Message typecast as a request SIPRequest transactionRequest; transactionRequest = (SIPRequest) messageToSend; // Set the branch id for the top via header. Via topVia = (Via) transactionRequest.getViaHeaders().getFirst(); // Tack on a branch identifier to match responses. try { topVia.setBranch(getBranch()); } catch (java.text.ParseException ex) { } if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("Sending Message " + messageToSend); sipStack.getStackLogger().logDebug("TransactionState " + this.getState()); } // If this is the first request for this transaction, if (TransactionState.PROCEEDING == getState() || TransactionState.CALLING == getState()) { // If this is a TU-generated ACK request, if (transactionRequest.getMethod().equals(Request.ACK)) { // Send directly to the underlying // transport and close this transaction if (isReliable()) { this.setState(TransactionState.TERMINATED); } else { this.setState(TransactionState.COMPLETED); } // BUGBUG -- This suppresses sending the ACK uncomment this // to // test 4xx retransmission // if (transactionRequest.getMethod() != Request.ACK) super.sendMessage(transactionRequest); return; } } try { // Send the message to the server lastRequest = transactionRequest; if (getState() == null) { // Save this request as the one this transaction // is handling setOriginalRequest(transactionRequest); // Change to trying/calling state // Set state first to avoid race condition.. if (transactionRequest.getMethod().equals(Request.INVITE)) { this.setState(TransactionState.CALLING); } else if (transactionRequest.getMethod().equals(Request.ACK)) { // Acks are never retransmitted. this.setState(TransactionState.TERMINATED); } else { this.setState(TransactionState.TRYING); } if (!isReliable()) { enableRetransmissionTimer(); } if (isInviteTransaction()) { enableTimeoutTimer(TIMER_B); } else { enableTimeoutTimer(TIMER_F); } } // BUGBUG This supresses sending ACKS -- uncomment to test // 4xx retransmission. // if (transactionRequest.getMethod() != Request.ACK) super.sendMessage(transactionRequest); } catch (IOException e) { this.setState(TransactionState.TERMINATED); throw e; } } finally { this.isMapped = true; this.startTransactionTimer(); } } /** * Process a new response message through this transaction. If necessary, this message will * also be passed onto the TU. * * @param transactionResponse Response to process. * @param sourceChannel Channel that received this message. */ public synchronized void processResponse(SIPResponse transactionResponse, MessageChannel sourceChannel, SIPDialog dialog) { // If the state has not yet been assigned then this is a // spurious response. if (getState() == null) return; // Ignore 1xx if ((TransactionState.COMPLETED == this.getState() || TransactionState.TERMINATED == this .getState()) && transactionResponse.getStatusCode() / 100 == 1) { return; } if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "processing " + transactionResponse.getFirstLine() + "current state = " + getState()); sipStack.getStackLogger().logDebug("dialog = " + dialog); } this.lastResponse = transactionResponse; /* * JvB: this is now duplicate with code in the other processResponse * * if (dialog != null && transactionResponse.getStatusCode() != 100 && * (transactionResponse.getTo().getTag() != null || sipStack .isRfc2543Supported())) { // * add the route before you process the response. dialog.setLastResponse(this, * transactionResponse); this.setDialog(dialog, transactionResponse.getDialogId(false)); } */ try { if (isInviteTransaction()) inviteClientTransaction(transactionResponse, sourceChannel, dialog); else nonInviteClientTransaction(transactionResponse, sourceChannel, dialog); } catch (IOException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logException(ex); this.setState(TransactionState.TERMINATED); raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); } } /** * Implements the state machine for invite client transactions. * *
     *                   
     *                    
     *                     
     *                      
     *                      
     *                                                         |Request from TU
     *                                                         |send request
     *                                     Timer E             V
     *                                     send request  +-----------+
     *                                         +---------|           |-------------------+
     *                                         |         |  Trying   |  Timer F          |
     *                                         +-------->|           |  or Transport Err.|
     *                                                   +-----------+  inform TU        |
     *                                      200-699         |  |                         |
     *                                      resp. to TU     |  |1xx                      |
     *                                      +---------------+  |resp. to TU              |
     *                                      |                  |                         |
     *                                      |   Timer E        V       Timer F           |
     *                                      |   send req +-----------+ or Transport Err. |
     *                                      |  +---------|           | inform TU         |
     *                                      |  |         |Proceeding |------------------>|
     *                                      |  +-------->|           |-----+             |
     *                                      |            +-----------+     |1xx          |
     *                                      |              |      ˆ        |resp to TU   |
     *                                      | 200-699      |      +--------+             |
     *                                      | resp. to TU  |                             |
     *                                      |              |                             |
     *                                      |              V                             |
     *                                      |            +-----------+                   |
     *                                      |            |           |                   |
     *                                      |            | Completed |                   |
     *                                      |            |           |                   |
     *                                      |            +-----------+                   |
     *                                      |              ˆ   |                         |
     *                                      |              |   | Timer K                 |
     *                                      +--------------+   | -                       |
     *                                                         |                         |
     *                                                         V                         |
     *                                   NOTE:           +-----------+                   |
     *                                                   |           |                   |
     *                               transitions         | Terminated|<------------------+
     *                               labeled with        |           |
     *                               the event           +-----------+
     *                               over the action
     *                               to take
     *                      
     *                                       Figure 6: non-INVITE client transaction
     *                      
     *                      
     *                     
     *                    
     * 
* * @param transactionResponse -- transaction response received. * @param sourceChannel - source channel on which the response was received. */ private void nonInviteClientTransaction(SIPResponse transactionResponse, MessageChannel sourceChannel, SIPDialog sipDialog) throws IOException { int statusCode = transactionResponse.getStatusCode(); if (TransactionState.TRYING == this.getState()) { if (statusCode / 100 == 1) { this.setState(TransactionState.PROCEEDING); enableRetransmissionTimer(MAXIMUM_RETRANSMISSION_TICK_COUNT); enableTimeoutTimer(TIMER_F); // According to RFC, the TU has to be informed on // this transition. if (respondTo != null) { respondTo.processResponse(transactionResponse, this, sipDialog); } else { this.semRelease(); } } else if (200 <= statusCode && statusCode <= 699) { // Send the response up to the TU. if (respondTo != null) { respondTo.processResponse(transactionResponse, this, sipDialog); } else { this.semRelease(); } if (!isReliable()) { this.setState(TransactionState.COMPLETED); enableTimeoutTimer(TIMER_K); } else { this.setState(TransactionState.TERMINATED); } } } else if (TransactionState.PROCEEDING == this.getState()) { if (statusCode / 100 == 1) { if (respondTo != null) { respondTo.processResponse(transactionResponse, this, sipDialog); } else { this.semRelease(); } } else if (200 <= statusCode && statusCode <= 699) { if (respondTo != null) { respondTo.processResponse(transactionResponse, this, sipDialog); } else { this.semRelease(); } disableRetransmissionTimer(); disableTimeoutTimer(); if (!isReliable()) { this.setState(TransactionState.COMPLETED); enableTimeoutTimer(TIMER_K); } else { this.setState(TransactionState.TERMINATED); } } } else { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( " Not sending response to TU! " + getState()); } this.semRelease(); } } /** * Implements the state machine for invite client transactions. * *
     *                   
     *                    
     *                     
     *                      
     *                      
     *                                                     |INVITE from TU
     *                                   Timer A fires     |INVITE sent
     *                                   Reset A,          V                      Timer B fires
     *                                   INVITE sent +-----------+                or Transport Err.
     *                                     +---------|           |---------------+inform TU
     *                                     |         |  Calling  |               |
     *                                     +-------->|           |-------------->|
     *                                               +-----------+ 2xx           |
     *                                                  |  |       2xx to TU     |
     *                                                  |  |1xx                  |
     *                          300-699 +---------------+  |1xx to TU            |
     *                         ACK sent |                  |                     |
     *                      resp. to TU |  1xx             V                     |
     *                                  |  1xx to TU  -----------+               |
     *                                  |  +---------|           |               |
     *                                  |  |         |Proceeding |-------------->|
     *                                  |  +-------->|           | 2xx           |
     *                                  |            +-----------+ 2xx to TU     |
     *                                  |       300-699    |                     |
     *                                  |       ACK sent,  |                     |
     *                                  |       resp. to TU|                     |
     *                                  |                  |                     |      NOTE:
     *                                  |  300-699         V                     |
     *                                  |  ACK sent  +-----------+Transport Err. |  transitions
     *                                  |  +---------|           |Inform TU      |  labeled with
     *                                  |  |         | Completed |-------------->|  the event
     *                                  |  +-------->|           |               |  over the action
     *                                  |            +-----------+               |  to take
     *                                  |              ˆ   |                     |
     *                                  |              |   | Timer D fires       |
     *                                  +--------------+   | -                   |
     *                                                     |                     |
     *                                                     V                     |
     *                                               +-----------+               |
     *                                               |           |               |
     *                                               | Terminated|<--------------+
     *                                               |           |
     *                                               +-----------+
     *                      
     *                      
     *                     
     *                    
     * 
* * @param transactionResponse -- transaction response received. * @param sourceChannel - source channel on which the response was received. */ private void inviteClientTransaction(SIPResponse transactionResponse, MessageChannel sourceChannel, SIPDialog dialog) throws IOException { int statusCode = transactionResponse.getStatusCode(); if (TransactionState.TERMINATED == this.getState()) { boolean ackAlreadySent = false; if (dialog != null && dialog.isAckSeen() && dialog.getLastAckSent() != null) { if (dialog.getLastAckSent().getCSeq().getSeqNumber() == transactionResponse.getCSeq() .getSeqNumber() && transactionResponse.getFromTag().equals( dialog.getLastAckSent().getFromTag())) { // the last ack sent corresponded to this response ackAlreadySent = true; } } // retransmit the ACK for this response. if (dialog!= null && ackAlreadySent && transactionResponse.getCSeq().getMethod().equals(dialog.getMethod())) { try { // Found the dialog - resend the ACK and // dont pass up the null transaction if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("resending ACK"); dialog.resendAck(); } catch (SipException ex) { // What to do here ?? kill the dialog? } } this.semRelease(); return; } else if (TransactionState.CALLING == this.getState()) { if (statusCode / 100 == 2) { // JvB: do this ~before~ calling the application, to avoid // retransmissions // of the INVITE after app sends ACK disableRetransmissionTimer(); disableTimeoutTimer(); this.setState(TransactionState.TERMINATED); // 200 responses are always seen by TU. if (respondTo != null) respondTo.processResponse(transactionResponse, this, dialog); else { this.semRelease(); } } else if (statusCode / 100 == 1) { disableRetransmissionTimer(); disableTimeoutTimer(); this.setState(TransactionState.PROCEEDING); if (respondTo != null) respondTo.processResponse(transactionResponse, this, dialog); else { this.semRelease(); } } else if (300 <= statusCode && statusCode <= 699) { // Send back an ACK request try { sendMessage((SIPRequest) createErrorAck()); } catch (Exception ex) { sipStack.getStackLogger().logError( "Unexpected Exception sending ACK -- sending error AcK ", ex); } /* * When in either the "Calling" or "Proceeding" states, reception of response with * status code from 300-699 MUST cause the client transaction to transition to * "Completed". The client transaction MUST pass the received response up to the * TU, and the client transaction MUST generate an ACK request. */ if (respondTo != null) { respondTo.processResponse(transactionResponse, this, dialog); } else { this.semRelease(); } if (this.getDialog() != null && ((SIPDialog)this.getDialog()).isBackToBackUserAgent()) { ((SIPDialog) this.getDialog()).releaseAckSem(); } if (!isReliable()) { this.setState(TransactionState.COMPLETED); enableTimeoutTimer(TIMER_D); } else { // Proceed immediately to the TERMINATED state. this.setState(TransactionState.TERMINATED); } } } else if (TransactionState.PROCEEDING == this.getState()) { if (statusCode / 100 == 1) { if (respondTo != null) { respondTo.processResponse(transactionResponse, this, dialog); } else { this.semRelease(); } } else if (statusCode / 100 == 2) { this.setState(TransactionState.TERMINATED); if (respondTo != null) { respondTo.processResponse(transactionResponse, this, dialog); } else { this.semRelease(); } } else if (300 <= statusCode && statusCode <= 699) { // Send back an ACK request try { sendMessage((SIPRequest) createErrorAck()); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } if (this.getDialog() != null) { ((SIPDialog) this.getDialog()).releaseAckSem(); } // JvB: update state before passing to app if (!isReliable()) { this.setState(TransactionState.COMPLETED); this.enableTimeoutTimer(TIMER_D); } else { this.setState(TransactionState.TERMINATED); } // Pass up to the TU for processing. if (respondTo != null) respondTo.processResponse(transactionResponse, this, dialog); else { this.semRelease(); } // JvB: duplicate with line 874 // if (!isReliable()) { // enableTimeoutTimer(TIMER_D); // } } } else if (TransactionState.COMPLETED == this.getState()) { if (300 <= statusCode && statusCode <= 699) { // Send back an ACK request try { sendMessage((SIPRequest) createErrorAck()); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } finally { this.semRelease(); } } } } /* * (non-Javadoc) * * @see javax.sip.ClientTransaction#sendRequest() */ public void sendRequest() throws SipException { SIPRequest sipRequest = this.getOriginalRequest(); if (this.getState() != null) throw new SipException("Request already sent"); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("sendRequest() " + sipRequest); } try { sipRequest.checkHeaders(); } catch (ParseException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("missing required header"); throw new SipException(ex.getMessage()); } if (getMethod().equals(Request.SUBSCRIBE) && sipRequest.getHeader(ExpiresHeader.NAME) == null) { /* * If no "Expires" header is present in a SUBSCRIBE request, the implied default is * defined by the event package being used. * */ if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logWarning( "Expires header missing in outgoing subscribe --" + " Notifier will assume implied value on event package"); } try { /* * This check is removed because it causes problems for load balancers ( See issue * 136) reported by Raghav Ramesh ( BT ) * */ if (this.getOriginalRequest().getMethod().equals(Request.CANCEL) && sipStack.isCancelClientTransactionChecked()) { SIPClientTransaction ct = (SIPClientTransaction) sipStack.findCancelTransaction( this.getOriginalRequest(), false); if (ct == null) { /* * If the original request has generated a final response, the CANCEL SHOULD * NOT be sent, as it is an effective no-op, since CANCEL has no effect on * requests that have already generated a final response. */ throw new SipException("Could not find original tx to cancel. RFC 3261 9.1"); } else if (ct.getState() == null) { throw new SipException( "State is null no provisional response yet -- cannot cancel RFC 3261 9.1"); } else if (!ct.getMethod().equals(Request.INVITE)) { throw new SipException("Cannot cancel non-invite requests RFC 3261 9.1"); } } else if (this.getOriginalRequest().getMethod().equals(Request.BYE) || this.getOriginalRequest().getMethod().equals(Request.NOTIFY)) { SIPDialog dialog = sipStack.getDialog(this.getOriginalRequest() .getDialogId(false)); // I want to behave like a user agent so send the BYE using the // Dialog if (this.getSipProvider().isAutomaticDialogSupportEnabled() && dialog != null) { throw new SipException( "Dialog is present and AutomaticDialogSupport is enabled for " + " the provider -- Send the Request using the Dialog.sendRequest(transaction)"); } } // Only map this after the fist request is sent out. if (this.getMethod().equals(Request.INVITE)) { SIPDialog dialog = this.getDefaultDialog(); if (dialog != null && dialog.isBackToBackUserAgent()) { // Block sending re-INVITE till we see the ACK. if ( ! dialog.takeAckSem() ) { throw new SipException ("Failed to take ACK semaphore"); } } } this.isMapped = true; this.sendMessage(sipRequest); } catch (IOException ex) { this.setState(TransactionState.TERMINATED); throw new SipException("IO Error sending request", ex); } } /** * Called by the transaction stack when a retransmission timer fires. */ protected void fireRetransmissionTimer() { try { // Resend the last request sent if (this.getState() == null || !this.isMapped) return; boolean inv = isInviteTransaction(); TransactionState s = this.getState(); // JvB: INVITE CTs only retransmit in CALLING, non-INVITE in both TRYING and // PROCEEDING // Bug-fix for non-INVITE transactions not retransmitted when 1xx response received if ((inv && TransactionState.CALLING == s) || (!inv && (TransactionState.TRYING == s || TransactionState.PROCEEDING == s))) { // If the retransmission filter is disabled then // retransmission of the INVITE is the application // responsibility. if (lastRequest != null) { if (sipStack.generateTimeStampHeader && lastRequest.getHeader(TimeStampHeader.NAME) != null) { long milisec = System.currentTimeMillis(); TimeStamp timeStamp = new TimeStamp(); try { timeStamp.setTimeStamp(milisec); } catch (InvalidArgumentException ex) { InternalErrorHandler.handleException(ex); } lastRequest.setHeader(timeStamp); } super.sendMessage(lastRequest); if (this.notifyOnRetransmit) { TimeoutEvent txTimeout = new TimeoutEvent(this.getSipProvider(), this, Timeout.RETRANSMIT); this.getSipProvider().handleEvent(txTimeout, this); } if (this.timeoutIfStillInCallingState && this.getState() == TransactionState.CALLING) { this.callingStateTimeoutCount--; if (callingStateTimeoutCount == 0) { TimeoutEvent timeoutEvent = new TimeoutEvent(this.getSipProvider(), this, Timeout.RETRANSMIT); this.getSipProvider().handleEvent(timeoutEvent, this); this.timeoutIfStillInCallingState = false; } } } } } catch (IOException e) { this.raiseIOExceptionEvent(); raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR); } } /** * Called by the transaction stack when a timeout timer fires. */ protected void fireTimeoutTimer() { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("fireTimeoutTimer " + this); SIPDialog dialog = (SIPDialog) this.getDialog(); if (TransactionState.CALLING == this.getState() || TransactionState.TRYING == this.getState() || TransactionState.PROCEEDING == this.getState()) { // Timeout occured. If this is asociated with a transaction // creation then kill the dialog. if (dialog != null && (dialog.getState() == null || dialog.getState() == DialogState.EARLY)) { if (((SIPTransactionStack) getSIPStack()).isDialogCreated(this .getOriginalRequest().getMethod())) { // If this is a re-invite we do not delete the dialog even // if the // reinvite times out. Else // terminate the enclosing dialog. dialog.delete(); } } else if (dialog != null) { // Guard against the case of BYE time out. if (getOriginalRequest().getMethod().equalsIgnoreCase(Request.BYE) && dialog.isTerminatedOnBye()) { // Terminate the associated dialog on BYE Timeout. dialog.delete(); } } } if (TransactionState.COMPLETED != this.getState()) { raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); // Got a timeout error on a cancel. if (this.getOriginalRequest().getMethod().equalsIgnoreCase(Request.CANCEL)) { SIPClientTransaction inviteTx = (SIPClientTransaction) this.getOriginalRequest() .getInviteTransaction(); if (inviteTx != null && ((inviteTx.getState() == TransactionState.CALLING || inviteTx .getState() == TransactionState.PROCEEDING)) && inviteTx.getDialog() != null) { /* * A proxy server should have started TIMER C and take care of the Termination * using transaction.terminate() by itself (i.e. this is not the job of the * stack at this point but we do it to be nice. */ inviteTx.setState(TransactionState.TERMINATED); } } } else { this.setState(TransactionState.TERMINATED); } } /* * (non-Javadoc) * * @see javax.sip.ClientTransaction#createCancel() */ public Request createCancel() throws SipException { SIPRequest originalRequest = this.getOriginalRequest(); if (originalRequest == null) throw new SipException("Bad state " + getState()); if (!originalRequest.getMethod().equals(Request.INVITE)) throw new SipException("Only INIVTE may be cancelled"); if (originalRequest.getMethod().equalsIgnoreCase(Request.ACK)) throw new SipException("Cannot Cancel ACK!"); else { SIPRequest cancelRequest = originalRequest.createCancelRequest(); cancelRequest.setInviteTransaction(this); return cancelRequest; } } /* * (non-Javadoc) * * @see javax.sip.ClientTransaction#createAck() */ public Request createAck() throws SipException { SIPRequest originalRequest = this.getOriginalRequest(); if (originalRequest == null) throw new SipException("bad state " + getState()); if (getMethod().equalsIgnoreCase(Request.ACK)) { throw new SipException("Cannot ACK an ACK!"); } else if (lastResponse == null) { throw new SipException("bad Transaction state"); } else if (lastResponse.getStatusCode() < 200) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("lastResponse = " + lastResponse); } throw new SipException("Cannot ACK a provisional response!"); } SIPRequest ackRequest = originalRequest.createAckRequest((To) lastResponse.getTo()); // Pull the record route headers from the last reesponse. RecordRouteList recordRouteList = lastResponse.getRecordRouteHeaders(); if (recordRouteList == null) { // If the record route list is null then we can // construct the ACK from the specified contact header. // Note the 3xx check here because 3xx is a redirect. // The contact header for the 3xx is the redirected // location so we cannot use that to construct the // request URI. if (lastResponse.getContactHeaders() != null && lastResponse.getStatusCode() / 100 != 3) { Contact contact = (Contact) lastResponse.getContactHeaders().getFirst(); javax.sip.address.URI uri = (javax.sip.address.URI) contact.getAddress().getURI() .clone(); ackRequest.setRequestURI(uri); } return ackRequest; } ackRequest.removeHeader(RouteHeader.NAME); RouteList routeList = new RouteList(); // start at the end of the list and walk backwards ListIterator li = recordRouteList.listIterator(recordRouteList.size()); while (li.hasPrevious()) { RecordRoute rr = (RecordRoute) li.previous(); Route route = new Route(); route.setAddress((AddressImpl) ((AddressImpl) rr.getAddress()).clone()); route.setParameters((NameValueList) rr.getParameters().clone()); routeList.add(route); } Contact contact = null; if (lastResponse.getContactHeaders() != null) { contact = (Contact) lastResponse.getContactHeaders().getFirst(); } if (!((SipURI) ((Route) routeList.getFirst()).getAddress().getURI()).hasLrParam()) { // Contact may not yet be there (bug reported by Andreas B). Route route = null; if (contact != null) { route = new Route(); route.setAddress((AddressImpl) ((AddressImpl) (contact.getAddress())).clone()); } Route firstRoute = (Route) routeList.getFirst(); routeList.removeFirst(); javax.sip.address.URI uri = firstRoute.getAddress().getURI(); ackRequest.setRequestURI(uri); if (route != null) routeList.add(route); ackRequest.addHeader(routeList); } else { if (contact != null) { javax.sip.address.URI uri = (javax.sip.address.URI) contact.getAddress().getURI() .clone(); ackRequest.setRequestURI(uri); ackRequest.addHeader(routeList); } } return ackRequest; } /* * Creates an ACK for an error response, according to RFC3261 section 17.1.1.3 * * Note that this is different from an ACK for 2xx */ private final Request createErrorAck() throws SipException, ParseException { SIPRequest originalRequest = this.getOriginalRequest(); if (originalRequest == null) throw new SipException("bad state " + getState()); if (!getMethod().equals(Request.INVITE)) { throw new SipException("Can only ACK an INVITE!"); } else if (lastResponse == null) { throw new SipException("bad Transaction state"); } else if (lastResponse.getStatusCode() < 200) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("lastResponse = " + lastResponse); } throw new SipException("Cannot ACK a provisional response!"); } return originalRequest.createErrorAck((To) lastResponse.getTo()); } /** * Set the port of the recipient. */ protected void setViaPort(int port) { this.viaPort = port; } /** * Set the port of the recipient. */ protected void setViaHost(String host) { this.viaHost = host; } /** * Get the port of the recipient. */ public int getViaPort() { return this.viaPort; } /** * Get the host of the recipient. */ public String getViaHost() { return this.viaHost; } /** * get the via header for an outgoing request. */ public Via getOutgoingViaHeader() { return this.getMessageProcessor().getViaHeader(); } /** * This is called by the stack after a non-invite client transaction goes to completed state. */ public void clearState() { // reduce the state to minimum // This assumes that the application will not need // to access the request once the transaction is // completed. // TODO -- revisit this - results in a null pointer // occuring occasionally. // this.lastRequest = null; // this.originalRequest = null; // this.lastResponse = null; } /** * 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().cacheClientConnections)) { // Set a time after which the connection // is closed. this.collectionTime = TIMER_J; } if (super.getState() != TransactionState.COMPLETED && (newState == TransactionState.COMPLETED || newState == TransactionState.TERMINATED)) { sipStack.decrementActiveClientTransactionCount(); } super.setState(newState); } /** * Start the timer task. */ protected void startTransactionTimer() { if (this.transactionTimerStarted.compareAndSet(false, true)) { TimerTask myTimer = new TransactionTimer(); if ( sipStack.getTimer() != null ) { sipStack.getTimer().schedule(myTimer, BASE_TIMER_INTERVAL, BASE_TIMER_INTERVAL); } } } /* * Terminate a transaction. This marks the tx as terminated The tx scanner will run and remove * the tx. (non-Javadoc) * * @see javax.sip.Transaction#terminate() */ public void terminate() throws ObjectInUseException { this.setState(TransactionState.TERMINATED); } /** * Check if the From tag of the response matches the from tag of the original message. A * Response with a tag mismatch should be dropped if a Dialog has been created for the * original request. * * @param sipResponse the response to check. * @return true if the check passes. */ public boolean checkFromTag(SIPResponse sipResponse) { String originalFromTag = ((SIPRequest) this.getRequest()).getFromTag(); if (this.defaultDialog != null) { if (originalFromTag == null ^ sipResponse.getFrom().getTag() == null) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("From tag mismatch -- dropping response"); return false; } if (originalFromTag != null && !originalFromTag.equalsIgnoreCase(sipResponse.getFrom().getTag())) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("From tag mismatch -- dropping response"); return false; } } return true; } /* * (non-Javadoc) * * @see gov.nist.javax.sip.stack.ServerResponseInterface#processResponse(gov.nist.javax.sip.message.SIPResponse, * gov.nist.javax.sip.stack.MessageChannel) */ public void processResponse(SIPResponse sipResponse, MessageChannel incomingChannel) { // If a dialog has already been created for this response, // pass it up. SIPDialog dialog = null; String method = sipResponse.getCSeq().getMethod(); String dialogId = sipResponse.getDialogId(false); if (method.equals(Request.CANCEL) && lastRequest != null) { // JvB for CANCEL: use invite CT in CANCEL request to get dialog // (instead of stripping tag) SIPClientTransaction ict = (SIPClientTransaction) lastRequest.getInviteTransaction(); if (ict != null) { dialog = ict.defaultDialog; } } else { dialog = this.getDialog(dialogId); } // JvB: Check all conditions required for creating a new Dialog if (dialog == null) { int code = sipResponse.getStatusCode(); if ((code > 100 && code < 300) /* skip 100 (may have a to tag */ && (sipResponse.getToTag() != null || sipStack.isRfc2543Supported()) && sipStack.isDialogCreated(method)) { /* * Dialog cannot be found for the response. This must be a forked response. no * dialog assigned to this response but a default dialog has been assigned. Note * that if automatic dialog support is configured then a default dialog is always * created. */ synchronized (this) { /* * We need synchronization here because two responses may compete for the * default dialog simultaneously */ if (defaultDialog != null) { if (sipResponse.getFromTag() != null) { SIPResponse dialogResponse = defaultDialog.getLastResponse(); String defaultDialogId = defaultDialog.getDialogId(); if (dialogResponse == null || (method.equals(Request.SUBSCRIBE) && dialogResponse.getCSeq().getMethod().equals( Request.NOTIFY) && defaultDialogId .equals(dialogId))) { // The default dialog has not been claimed yet. defaultDialog.setLastResponse(this, sipResponse); dialog = defaultDialog; } else { /* * check if we have created one previously (happens in the case of * REINVITE processing. JvB: should not happen, this.defaultDialog * should then get set in Dialog#sendRequest line 1662 */ dialog = sipStack.getDialog(dialogId); if (dialog == null) { if (defaultDialog.isAssigned()) { /* * Nop we dont have one. so go ahead and allocate a new * one. */ dialog = sipStack.createDialog(this, sipResponse); } } } if ( dialog != null ) { this.setDialog(dialog, dialog.getDialogId()); } else { sipStack.getStackLogger().logError("dialog is unexpectedly null",new NullPointerException()); } } else { throw new RuntimeException("Response without from-tag"); } } else { // Need to create a new Dialog, this becomes default // JvB: not sure if this ever gets executed if (sipStack.isAutomaticDialogSupportEnabled) { dialog = sipStack.createDialog(this, sipResponse); this.setDialog(dialog, dialog.getDialogId()); } } } // synchronized } else { dialog = defaultDialog; } } else { dialog.setLastResponse(this, sipResponse); } this.processResponse(sipResponse, incomingChannel, dialog); } /* * (non-Javadoc) * * @see gov.nist.javax.sip.stack.SIPTransaction#getDialog() */ public Dialog getDialog() { // This is for backwards compatibility. Dialog retval = null; if (this.lastResponse != null && this.lastResponse.getFromTag() != null && this.lastResponse.getToTag() != null && this.lastResponse.getStatusCode() != 100) { String dialogId = this.lastResponse.getDialogId(false); retval = (Dialog) getDialog(dialogId); } if (retval == null) { retval = (Dialog) this.defaultDialog; } if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( " sipDialogs = " + sipDialogs + " default dialog " + this.defaultDialog + " retval " + retval); } return retval; } /* * (non-Javadoc) * * @see gov.nist.javax.sip.stack.SIPTransaction#setDialog(gov.nist.javax.sip.stack.SIPDialog, * gov.nist.javax.sip.message.SIPMessage) */ public SIPDialog getDialog(String dialogId) { SIPDialog retval = (SIPDialog) this.sipDialogs.get(dialogId); return retval; } /* * (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.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "setDialog: " + dialogId + "sipDialog = " + sipDialog); if (sipDialog == null) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("NULL DIALOG!!"); throw new NullPointerException("bad dialog null"); } if (this.defaultDialog == null) { this.defaultDialog = sipDialog; if ( this.getMethod().equals(Request.INVITE) && this.getSIPStack().maxForkTime != 0) { this.getSIPStack().addForkedClientTransaction(this); } } if (dialogId != null && sipDialog.getDialogId() != null) { this.sipDialogs.put(dialogId, sipDialog); } } public SIPDialog getDefaultDialog() { return this.defaultDialog; } /** * Set the next hop ( if it has already been computed). * * @param hop -- the hop that has been previously computed. */ public void setNextHop(Hop hop) { this.nextHop = hop; } /** * Reeturn the previously computed next hop (avoid computing it twice). * * @return -- next hop previously computed. */ public Hop getNextHop() { return nextHop; } /** * Set this flag if you want your Listener to get Timeout.RETRANSMIT notifications each time a * retransmission occurs. * * @param notifyOnRetransmit the notifyOnRetransmit to set */ public void setNotifyOnRetransmit(boolean notifyOnRetransmit) { this.notifyOnRetransmit = notifyOnRetransmit; } /** * @return the notifyOnRetransmit */ public boolean isNotifyOnRetransmit() { return notifyOnRetransmit; } public void alertIfStillInCallingStateBy(int count) { this.timeoutIfStillInCallingState = true; this.callingStateTimeoutCount = count; } }