| /* |
| * 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.javax.sip.SIPConstants; |
| import gov.nist.javax.sip.SipProviderImpl; |
| import gov.nist.javax.sip.header.CallID; |
| import gov.nist.javax.sip.header.Event; |
| import gov.nist.javax.sip.header.From; |
| 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.net.InetAddress; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import javax.net.ssl.SSLPeerUnverifiedException; |
| import javax.sip.Dialog; |
| import javax.sip.IOExceptionEvent; |
| import javax.sip.ServerTransaction; |
| import javax.sip.TransactionState; |
| import javax.sip.message.Request; |
| import javax.sip.message.Response; |
| |
| /* |
| * Modifications for TLS Support added by Daniel J. Martinez Manzano |
| * <dani@dif.um.es> Bug fixes by Jeroen van Bemmel (JvB) and others. |
| */ |
| |
| /** |
| * Abstract class to support both client and server transactions. Provides an |
| * encapsulation of a message channel, handles timer events, and creation of the |
| * Via header for a message. |
| * |
| * @author Jeff Keyser |
| * @author M. Ranganathan |
| * |
| * |
| * @version 1.2 $Revision: 1.71 $ $Date: 2009/11/29 04:31:29 $ |
| */ |
| public abstract class SIPTransaction extends MessageChannel implements |
| javax.sip.Transaction, gov.nist.javax.sip.TransactionExt { |
| |
| protected boolean toListener; // Flag to indicate that the listener gets |
| |
| // to see the event. |
| |
| protected int BASE_TIMER_INTERVAL = SIPTransactionStack.BASE_TIMER_INTERVAL; |
| /** |
| * 5 sec Maximum duration a message will remain in the network |
| */ |
| protected int T4 = 5000 / BASE_TIMER_INTERVAL; |
| |
| /** |
| * The maximum retransmit interval for non-INVITE requests and INVITE |
| * responses |
| */ |
| protected int T2 = 4000 / BASE_TIMER_INTERVAL; |
| protected int TIMER_I = T4; |
| |
| protected int TIMER_K = T4; |
| |
| protected int TIMER_D = 32000 / BASE_TIMER_INTERVAL; |
| |
| // protected static final int TIMER_C = 3 * 60 * 1000 / BASE_TIMER_INTERVAL; |
| |
| /** |
| * One timer tick. |
| */ |
| protected static final int T1 = 1; |
| |
| /** |
| * INVITE request retransmit interval, for UDP only |
| */ |
| protected static final int TIMER_A = 1; |
| |
| /** |
| * INVITE transaction timeout timer |
| */ |
| protected static final int TIMER_B = 64; |
| |
| protected static final int TIMER_J = 64; |
| |
| protected static final int TIMER_F = 64; |
| |
| protected static final int TIMER_H = 64; |
| |
| // Proposed feature for next release. |
| protected transient Object applicationData; |
| |
| protected SIPResponse lastResponse; |
| |
| // private SIPDialog dialog; |
| |
| protected boolean isMapped; |
| |
| private Semaphore semaphore; |
| |
| protected boolean isSemaphoreAquired; |
| |
| // protected boolean eventPending; // indicate that an event is pending |
| // here. |
| |
| protected String transactionId; // Transaction Id. |
| |
| // Audit tag used by the SIP Stack audit |
| public long auditTag = 0; |
| |
| /** |
| * Initialized but no state assigned. |
| */ |
| public static final TransactionState INITIAL_STATE = null; |
| |
| /** |
| * Trying state. |
| */ |
| public static final TransactionState TRYING_STATE = TransactionState.TRYING; |
| |
| /** |
| * CALLING State. |
| */ |
| public static final TransactionState CALLING_STATE = TransactionState.CALLING; |
| |
| /** |
| * Proceeding state. |
| */ |
| public static final TransactionState PROCEEDING_STATE = TransactionState.PROCEEDING; |
| |
| /** |
| * Completed state. |
| */ |
| public static final TransactionState COMPLETED_STATE = TransactionState.COMPLETED; |
| |
| /** |
| * Confirmed state. |
| */ |
| public static final TransactionState CONFIRMED_STATE = TransactionState.CONFIRMED; |
| |
| /** |
| * Terminated state. |
| */ |
| public static final TransactionState TERMINATED_STATE = TransactionState.TERMINATED; |
| |
| /** |
| * Maximum number of ticks between retransmissions. |
| */ |
| protected static final int MAXIMUM_RETRANSMISSION_TICK_COUNT = 8; |
| |
| // Parent stack for this transaction |
| protected transient SIPTransactionStack sipStack; |
| |
| // Original request that is being handled by this transaction |
| protected SIPRequest originalRequest; |
| |
| // Underlying channel being used to send messages for this transaction |
| private transient MessageChannel encapsulatedChannel; |
| |
| // Port of peer |
| protected int peerPort; |
| |
| // Address of peer |
| protected InetAddress peerInetAddress; |
| |
| // Address of peer as a string |
| protected String peerAddress; |
| |
| // Protocol of peer |
| protected String peerProtocol; |
| |
| // @@@ hagai - NAT changes |
| // Source port extracted from peer packet |
| protected int peerPacketSourcePort; |
| |
| protected InetAddress peerPacketSourceAddress; |
| |
| protected AtomicBoolean transactionTimerStarted = new AtomicBoolean(false); |
| |
| // Transaction branch ID |
| private String branch; |
| |
| // Method of the Request used to create the transaction. |
| private String method; |
| |
| // Sequence number of request used to create the transaction |
| private long cSeq; |
| |
| // Current transaction state |
| private TransactionState currentState; |
| |
| // Number of ticks the retransmission timer was set to last |
| private transient int retransmissionTimerLastTickCount; |
| |
| // Number of ticks before the message is retransmitted |
| private transient int retransmissionTimerTicksLeft; |
| |
| // Number of ticks before the transaction times out |
| protected int timeoutTimerTicksLeft; |
| |
| // List of event listeners for this transaction |
| private transient Set<SIPTransactionEventListener> eventListeners; |
| |
| // Hang on to these - we clear out the request URI after |
| // transaction goes to final state. Pointers to these are kept around |
| // for transaction matching as long as the transaction is in |
| // the transaction table. |
| protected From from; |
| |
| protected To to; |
| |
| protected Event event; |
| |
| protected CallID callId; |
| |
| // Back ptr to the JAIN layer. |
| // private Object wrapper; |
| |
| // Counter for caching of connections. |
| // Connection lingers for collectionTime |
| // after the Transaction goes to terminated state. |
| protected int collectionTime; |
| |
| protected String toTag; |
| |
| protected String fromTag; |
| |
| private boolean terminatedEventDelivered; |
| |
| public String getBranchId() { |
| return this.branch; |
| } |
| |
| /** |
| * The linger timer is used to remove the transaction from the transaction |
| * table after it goes into terminated state. This allows connection caching |
| * and also takes care of race conditins. |
| * |
| * |
| */ |
| class LingerTimer extends SIPStackTimerTask { |
| |
| public LingerTimer() { |
| SIPTransaction sipTransaction = SIPTransaction.this; |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("LingerTimer : " |
| + sipTransaction.getTransactionId()); |
| } |
| |
| } |
| |
| protected void runTask() { |
| SIPTransaction transaction = SIPTransaction.this; |
| // release the connection associated with this transaction. |
| SIPTransactionStack sipStack = transaction.getSIPStack(); |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("LingerTimer: run() : " |
| + getTransactionId()); |
| } |
| |
| if (transaction instanceof SIPClientTransaction) { |
| sipStack.removeTransaction(transaction); |
| transaction.close(); |
| |
| } else if (transaction instanceof ServerTransaction) { |
| // Remove it from the set |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("removing" + transaction); |
| sipStack.removeTransaction(transaction); |
| if ((!sipStack.cacheServerConnections) |
| && --transaction.encapsulatedChannel.useCount <= 0) { |
| // Close the encapsulated socket if stack is configured |
| transaction.close(); |
| } else { |
| if (sipStack.isLoggingEnabled() |
| && (!sipStack.cacheServerConnections) |
| && transaction.isReliable()) { |
| int useCount = transaction.encapsulatedChannel.useCount; |
| sipStack.getStackLogger().logDebug("Use Count = " + useCount); |
| } |
| } |
| } |
| |
| } |
| } |
| |
| /** |
| * Transaction constructor. |
| * |
| * @param newParentStack |
| * Parent stack for this transaction. |
| * @param newEncapsulatedChannel |
| * Underlying channel for this transaction. |
| */ |
| protected SIPTransaction(SIPTransactionStack newParentStack, |
| MessageChannel newEncapsulatedChannel) { |
| |
| sipStack = newParentStack; |
| this.semaphore = new Semaphore(1,true); |
| |
| encapsulatedChannel = newEncapsulatedChannel; |
| // Record this to check if the address has changed before sending |
| // message to avoid possible race condition. |
| this.peerPort = newEncapsulatedChannel.getPeerPort(); |
| this.peerAddress = newEncapsulatedChannel.getPeerAddress(); |
| this.peerInetAddress = newEncapsulatedChannel.getPeerInetAddress(); |
| // @@@ hagai |
| this.peerPacketSourcePort = newEncapsulatedChannel |
| .getPeerPacketSourcePort(); |
| this.peerPacketSourceAddress = newEncapsulatedChannel |
| .getPeerPacketSourceAddress(); |
| this.peerProtocol = newEncapsulatedChannel.getPeerProtocol(); |
| if (this.isReliable()) { |
| encapsulatedChannel.useCount++; |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger() |
| .logDebug("use count for encapsulated channel" |
| + this |
| + " " |
| + encapsulatedChannel.useCount ); |
| } |
| |
| this.currentState = null; |
| |
| disableRetransmissionTimer(); |
| disableTimeoutTimer(); |
| eventListeners = Collections.synchronizedSet(new HashSet<SIPTransactionEventListener>()); |
| |
| // Always add the parent stack as a listener |
| // of this transaction |
| addEventListener(newParentStack); |
| |
| } |
| |
| /** |
| * Sets the request message that this transaction handles. |
| * |
| * @param newOriginalRequest |
| * Request being handled. |
| */ |
| public void setOriginalRequest(SIPRequest newOriginalRequest) { |
| |
| // Branch value of topmost Via header |
| String newBranch; |
| |
| if (this.originalRequest != null |
| && (!this.originalRequest.getTransactionId().equals( |
| newOriginalRequest.getTransactionId()))) { |
| sipStack.removeTransactionHash(this); |
| } |
| // This will be cleared later. |
| |
| this.originalRequest = newOriginalRequest; |
| |
| // just cache the control information so the |
| // original request can be released later. |
| this.method = newOriginalRequest.getMethod(); |
| this.from = (From) newOriginalRequest.getFrom(); |
| this.to = (To) newOriginalRequest.getTo(); |
| // Save these to avoid concurrent modification exceptions! |
| this.toTag = this.to.getTag(); |
| this.fromTag = this.from.getTag(); |
| this.callId = (CallID) newOriginalRequest.getCallId(); |
| this.cSeq = newOriginalRequest.getCSeq().getSeqNumber(); |
| this.event = (Event) newOriginalRequest.getHeader("Event"); |
| this.transactionId = newOriginalRequest.getTransactionId(); |
| |
| originalRequest.setTransaction(this); |
| |
| // If the message has an explicit branch value set, |
| newBranch = ((Via) newOriginalRequest.getViaHeaders().getFirst()) |
| .getBranch(); |
| if (newBranch != null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Setting Branch id : " + newBranch); |
| |
| // Override the default branch with the one |
| // set by the message |
| setBranch(newBranch); |
| |
| } else { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Branch id is null - compute TID!" |
| + newOriginalRequest.encode()); |
| setBranch(newOriginalRequest.getTransactionId()); |
| } |
| } |
| |
| /** |
| * Gets the request being handled by this transaction. |
| * |
| * @return -- the original Request associated with this transaction. |
| */ |
| public SIPRequest getOriginalRequest() { |
| return originalRequest; |
| } |
| |
| /** |
| * Get the original request but cast to a Request structure. |
| * |
| * @return the request that generated this transaction. |
| */ |
| public Request getRequest() { |
| return (Request) originalRequest; |
| } |
| |
| /** |
| * Returns a flag stating whether this transaction is for an INVITE request |
| * or not. |
| * |
| * @return -- true if this is an INVITE request, false if not. |
| */ |
| public final boolean isInviteTransaction() { |
| return getMethod().equals(Request.INVITE); |
| } |
| |
| /** |
| * Return true if the transaction corresponds to a CANCEL message. |
| * |
| * @return -- true if the transaciton is a CANCEL transaction. |
| */ |
| public final boolean isCancelTransaction() { |
| return getMethod().equals(Request.CANCEL); |
| } |
| |
| /** |
| * Return a flag that states if this is a BYE transaction. |
| * |
| * @return true if the transaciton is a BYE transaction. |
| */ |
| public final boolean isByeTransaction() { |
| return getMethod().equals(Request.BYE); |
| } |
| |
| /** |
| * Returns the message channel used for transmitting/receiving messages for |
| * this transaction. Made public in support of JAIN dual transaction model. |
| * |
| * @return Encapsulated MessageChannel. |
| * |
| */ |
| public MessageChannel getMessageChannel() { |
| return encapsulatedChannel; |
| } |
| |
| /** |
| * Sets the Via header branch parameter used to identify this transaction. |
| * |
| * @param newBranch |
| * New string used as the branch for this transaction. |
| */ |
| public final void setBranch(String newBranch) { |
| branch = newBranch; |
| } |
| |
| /** |
| * Gets the current setting for the branch parameter of this transaction. |
| * |
| * @return Branch parameter for this transaction. |
| */ |
| public final String getBranch() { |
| if (this.branch == null) { |
| this.branch = getOriginalRequest().getTopmostVia().getBranch(); |
| } |
| return branch; |
| } |
| |
| /** |
| * Get the method of the request used to create this transaction. |
| * |
| * @return the method of the request for the transaction. |
| */ |
| public final String getMethod() { |
| return this.method; |
| } |
| |
| /** |
| * Get the Sequence number of the request used to create the transaction. |
| * |
| * @return the cseq of the request used to create the transaction. |
| */ |
| public final long getCSeq() { |
| return this.cSeq; |
| } |
| |
| /** |
| * Changes the state of this transaction. |
| * |
| * @param newState |
| * New state of this transaction. |
| */ |
| public void setState(TransactionState newState) { |
| // PATCH submitted by sribeyron |
| if (currentState == TransactionState.COMPLETED) { |
| if (newState != TransactionState.TERMINATED |
| && newState != TransactionState.CONFIRMED) |
| newState = TransactionState.COMPLETED; |
| } |
| if (currentState == TransactionState.CONFIRMED) { |
| if (newState != TransactionState.TERMINATED) |
| newState = TransactionState.CONFIRMED; |
| } |
| if (currentState != TransactionState.TERMINATED) |
| currentState = newState; |
| else |
| newState = currentState; |
| // END OF PATCH |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("Transaction:setState " + newState |
| + " " + this + " branchID = " + this.getBranch() |
| + " isClient = " + (this instanceof SIPClientTransaction)); |
| sipStack.getStackLogger().logStackTrace(); |
| } |
| } |
| |
| /** |
| * Gets the current state of this transaction. |
| * |
| * @return Current state of this transaction. |
| */ |
| public TransactionState getState() { |
| return this.currentState; |
| } |
| |
| /** |
| * Enables retransmission timer events for this transaction to begin in one |
| * tick. |
| */ |
| protected final void enableRetransmissionTimer() { |
| enableRetransmissionTimer(1); |
| } |
| |
| /** |
| * Enables retransmission timer events for this transaction to begin after |
| * the number of ticks passed to this routine. |
| * |
| * @param tickCount |
| * Number of ticks before the next retransmission timer event |
| * occurs. |
| */ |
| protected final void enableRetransmissionTimer(int tickCount) { |
| // For INVITE Client transactions, double interval each time |
| if (isInviteTransaction() && (this instanceof SIPClientTransaction)) { |
| retransmissionTimerTicksLeft = tickCount; |
| } else { |
| // non-INVITE transactions and 3xx-6xx responses are capped at T2 |
| retransmissionTimerTicksLeft = Math.min(tickCount, |
| MAXIMUM_RETRANSMISSION_TICK_COUNT); |
| } |
| retransmissionTimerLastTickCount = retransmissionTimerTicksLeft; |
| } |
| |
| /** |
| * Turns off retransmission events for this transaction. |
| */ |
| protected final void disableRetransmissionTimer() { |
| retransmissionTimerTicksLeft = -1; |
| } |
| |
| /** |
| * Enables a timeout event to occur for this transaction after the number of |
| * ticks passed to this method. |
| * |
| * @param tickCount |
| * Number of ticks before this transaction times out. |
| */ |
| protected final void enableTimeoutTimer(int tickCount) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("enableTimeoutTimer " + this |
| + " tickCount " + tickCount + " currentTickCount = " |
| + timeoutTimerTicksLeft); |
| |
| timeoutTimerTicksLeft = tickCount; |
| } |
| |
| /** |
| * Disabled the timeout timer. |
| */ |
| protected final void disableTimeoutTimer() { |
| timeoutTimerTicksLeft = -1; |
| } |
| |
| /** |
| * Fired after each timer tick. Checks the retransmission and timeout timers |
| * of this transaction, and fired these events if necessary. |
| */ |
| final void fireTimer() { |
| // If the timeout timer is enabled, |
| |
| if (timeoutTimerTicksLeft != -1) { |
| // Count down the timer, and if it has run out, |
| if (--timeoutTimerTicksLeft == 0) { |
| // Fire the timeout timer |
| fireTimeoutTimer(); |
| } |
| } |
| |
| // If the retransmission timer is enabled, |
| if (retransmissionTimerTicksLeft != -1) { |
| // Count down the timer, and if it has run out, |
| if (--retransmissionTimerTicksLeft == 0) { |
| // Enable this timer to fire again after |
| // twice the original time |
| enableRetransmissionTimer(retransmissionTimerLastTickCount * 2); |
| // Fire the timeout timer |
| fireRetransmissionTimer(); |
| } |
| } |
| } |
| |
| /** |
| * Tests if this transaction has terminated. |
| * |
| * @return Trus if this transaction is terminated, false if not. |
| */ |
| public final boolean isTerminated() { |
| return getState() == TERMINATED_STATE; |
| } |
| |
| public String getHost() { |
| return encapsulatedChannel.getHost(); |
| } |
| |
| public String getKey() { |
| return encapsulatedChannel.getKey(); |
| } |
| |
| public int getPort() { |
| return encapsulatedChannel.getPort(); |
| } |
| |
| public SIPTransactionStack getSIPStack() { |
| return (SIPTransactionStack) sipStack; |
| } |
| |
| public String getPeerAddress() { |
| return this.peerAddress; |
| } |
| |
| public int getPeerPort() { |
| return this.peerPort; |
| } |
| |
| // @@@ hagai |
| public int getPeerPacketSourcePort() { |
| return this.peerPacketSourcePort; |
| } |
| |
| public InetAddress getPeerPacketSourceAddress() { |
| return this.peerPacketSourceAddress; |
| } |
| |
| protected InetAddress getPeerInetAddress() { |
| return this.peerInetAddress; |
| } |
| |
| protected String getPeerProtocol() { |
| return this.peerProtocol; |
| } |
| |
| public String getTransport() { |
| return encapsulatedChannel.getTransport(); |
| } |
| |
| public boolean isReliable() { |
| return encapsulatedChannel.isReliable(); |
| } |
| |
| /** |
| * Returns the Via header for this channel. Gets the Via header of the |
| * underlying message channel, and adds a branch parameter to it for this |
| * transaction. |
| */ |
| public Via getViaHeader() { |
| // Via header of the encapulated channel |
| Via channelViaHeader; |
| |
| // Add the branch parameter to the underlying |
| // channel's Via header |
| channelViaHeader = super.getViaHeader(); |
| try { |
| channelViaHeader.setBranch(branch); |
| } catch (java.text.ParseException ex) { |
| } |
| return channelViaHeader; |
| |
| } |
| |
| /** |
| * Process the message through the transaction and sends it to the SIP peer. |
| * |
| * @param messageToSend |
| * Message to send to the SIP peer. |
| */ |
| public void sendMessage(SIPMessage messageToSend) throws IOException { |
| // Use the peer address, port and transport |
| // that was specified when the transaction was |
| // created. Bug was noted by Bruce Evangelder |
| // soleo communications. |
| try { |
| encapsulatedChannel.sendMessage(messageToSend, |
| this.peerInetAddress, this.peerPort); |
| } finally { |
| this.startTransactionTimer(); |
| } |
| } |
| |
| /** |
| * Parse the byte array as a message, process it through the transaction, |
| * and send it to the SIP peer. This is just a placeholder method -- calling |
| * it will result in an IO exception. |
| * |
| * @param messageBytes |
| * Bytes of the message to send. |
| * @param receiverAddress |
| * Address of the target peer. |
| * @param receiverPort |
| * Network port of the target peer. |
| * |
| * @throws IOException |
| * If called. |
| */ |
| protected void sendMessage(byte[] messageBytes, |
| InetAddress receiverAddress, int receiverPort, boolean retry) |
| throws IOException { |
| throw new IOException( |
| "Cannot send unparsed message through Transaction Channel!"); |
| } |
| |
| /** |
| * Adds a new event listener to this transaction. |
| * |
| * @param newListener |
| * Listener to add. |
| */ |
| public void addEventListener(SIPTransactionEventListener newListener) { |
| eventListeners.add(newListener); |
| } |
| |
| /** |
| * Removed an event listener from this transaction. |
| * |
| * @param oldListener |
| * Listener to remove. |
| */ |
| public void removeEventListener(SIPTransactionEventListener oldListener) { |
| eventListeners.remove(oldListener); |
| } |
| |
| /** |
| * Creates a SIPTransactionErrorEvent and sends it to all of the listeners |
| * of this transaction. This method also flags the transaction as |
| * terminated. |
| * |
| * @param errorEventID |
| * ID of the error to raise. |
| */ |
| protected void raiseErrorEvent(int errorEventID) { |
| |
| // Error event to send to all listeners |
| SIPTransactionErrorEvent newErrorEvent; |
| // Iterator through the list of listeners |
| Iterator<SIPTransactionEventListener> listenerIterator; |
| // Next listener in the list |
| SIPTransactionEventListener nextListener; |
| |
| // Create the error event |
| newErrorEvent = new SIPTransactionErrorEvent(this, errorEventID); |
| |
| // Loop through all listeners of this transaction |
| synchronized (eventListeners) { |
| listenerIterator = eventListeners.iterator(); |
| while (listenerIterator.hasNext()) { |
| // Send the event to the next listener |
| nextListener = (SIPTransactionEventListener) listenerIterator |
| .next(); |
| nextListener.transactionErrorEvent(newErrorEvent); |
| } |
| } |
| // Clear the event listeners after propagating the error. |
| // Retransmit notifications are just an alert to the |
| // application (they are not an error). |
| if (errorEventID != SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) { |
| eventListeners.clear(); |
| |
| // Errors always terminate a transaction |
| this.setState(TransactionState.TERMINATED); |
| |
| if (this instanceof SIPServerTransaction && this.isByeTransaction() |
| && this.getDialog() != null) |
| ((SIPDialog) this.getDialog()) |
| .setState(SIPDialog.TERMINATED_STATE); |
| } |
| } |
| |
| /** |
| * A shortcut way of telling if we are a server transaction. |
| */ |
| protected boolean isServerTransaction() { |
| return this instanceof SIPServerTransaction; |
| } |
| |
| /** |
| * Gets the dialog object of this Transaction object. This object returns |
| * null if no dialog exists. A dialog only exists for a transaction when a |
| * session is setup between a User Agent Client and a User Agent Server, |
| * either by a 1xx Provisional Response for an early dialog or a 200OK |
| * Response for a committed dialog. |
| * |
| * @return the Dialog Object of this Transaction object. |
| * @see Dialog |
| */ |
| public abstract Dialog getDialog(); |
| |
| /** |
| * set the dialog object. |
| * |
| * @param sipDialog -- |
| * the dialog to set. |
| * @param dialogId -- |
| * the dialog id ot associate with the dialog.s |
| */ |
| public abstract void setDialog(SIPDialog sipDialog, String dialogId); |
| |
| /** |
| * Returns the current value of the retransmit timer in milliseconds used to |
| * retransmit messages over unreliable transports. |
| * |
| * @return the integer value of the retransmit timer in milliseconds. |
| */ |
| public int getRetransmitTimer() { |
| return SIPTransactionStack.BASE_TIMER_INTERVAL; |
| } |
| |
| /** |
| * Get the host to assign for an outgoing Request via header. |
| */ |
| public String getViaHost() { |
| return this.getViaHeader().getHost(); |
| |
| } |
| |
| /** |
| * Get the last response. This is used internally by the implementation. |
| * Dont rely on it. |
| * |
| * @return the last response received (for client transactions) or sent (for |
| * server transactions). |
| */ |
| public SIPResponse getLastResponse() { |
| return this.lastResponse; |
| } |
| |
| /** |
| * Get the JAIN interface response |
| */ |
| public Response getResponse() { |
| return (Response) this.lastResponse; |
| } |
| |
| /** |
| * Get the transaction Id. |
| */ |
| public String getTransactionId() { |
| return this.transactionId; |
| } |
| |
| /** |
| * Hashcode method for fast hashtable lookup. |
| */ |
| public int hashCode() { |
| if (this.transactionId == null) |
| return -1; |
| else |
| return this.transactionId.hashCode(); |
| } |
| |
| /** |
| * Get the port to assign for the via header of an outgoing message. |
| */ |
| public int getViaPort() { |
| return this.getViaHeader().getPort(); |
| } |
| |
| /** |
| * A method that can be used to test if an incoming request belongs to this |
| * transction. This does not take the transaction state into account when |
| * doing the check otherwise it is identical to isMessagePartOfTransaction. |
| * This is useful for checking if a CANCEL belongs to this transaction. |
| * |
| * @param requestToTest |
| * is the request to test. |
| * @return true if the the request belongs to the transaction. |
| * |
| */ |
| public boolean doesCancelMatchTransaction(SIPRequest requestToTest) { |
| |
| // List of Via headers in the message to test |
| ViaList viaHeaders; |
| // Topmost Via header in the list |
| Via topViaHeader; |
| // Branch code in the topmost Via header |
| String messageBranch; |
| // Flags whether the select message is part of this transaction |
| boolean transactionMatches; |
| |
| transactionMatches = false; |
| |
| if (this.getOriginalRequest() == null |
| || this.getOriginalRequest().getMethod().equals(Request.CANCEL)) |
| return false; |
| // Get the topmost Via header and its branch parameter |
| viaHeaders = requestToTest.getViaHeaders(); |
| if (viaHeaders != null) { |
| |
| topViaHeader = (Via) viaHeaders.getFirst(); |
| messageBranch = topViaHeader.getBranch(); |
| if (messageBranch != null) { |
| |
| // If the branch parameter exists but |
| // does not start with the magic cookie, |
| if (!messageBranch.toLowerCase().startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) { |
| |
| // Flags this as old |
| // (RFC2543-compatible) client |
| // version |
| messageBranch = null; |
| |
| } |
| |
| } |
| |
| // If a new branch parameter exists, |
| if (messageBranch != null && this.getBranch() != null) { |
| |
| // If the branch equals the branch in |
| // this message, |
| if (getBranch().equalsIgnoreCase(messageBranch) |
| && topViaHeader.getSentBy().equals( |
| ((Via) getOriginalRequest().getViaHeaders() |
| .getFirst()).getSentBy())) { |
| transactionMatches = true; |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("returning true"); |
| } |
| |
| } else { |
| // If this is an RFC2543-compliant message, |
| // If RequestURI, To tag, From tag, |
| // CallID, CSeq number, and top Via |
| // headers are the same, |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("testing against " |
| + getOriginalRequest()); |
| |
| if (getOriginalRequest().getRequestURI().equals( |
| requestToTest.getRequestURI()) |
| && getOriginalRequest().getTo().equals( |
| requestToTest.getTo()) |
| && getOriginalRequest().getFrom().equals( |
| requestToTest.getFrom()) |
| && getOriginalRequest().getCallId().getCallId().equals( |
| requestToTest.getCallId().getCallId()) |
| && getOriginalRequest().getCSeq().getSeqNumber() == requestToTest |
| .getCSeq().getSeqNumber() |
| && topViaHeader.equals(getOriginalRequest() |
| .getViaHeaders().getFirst())) { |
| |
| transactionMatches = true; |
| } |
| |
| } |
| |
| } |
| |
| // JvB: Need to pass the CANCEL to the listener! Retransmitted INVITEs |
| // set it to false |
| if (transactionMatches) { |
| this.setPassToListener(); |
| } |
| return transactionMatches; |
| } |
| |
| /** |
| * Sets the value of the retransmit timer to the newly supplied timer value. |
| * The retransmit timer is expressed in milliseconds and its default value |
| * is 500ms. This method allows the application to change the transaction |
| * retransmit behavior for different networks. Take the gateway proxy as an |
| * example. The internal intranet is likely to be reatively uncongested and |
| * the endpoints will be relatively close. The external network is the |
| * general Internet. This functionality allows different retransmit times |
| * for either side. |
| * |
| * @param retransmitTimer - |
| * the new integer value of the retransmit timer in milliseconds. |
| */ |
| public void setRetransmitTimer(int retransmitTimer) { |
| |
| if (retransmitTimer <= 0) |
| throw new IllegalArgumentException( |
| "Retransmit timer must be positive!"); |
| if (this.transactionTimerStarted.get()) |
| throw new IllegalStateException( |
| "Transaction timer is already started"); |
| BASE_TIMER_INTERVAL = retransmitTimer; |
| T4 = 5000 / BASE_TIMER_INTERVAL; |
| |
| T2 = 4000 / BASE_TIMER_INTERVAL; |
| TIMER_I = T4; |
| |
| TIMER_K = T4; |
| |
| TIMER_D = 32000 / BASE_TIMER_INTERVAL; |
| |
| } |
| |
| /** |
| * Close the encapsulated channel. |
| */ |
| public void close() { |
| this.encapsulatedChannel.close(); |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Closing " + this.encapsulatedChannel); |
| |
| } |
| |
| public boolean isSecure() { |
| return encapsulatedChannel.isSecure(); |
| } |
| |
| public MessageProcessor getMessageProcessor() { |
| return this.encapsulatedChannel.getMessageProcessor(); |
| } |
| |
| /** |
| * Set the application data pointer. This is un-interpreted by the stack. |
| * This is provided as a conveniant way of keeping book-keeping data for |
| * applications. Note that null clears the application data pointer |
| * (releases it). |
| * |
| * @param applicationData -- |
| * application data pointer to set. null clears the applicationd |
| * data pointer. |
| * |
| */ |
| |
| public void setApplicationData(Object applicationData) { |
| this.applicationData = applicationData; |
| } |
| |
| /** |
| * Get the application data associated with this transaction. |
| * |
| * @return stored application data. |
| */ |
| public Object getApplicationData() { |
| return this.applicationData; |
| } |
| |
| /** |
| * Set the encapsuated channel. The peer inet address and port are set equal |
| * to the message channel. |
| */ |
| public void setEncapsulatedChannel(MessageChannel messageChannel) { |
| this.encapsulatedChannel = messageChannel; |
| this.peerInetAddress = messageChannel.getPeerInetAddress(); |
| this.peerPort = messageChannel.getPeerPort(); |
| } |
| |
| /** |
| * Return the SipProvider for which the transaction is assigned. |
| * |
| * @return the SipProvider for the transaction. |
| */ |
| public SipProviderImpl getSipProvider() { |
| |
| return this.getMessageProcessor().getListeningPoint().getProvider(); |
| } |
| |
| /** |
| * Raise an IO Exception event - this is used for reporting asynchronous IO |
| * Exceptions that are attributable to this transaction. |
| * |
| */ |
| public void raiseIOExceptionEvent() { |
| setState(TransactionState.TERMINATED); |
| String host = getPeerAddress(); |
| int port = getPeerPort(); |
| String transport = getTransport(); |
| IOExceptionEvent exceptionEvent = new IOExceptionEvent(this, host, |
| port, transport); |
| getSipProvider().handleEvent(exceptionEvent, this); |
| } |
| |
| /** |
| * A given tx can process only a single outstanding event at a time. This |
| * semaphore gaurds re-entrancy to the transaction. |
| * |
| */ |
| public boolean acquireSem() { |
| boolean retval = false; |
| try { |
| if (sipStack.getStackLogger().isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("acquireSem [[[[" + this); |
| sipStack.getStackLogger().logStackTrace(); |
| } |
| retval = this.semaphore.tryAcquire(1000, TimeUnit.MILLISECONDS); |
| if ( sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "acquireSem() returning : " + retval); |
| return retval; |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Unexpected exception acquiring sem", |
| ex); |
| InternalErrorHandler.handleException(ex); |
| return false; |
| } finally { |
| this.isSemaphoreAquired = retval; |
| } |
| |
| } |
| |
| /** |
| * Release the transaction semaphore. |
| * |
| */ |
| public void releaseSem() { |
| try { |
| |
| this.toListener = false; |
| this.semRelease(); |
| |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Unexpected exception releasing sem", |
| ex); |
| |
| } |
| |
| } |
| |
| protected void semRelease() { |
| try { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("semRelease ]]]]" + this); |
| sipStack.getStackLogger().logStackTrace(); |
| } |
| this.isSemaphoreAquired = false; |
| this.semaphore.release(); |
| |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Unexpected exception releasing sem", |
| ex); |
| |
| } |
| } |
| |
| /** |
| * Set true to pass the request up to the listener. False otherwise. |
| * |
| */ |
| |
| public boolean passToListener() { |
| return toListener; |
| } |
| |
| /** |
| * Set the passToListener flag to true. |
| */ |
| public void setPassToListener() { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("setPassToListener()"); |
| } |
| this.toListener = true; |
| |
| } |
| |
| /** |
| * Flag to test if the terminated event is delivered. |
| * |
| * @return |
| */ |
| protected synchronized boolean testAndSetTransactionTerminatedEvent() { |
| boolean retval = !this.terminatedEventDelivered; |
| this.terminatedEventDelivered = true; |
| return retval; |
| } |
| |
| public String getCipherSuite() throws UnsupportedOperationException { |
| if (this.getMessageChannel() instanceof TLSMessageChannel ) { |
| if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) |
| return null; |
| else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null) |
| return null; |
| else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getCipherSuite(); |
| } else throw new UnsupportedOperationException("Not a TLS channel"); |
| |
| } |
| |
| |
| public java.security.cert.Certificate[] getLocalCertificates() throws UnsupportedOperationException { |
| if (this.getMessageChannel() instanceof TLSMessageChannel ) { |
| if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) |
| return null; |
| else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null) |
| return null; |
| else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getLocalCertificates(); |
| } else throw new UnsupportedOperationException("Not a TLS channel"); |
| } |
| |
| |
| public java.security.cert.Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { |
| if (this.getMessageChannel() instanceof TLSMessageChannel ) { |
| if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener() == null ) |
| return null; |
| else if ( ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent() == null) |
| return null; |
| else return ((TLSMessageChannel) this.getMessageChannel()).getHandshakeCompletedListener().getHandshakeCompletedEvent().getPeerCertificates(); |
| } else throw new UnsupportedOperationException("Not a TLS channel"); |
| |
| } |
| |
| |
| /** |
| * Start the timer that runs the transaction state machine. |
| * |
| */ |
| |
| protected abstract void startTransactionTimer(); |
| |
| /** |
| * Tests a message to see if it is part of this transaction. |
| * |
| * @return True if the message is part of this transaction, false if not. |
| */ |
| public abstract boolean isMessagePartOfTransaction(SIPMessage messageToTest); |
| |
| /** |
| * This method is called when this transaction's retransmission timer has |
| * fired. |
| */ |
| protected abstract void fireRetransmissionTimer(); |
| |
| /** |
| * This method is called when this transaction's timeout timer has fired. |
| */ |
| protected abstract void fireTimeoutTimer(); |
| |
| } |