1282 lines
41 KiB
Java
1282 lines
41 KiB
Java
/*
|
|
* 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();
|
|
|
|
}
|