| /* |
| * 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) |
| * |
| * <pre> |
| * |
| * |
| * |
| * |
| * |
| * |
| * |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 |
| * |
| * |
| * |
| * |
| * |
| * |
| * </pre> |
| * |
| * |
| * @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<String,SIPDialog> 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. |
| * |
| * <pre> |
| * |
| * |
| * |
| * |
| * |
| * |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 |
| * |
| * |
| * |
| * |
| * </pre> |
| * |
| * @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. |
| * |
| * <pre> |
| * |
| * |
| * |
| * |
| * |
| * |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|<--------------+ |
| * | | |
| * +-----------+ |
| * |
| * |
| * |
| * |
| * </pre> |
| * |
| * @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<RecordRoute> 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; |
| } |
| |
| |
| |
| } |