android.gov.nist.javax.sip.stack.SIPServerTransactionImpl Maven / Gradle / Ivy
/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain. As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY. NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
package android.gov.nist.javax.sip.stack;
import android.gov.nist.core.CommonLogger;
import android.gov.nist.core.HostPort;
import android.gov.nist.core.InternalErrorHandler;
import android.gov.nist.core.LogWriter;
import android.gov.nist.core.ServerLogger;
import android.gov.nist.core.StackLogger;
import android.gov.nist.javax.sip.ReleaseReferencesStrategy;
import android.gov.nist.javax.sip.SIPConstants;
import android.gov.nist.javax.sip.SipProviderImpl;
import android.gov.nist.javax.sip.Utils;
import android.gov.nist.javax.sip.header.Expires;
import android.gov.nist.javax.sip.header.ParameterNames;
import android.gov.nist.javax.sip.header.RSeq;
import android.gov.nist.javax.sip.header.Via;
import android.gov.nist.javax.sip.message.SIPMessage;
import android.gov.nist.javax.sip.message.SIPRequest;
import android.gov.nist.javax.sip.message.SIPResponse;
import android.gov.nist.javax.sip.stack.IllegalTransactionStateException.Reason;
import java.io.IOException;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import android.javax.sip.Dialog;
import android.javax.sip.DialogState;
import android.javax.sip.DialogTerminatedEvent;
import android.javax.sip.ObjectInUseException;
import android.javax.sip.SipException;
import android.javax.sip.Timeout;
import android.javax.sip.TimeoutEvent;
import android.javax.sip.TransactionState;
import android.javax.sip.address.Hop;
import android.javax.sip.header.ContactHeader;
import android.javax.sip.header.ContentTypeHeader;
import android.javax.sip.header.ExpiresHeader;
import android.javax.sip.header.RSeqHeader;
import android.javax.sip.message.Request;
import android.javax.sip.message.Response;
/*
* Bug fixes / enhancements:Emil Ivov, Antonis Karydas, Daniel J. Martinez Manzano, Daniel, Hagai
* Sela, Vazques-Illa, Bill Roome, Thomas Froment and Pierre De Rop, Christophe Anzille and Jeroen
* van Bemmel, Frank Reif.
* Carolyn Beeton ( Avaya ).
*
*/
/**
* Represents a server transaction. Implements the following state machines.
*
*
*
*
*
* |INVITE
* |pass INV to TU
* INVITE V send 100 if TU won't in 200ms
* send response+-----------+
* +--------| |--------+101-199 from TU
* | | Proceeding| |send response
* +------->| |<-------+
* | | Transport Err.
* | | Inform TU
* | |--------------->+
* +-----------+ |
* 300-699 from TU | |2xx from TU |
* send response | |send response |
* | +------------------>+
* | |
* INVITE V Timer G fires |
* send response+-----------+ send response |
* +--------| |--------+ |
* | | Completed | | |
* +------->| |<-------+ |
* +-----------+ |
* | | |
* ACK | | |
* - | +------------------>+
* | Timer H fires |
* V or Transport Err.|
* +-----------+ Inform TU |
* | | |
* | Confirmed | |
* | | |
* +-----------+ |
* | |
* |Timer I fires |
* |- |
* | |
* V |
* +-----------+ |
* | | |
* | Terminated|<---------------+
* | |
* +-----------+
*
* Figure 7: INVITE server transaction
* Request received
* |pass to TU
*
* V
* +-----------+
* | |
* | Trying |-------------+
* | | |
* +-----------+ |200-699 from TU
* | |send response
* |1xx from TU |
* |send response |
* | |
* Request V 1xx from TU |
* send response+-----------+send response|
* +--------| |--------+ |
* | | Proceeding| | |
* +------->| |<-------+ |
* +<--------------| | |
* |Trnsprt Err +-----------+ |
* |Inform TU | |
* | | |
* | |200-699 from TU |
* | |send response |
* | Request V |
* | send response+-----------+ |
* | +--------| | |
* | | | Completed |<------------+
* | +------->| |
* +<--------------| |
* |Trnsprt Err +-----------+
* |Inform TU |
* | |Timer J fires
* | |-
* | |
* | V
* | +-----------+
* | | |
* +-------------->| Terminated|
* | |
* +-----------+
*
*
*
*
*
*
*
* @version 1.2 $Revision: 1.150 $ $Date: 2010-12-02 22:04:15 $
* @author M. Ranganathan
*
*/
public class SIPServerTransactionImpl extends SIPTransactionImpl implements SIPServerTransaction {
private static StackLogger logger = CommonLogger.getLogger(SIPServerTransaction.class);
private int rseqNumber = -1;
// private LinkedList pendingRequests;
// Real RequestInterface to pass messages to
private transient ServerRequestInterface requestOf;
private SIPDialog dialog;
// jeand needed because we nullify the dialog ref early and keep only the dialogId to save on mem and help GC
protected String dialogId;
// the unacknowledged SIPResponse
// private SIPResponse pendingReliableResponse;
// wondering if the pendingReliableResponseAsBytes could be put into the lastResponseAsBytes
private byte[] pendingReliableResponseAsBytes;
private String pendingReliableResponseMethod;
private long pendingReliableCSeqNumber;
private long pendingReliableRSeqNumber;
// The pending reliable Response Timer
private ProvisionalResponseTask provisionalResponseTask;
private boolean retransmissionAlertEnabled;
private RetransmissionAlertTimerTask retransmissionAlertTimerTask;
protected boolean isAckSeen;
private SIPClientTransaction pendingSubscribeTransaction;
private SIPServerTransaction inviteTransaction;
// Experimental.
private static boolean interlockProvisionalResponses = true;
private Semaphore provisionalResponseSem = new Semaphore(1);
private Semaphore terminationSemaphore = new Semaphore(0);
// jeand we nullify the last response fast to save on mem and help GC, but we keep only the information needed
private byte[] lastResponseAsBytes;
private String lastResponseHost;
private int lastResponsePort;
private String lastResponseTransport;
private int lastResponseStatusCode;
private HostPort originalRequestSentBy;
private String originalRequestFromTag;
/**
* This timer task is used for alerting the application to send retransmission alerts.
*
*
*/
class RetransmissionAlertTimerTask extends SIPStackTimerTask {
String dialogId;
int ticks;
int ticksLeft;
public RetransmissionAlertTimerTask(String dialogId) {
this.ticks = SIPTransactionImpl.T1;
this.ticksLeft = this.ticks;
// Fix from http://java.net/jira/browse/JSIP-443
// by mitchell.c.ackerman
this.dialogId = dialogId;
}
public void runTask() {
SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this;
ticksLeft--;
if (ticksLeft == -1) {
serverTransaction.fireRetransmissionTimer();
this.ticksLeft = 2 * ticks;
}
}
@Override
public Object getThreadHash() {
Request request = getRequest();
if (request != null && request instanceof SIPRequest) {
return ((SIPRequest)request).getCallIdHeader().getCallId();
} else {
return null;
}
}
}
class ProvisionalResponseTask extends SIPStackTimerTask {
int ticks;
int ticksLeft;
public ProvisionalResponseTask() {
this.ticks = SIPTransactionImpl.T1;
this.ticksLeft = this.ticks;
}
public void runTask() {
SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this;
/*
* The reliable provisional response is passed to the transaction layer periodically
* with an interval that starts at T1 seconds and doubles for each retransmission (T1
* is defined in Section 17 of RFC 3261). Once passed to the server transaction, it is
* added to an internal list of unacknowledged reliable provisional responses. The
* transaction layer will forward each retransmission passed from the UAS core.
*
* This differs from retransmissions of 2xx responses, whose intervals cap at T2
* seconds. This is because retransmissions of ACK are triggered on receipt of a 2xx,
* but retransmissions of PRACK take place independently of reception of 1xx.
*/
// If the transaction has terminated,
if (serverTransaction.isTerminated()) {
sipStack.getTimer().cancel(this);
} else {
ticksLeft--;
if (ticksLeft == -1) {
serverTransaction.fireReliableResponseRetransmissionTimer();
this.ticksLeft = 2 * ticks;
this.ticks = this.ticksLeft;
// timer H MUST be set to fire in 64*T1 seconds for all transports. Timer H
// determines when the server
// transaction abandons retransmitting the response
if (this.ticksLeft >= SIPTransactionImpl.TIMER_H) {
sipStack.getTimer().cancel(this);
setState(TransactionState._TERMINATED);
fireTimeoutTimer();
}
}
}
}
@Override
public Object getThreadHash() {
Request request = getRequest();
if (request != null && request instanceof SIPRequest) {
return ((SIPRequest)request).getCallIdHeader().getCallId();
} else {
return null;
}
}
}
/**
* This timer task is for INVITE server transactions. It will send a trying in 200 ms. if the
* TU does not do so.
*
*/
class SendTrying extends SIPStackTimerTask {
protected SendTrying() {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("scheduled timer for " + SIPServerTransactionImpl.this);
}
public void runTask() {
SIPServerTransactionImpl serverTransaction = SIPServerTransactionImpl.this;
int realState = serverTransaction.getRealState();
if (realState < 0 || TransactionState._TRYING == realState) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug(" sending Trying current state = "
+ serverTransaction.getRealState());
try {
serverTransaction.sendMessage(serverTransaction.getOriginalRequest()
.createResponse(100, "Trying"));
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug(" trying sent "
+ serverTransaction.getRealState());
} catch (IOException ex) {
if (logger.isLoggingEnabled())
logger.logError("IO error sending TRYING");
}
}
}
@Override
public Object getThreadHash() {
Request request = getRequest();
if (request != null && request instanceof SIPRequest) {
return ((SIPRequest)request).getCallIdHeader().getCallId();
} else {
return null;
}
}
}
class TransactionTimer extends SIPStackTimerTask {
public TransactionTimer() {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("TransactionTimer() : " + getTransactionId());
}
}
public void runTask() {
// If the transaction has terminated,
if (isTerminated()) {
// Keep the transaction hanging around in the transaction table
// to catch the incoming ACK -- this is needed for tcp only.
// Note that the transaction record is actually removed in
// the connection linger timer.
try {
sipStack.getTimer().cancel(this);
} catch (IllegalStateException ex) {
if (!sipStack.isAlive())
return;
}
// Oneshot timer that garbage collects the SeverTransaction
// after a scheduled amount of time. The linger timer allows
// the client side of the tx to use the same connection to
// send an ACK and prevents a race condition for creation
// of new server tx
SIPStackTimerTask myTimer = new LingerTimer();
if(sipStack.getConnectionLingerTimer() != 0) {
sipStack.getTimer().schedule(myTimer, sipStack.getConnectionLingerTimer() * 1000);
} else {
myTimer.runTask();
}
} else {
// Add to the fire list -- needs to be moved
// outside the synchronized block to prevent
// deadlock.
fireTimer();
}
if(originalRequest != null) {
originalRequest.cleanUp();
}
}
@Override
public Object getThreadHash() {
Request request = getRequest();
if (request != null && request instanceof SIPRequest) {
return ((SIPRequest)request).getCallIdHeader().getCallId();
} else {
return null;
}
}
}
/**
* Send a response.
*
* @param transactionResponse -- the response to send
*
*/
protected void sendResponse(SIPResponse transactionResponse) throws IOException {
if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("sipServerTransaction::sendResponse " + transactionResponse.getFirstLine());
}
try {
// RFC18.2.2. Sending Responses
// The server transport uses the value of the top Via header field
// in
// order
// to determine where to send a response.
// It MUST follow the following process:
// If the "sent-protocol" is a reliable transport
// protocol such as TCP or SCTP,
// or TLS over those, the response MUST be
// sent using the existing connection
// to the source of the original request
// that created the transaction, if that connection is still open.
if (isReliable()) {
getMessageChannel().sendMessage(transactionResponse);
} else {
Via via = transactionResponse.getTopmostVia();
String transport = via.getTransport();
if (transport == null)
throw new IOException("missing transport!");
// @@@ hagai Symmetric NAT support
int port = via.getRPort();
if (port == -1)
port = via.getPort();
if (port == -1) {
if (transport.equalsIgnoreCase("TLS"))
port = 5061;
else
port = 5060;
}
// Otherwise, if the Via header field value contains a
// "maddr" parameter, the response MUST be forwarded to
// the address listed there, using the port indicated in
// "sent-by",
// or port 5060 if none is present. If the address is a
// multicast
// address, the response SHOULD be sent using
// the TTL indicated in the "ttl" parameter, or with a
// TTL of 1 if that parameter is not present.
String host = null;
if (via.getMAddr() != null) {
host = via.getMAddr();
} else {
// Otherwise (for unreliable unicast transports),
// if the top Via has a "received" parameter, the response
// MUST
// be sent to the
// address in the "received" parameter, using the port
// indicated
// in the
// "sent-by" value, or using port 5060 if none is specified
// explicitly.
host = via.getParameter(Via.RECEIVED);
if (host == null) {
// Otherwise, if it is not receiver-tagged, the response
// MUST be
// sent to the address indicated by the "sent-by" value,
// using the procedures in Section 5
// RFC 3263 PROCEDURE TO BE DONE HERE
host = via.getHost();
}
}
Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(host, port,
transport));
MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack())
.createRawMessageChannel(this.getSipProvider().getListeningPoint(
hop.getTransport()).getIPAddress(), this.getPort(), hop);
if (messageChannel != null) {
messageChannel.sendMessage(transactionResponse);
lastResponseHost = host;
lastResponsePort = port;
lastResponseTransport = transport;
} else {
throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+
this.getSipProvider().getListeningPoint(
hop.getTransport()).getIPAddress() + ":" + this.getPort());
}
}
lastResponseAsBytes = transactionResponse.encodeAsBytes(this.getTransport());
lastResponse = null;
} finally {
this.startTransactionTimer();
}
}
/**
* Creates a new server transaction.
*
* @param sipStack Transaction stack this transaction belongs to.
* @param newChannelToUse Channel to encapsulate.
*/
protected SIPServerTransactionImpl(SIPTransactionStack sipStack, MessageChannel newChannelToUse) {
super(sipStack, newChannelToUse);
// Only one outstanding request for a given server tx.
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("Creating Server Transaction" + this.getBranchId());
logger.logStackTrace();
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setRequestInterface(android.gov.nist.javax.sip.stack.ServerRequestInterface)
*/
@Override
public void setRequestInterface(ServerRequestInterface newRequestOf) {
requestOf = newRequestOf;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getResponseChannel()
*/
@Override
public MessageChannel getResponseChannel() {
return encapsulatedChannel;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#isMessagePartOfTransaction(android.gov.nist.javax.sip.message.SIPMessage)
*/
@Override
public boolean isMessagePartOfTransaction(SIPMessage messageToTest) {
// List of Via headers in the message to test
// ViaList viaHeaders;
// Flags whether the select message is part of this transaction
boolean transactionMatches = false;
final String method = messageToTest.getCSeq().getMethod();
SIPRequest origRequest = getOriginalRequest();
// Invite Server transactions linger in the terminated state in the
// transaction
// table and are matched to compensate for
// http://bugs.sipit.net/show_bug.cgi?id=769
if (isInviteTransaction() || !isTerminated()) {
// Get the topmost Via header and its branch parameter
final Via topViaHeader = messageToTest.getTopmostVia();
if (topViaHeader != null) {
// topViaHeader = (Via) viaHeaders.getFirst();
// Branch code in the topmost Via header
String messageBranch = topViaHeader.getBranch();
if (messageBranch != null) {
// If the branch parameter exists but
// does not start with the magic cookie,
if (!messageBranch.toLowerCase().startsWith(
SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
// Flags this as old
// (RFC2543-compatible) client
// version
messageBranch = null;
}
}
// If a new branch parameter exists,
if (messageBranch != null && this.getBranch() != null) {
if (method.equals(Request.CANCEL)) {
// Cancel is handled as a special case because it
// shares the same same branch id of the invite
// that it is trying to cancel.
transactionMatches = this.getMethod().equals(Request.CANCEL)
&& getBranch().equalsIgnoreCase(messageBranch)
&& topViaHeader.getSentBy().equals(
origRequest.getTopmostVia()
.getSentBy());
} else {
// Matching server side transaction with only the
// branch parameter.
if(origRequest != null) {
transactionMatches = getBranch().equalsIgnoreCase(messageBranch)
&& topViaHeader.getSentBy().equals(
origRequest.getTopmostVia()
.getSentBy());
} else {
transactionMatches = getBranch().equalsIgnoreCase(messageBranch)
&& topViaHeader.getSentBy().equals(originalRequestSentBy);
}
}
} else {
// force the reparsing only on non RFC 3261 messages
origRequest = (SIPRequest) getRequest();
// This is an RFC2543-compliant message; this code is here
// for backwards compatibility.
// It is a weak check.
// If RequestURI, To tag, From tag, CallID, CSeq number, and
// top Via headers are the same, the
// SIPMessage matches this transaction. An exception is for
// a CANCEL request, which is not deemed
// to be part of an otherwise-matching INVITE transaction.
String originalFromTag = origRequest.getFromTag();
String thisFromTag = messageToTest.getFrom().getTag();
boolean skipFrom = (originalFromTag == null || thisFromTag == null);
String originalToTag = origRequest.getToTag();
String thisToTag = messageToTest.getTo().getTag();
boolean skipTo = (originalToTag == null || thisToTag == null);
boolean isResponse = (messageToTest instanceof SIPResponse);
// Issue #96: special case handling for a CANCEL request -
// the CSeq method of the original request must
// be CANCEL for it to have a chance at matching.
if (messageToTest.getCSeq().getMethod().equalsIgnoreCase(Request.CANCEL)
&& !origRequest.getCSeq().getMethod().equalsIgnoreCase(
Request.CANCEL)) {
transactionMatches = false;
} else if ((isResponse || origRequest.getRequestURI().equals(
((SIPRequest) messageToTest).getRequestURI()))
&& (skipFrom || originalFromTag != null && originalFromTag.equalsIgnoreCase(thisFromTag))
&& (skipTo || originalToTag != null && originalToTag.equalsIgnoreCase(thisToTag))
&& origRequest.getCallId().getCallId().equalsIgnoreCase(
messageToTest.getCallId().getCallId())
&& origRequest.getCSeq().getSeqNumber() == messageToTest
.getCSeq().getSeqNumber()
&& ((!messageToTest.getCSeq().getMethod().equals(Request.CANCEL)) ||
getMethod().equals(messageToTest.getCSeq().getMethod()))
&& topViaHeader.equals(origRequest.getTopmostVia())) {
transactionMatches = true;
}
}
}
}
return transactionMatches;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#map()
*/
@Override
public void map() {
// note that TRYING is a pseudo-state for invite transactions
int realState = getRealState();
if (realState < 0 || realState == TransactionState._TRYING) {
// Also sent by intermediate proxies.
// null check added as the stack may be stopped. TRYING is not sent by reliable transports.
if (isInviteTransaction() && !this.isMapped && sipStack.getTimer() != null ) {
this.isMapped = true;
// Schedule a timer to fire in 200 ms if the
// TU did not send a trying in that time.
sipStack.getTimer().schedule(new SendTrying(), 200);
} else {
isMapped = true;
}
}
// Pull it out of the pending transactions list.
sipStack.removePendingTransaction(this);
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#processRequest(android.gov.nist.javax.sip.message.SIPRequest, android.gov.nist.javax.sip.stack.MessageChannel)
*/
@Override
public void processRequest(SIPRequest transactionRequest, MessageChannel sourceChannel) {
boolean toTu = false;
// Can only process a single request directed to the
// transaction at a time. For a given server transaction
// the listener sees only one event at a time.
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("processRequest: " + transactionRequest.getFirstLine());
logger.logDebug("tx state = " + this.getRealState());
}
try {
// If this is the first request for this transaction,
if (getRealState() < 0) {
// Save this request as the one this
// transaction is handling
setOriginalRequest(transactionRequest);
this.setState(TransactionState._TRYING);
toTu = true;
this.setPassToListener();
// Rsends the TRYING on retransmission of the request.
if (isInviteTransaction() && this.isMapped) {
// JvB: also
// proxies need
// to do this
// Has side-effect of setting
// state to "Proceeding"
sendMessage(transactionRequest.createResponse(100, "Trying"));
}
// If an invite transaction is ACK'ed while in
// the completed state,
} else if (isInviteTransaction() && TransactionState._COMPLETED == getRealState()
&& transactionRequest.getMethod().equals(Request.ACK)) {
// @jvB bug fix
this.setState(TransactionState._CONFIRMED);
disableRetransmissionTimer();
if (!isReliable()) {
enableTimeoutTimer(timerI);
} else {
this.setState(TransactionState._TERMINATED);
}
// JvB: For the purpose of testing a TI, added a property to
// pass it anyway
if (sipStack.isNon2XXAckPassedToListener()) {
// This is useful for test applications that want to see
// all messages.
requestOf.processRequest(transactionRequest, encapsulatedChannel);
} else {
// According to RFC3261 Application should not Ack in
// CONFIRMED state
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("ACK received for server Tx "
+ this.getTransactionId() + " not delivering to application!");
}
this.semRelease();
}
return;
// If we receive a retransmission of the original
// request,
} else if (transactionRequest.getMethod().equals(getMethod())) {
if (TransactionState._PROCEEDING == getRealState()
|| TransactionState._COMPLETED == getRealState()) {
this.semRelease();
// Resend the last response to
// the client
// Send the message to the client
resendLastResponseAsBytes();
} else if (transactionRequest.getMethod().equals(Request.ACK)) {
// This is passed up to the TU to suppress
// retransmission of OK
if (requestOf != null)
requestOf.processRequest(transactionRequest, encapsulatedChannel);
else
this.semRelease();
} else {
// none of the above? well release the lock anyhow!
this.semRelease();
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("completed processing retransmitted request : "
+ transactionRequest.getFirstLine() + this + " txState = "
+ this.getState() + " lastResponse = " + this.lastResponseAsBytes);
return;
}
// Pass message to the TU
if (TransactionState._COMPLETED != getRealState()
&& TransactionState._TERMINATED != getRealState() && requestOf != null) {
if (getMethod().equals(transactionRequest.getMethod())) {
// Only send original request to TU once!
if (toTu) {
requestOf.processRequest(transactionRequest, encapsulatedChannel);
} else
this.semRelease();
} else {
if (requestOf != null)
requestOf.processRequest(transactionRequest, encapsulatedChannel);
else
this.semRelease();
}
} else {
// This seems like a common bug so I am allowing it through!
if (SIPTransactionStack.isDialogCreated(getMethod())
&& getRealState() == TransactionState._TERMINATED
&& transactionRequest.getMethod().equals(Request.ACK)
&& requestOf != null) {
SIPDialog thisDialog = (SIPDialog) getDialog();
if (thisDialog == null || !thisDialog.ackProcessed) {
// Filter out duplicate acks
if (thisDialog != null) {
thisDialog.ackReceived(transactionRequest.getCSeq().getSeqNumber());
thisDialog.ackProcessed = true;
}
requestOf.processRequest(transactionRequest, encapsulatedChannel);
} else {
this.semRelease();
}
} else if (transactionRequest.getMethod().equals(Request.CANCEL)) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("Too late to cancel Transaction");
this.semRelease();
// send OK and just ignore the CANCEL.
try {
this.sendMessage(transactionRequest.createResponse(Response.OK));
} catch (IOException ex) {
// Transaction is already terminated
// just ignore the IOException.
}
} else {
// none of the above? well release the lock anyhow!
this.semRelease();
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("Dropping request " + getRealState());
}
} catch (IOException e) {
if (logger.isLoggingEnabled())
logger.logError("IOException " ,e);
this.semRelease();
this.raiseIOExceptionEvent();
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#sendMessage(android.gov.nist.javax.sip.message.SIPMessage)
*/
@Override
public void sendMessage(SIPMessage messageToSend) throws IOException {
if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("sipServerTransaction::sendMessage " + messageToSend.getFirstLine());
}
// Message typecast as a response
final SIPResponse transactionResponse = (SIPResponse) messageToSend;
// Status code of the response being sent to the client
final int statusCode = transactionResponse.getStatusCode();
try {
try {
// Provided we have set the banch id for this we set the BID for
// the
// outgoing via.
if (originalRequestBranch != null)
transactionResponse.getTopmostVia().setBranch(this.getBranch());
else
transactionResponse.getTopmostVia().removeParameter(ParameterNames.BRANCH);
// Make the topmost via headers match identically for the
// transaction rsponse.
if (!originalRequestHasPort)
transactionResponse.getTopmostVia().removePort();
} catch (ParseException ex) {
logger.logError("UnexpectedException",ex);
throw new IOException("Unexpected exception");
}
// Method of the response does not match the request used to
// create the transaction - transaction state does not change.
if (!transactionResponse.getCSeq().getMethod().equals(
getMethod())) {
sendResponse(transactionResponse);
return;
}
if(!checkStateTimers(statusCode)) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("checkStateTimers returned false -- not sending message");
}
return;
}
try {
// Send the message to the client.
// Record the last message sent out.
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug(
"sendMessage : tx = " + this + " getState = " + this.getState());
}
lastResponse = transactionResponse;
lastResponseStatusCode = transactionResponse.getStatusCode();
this.sendResponse(transactionResponse);
} catch (IOException e) {
this.setState(TransactionState._TERMINATED);
this.collectionTime = 0;
throw e;
}
} finally {
this.startTransactionTimer();
}
}
private boolean checkStateTimers(int statusCode) {
// If the TU sends a provisional response while in the
// trying state,
if (getRealState() == TransactionState._TRYING) {
if (statusCode / 100 == 1) {
this.setState(TransactionState._PROCEEDING);
} else if (200 <= statusCode && statusCode <= 699) {
// INVITE ST has TRYING as a Pseudo state
// (See issue 76). We are using the TRYING
// pseudo state invite Transactions
// to signal if the application
// has sent trying or not and hence this
// check is necessary.
if (!isInviteTransaction()) {
if (!isReliable() && getInternalState() != TransactionState._COMPLETED) {
// Linger in the completed state to catch
// retransmissions if the transport is not
// reliable.
this.setState(TransactionState._COMPLETED);
// Note that Timer J is only set for Unreliable
// transports -- see Issue 75.
/*
* From RFC 3261 Section 17.2.2 (non-invite server transaction)
*
* When the server transaction enters the "Completed" state, it MUST
* set Timer J to fire in 64*T1 seconds for unreliable transports, and
* zero seconds for reliable transports. While in the "Completed"
* state, the server transaction MUST pass the final response to the
* transport layer for retransmission whenever a retransmission of the
* request is received. Any other final responses passed by the TU to
* the server transaction MUST be discarded while in the "Completed"
* state. The server transaction remains in this state until Timer J
* fires, at which point it MUST transition to the "Terminated" state.
*/
startTransactionTimerJ(TIMER_J);
cleanUpOnTimer();
} else {
cleanUpOnTimer();
this.setState(TransactionState._TERMINATED);
startTransactionTimerJ(0);
}
} else {
// This is the case for INVITE server transactions.
// essentially, it duplicates the code in the
// PROCEEDING case below. There is no TRYING state for INVITE
// transactions in the RFC. We are using it to signal whether the
// application has sent a provisional response or not. Hence
// this is treated the same as as Proceeding.
if (statusCode / 100 == 2) {
// Status code is 2xx means that the
// transaction transitions to TERMINATED
// for both Reliable as well as unreliable
// transports. Note that the dialog layer
// takes care of retransmitting 2xx final
// responses.
/*
* RFC 3261 Section 13.3.1.4 Note, however, that the INVITE server
* transaction will be destroyed as soon as it receives this final
* response and passes it to the transport. Therefore, it is necessary
* to periodically pass the response directly to the transport until
* the ACK arrives. The 2xx response is passed to the transport with
* an interval that starts at T1 seconds and doubles for each
* retransmission until it reaches T2 seconds (T1 and T2 are defined
* in Section 17). Response retransmissions cease when an ACK request
* for the response is received. This is independent of whatever
* transport protocols are used to send the response.
*/
this.disableRetransmissionTimer();
this.disableTimeoutTimer();
this.collectionTime = TIMER_J;
cleanUpOnTimer();
this.setState(TransactionState._TERMINATED);
if (this.getDialog() != null)
((SIPDialog) this.getDialog()).setRetransmissionTicks();
} else {
// This an error final response.
this.setState(TransactionState._COMPLETED);
if (!isReliable()) {
/*
* RFC 3261
*
* While in the "Proceeding" state, if the TU passes a response
* with status code from 300 to 699 to the server transaction, the
* response MUST be passed to the transport layer for
* transmission, and the state machine MUST enter the "Completed"
* state. For unreliable transports, timer G is set to fire in T1
* seconds, and is not set to fire for reliable transports.
*/
enableRetransmissionTimer();
}
cleanUpOnTimer();
enableTimeoutTimer(TIMER_H);
}
}
}
// If the transaction is in the proceeding state,
} else if (getRealState() == TransactionState._PROCEEDING) {
if (isInviteTransaction()) {
// If the response is a failure message,
if (statusCode / 100 == 2) {
// Set up to catch returning ACKs
// The transaction lingers in the
// terminated state for some time
// to catch retransmitted INVITEs
this.disableRetransmissionTimer();
this.disableTimeoutTimer();
this.collectionTime = TIMER_J;
cleanUpOnTimer();
this.setState(TransactionState._TERMINATED);
if (this.getDialog() != null)
((SIPDialog) this.getDialog()).setRetransmissionTicks();
} else if (300 <= statusCode && statusCode <= 699) {
// Set up to catch returning ACKs
this.setState(TransactionState._COMPLETED);
if (!isReliable()) {
/*
* While in the "Proceeding" state, if the TU passes a response with
* status code from 300 to 699 to the server transaction, the response
* MUST be passed to the transport layer for transmission, and the
* state machine MUST enter the "Completed" state. For unreliable
* transports, timer G is set to fire in T1 seconds, and is not set to
* fire for reliable transports.
*/
enableRetransmissionTimer();
}
cleanUpOnTimer();
enableTimeoutTimer(TIMER_H);
}
// If the transaction is not an invite transaction
// and this is a final response,
} else if (200 <= statusCode && statusCode <= 699) {
// This is for Non-invite server transactions.
// Set up to retransmit this response,
// or terminate the transaction
this.setState(TransactionState._COMPLETED);
if (!isReliable()) {
disableRetransmissionTimer();
// enableTimeoutTimer(TIMER_J);
startTransactionTimerJ(TIMER_J);
} else {
this.setState(TransactionState._TERMINATED);
startTransactionTimerJ(0);
}
cleanUpOnTimer();
}
// If the transaction has already completed,
} else if (TransactionState._COMPLETED == this.getRealState()) {
return false;
}
return true;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getViaHost()
*/
@Override
public String getViaHost() {
return super.getViaHost();
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getViaPort()
*/
@Override
public int getViaPort() {
return super.getViaPort();
}
/**
* @see android.gov.nist.javax.sip.stack.SIPTransactionImpl#fireRetransmissionTimer()
*/
@Override
public void fireRetransmissionTimer() {
try {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("fireRetransmissionTimer() -- " + this + " state " + getState());
}
// Resend the last response sent by this transaction
if (isInviteTransaction() && (lastResponse != null || lastResponseAsBytes != null)) {
// null can happen if this is terminating when the timer fires.
if (!this.retransmissionAlertEnabled || sipStack.isTransactionPendingAck(this) ) {
// Retransmit last response until ack.
if (lastResponseStatusCode / 100 >= 2 && !this.isAckSeen) {
resendLastResponseAsBytes();
}
} else {
// alert the application to retransmit the last response
SipProviderImpl sipProvider = (SipProviderImpl) this.getSipProvider();
TimeoutEvent txTimeout = new TimeoutEvent(sipProvider, this,
Timeout.RETRANSMIT);
sipProvider.handleEvent(txTimeout, this);
}
}
} catch (IOException e) {
if (logger.isLoggingEnabled())
logger.logException(e);
raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR);
}
}
// jeand we nullify the last response very fast to save on mem and help GC but we keep it as byte array
// so this method is used to resend the last response either as a response or byte array depending on if it has been nullified
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#resendLastResponseAsBytes()
*/
@Override
public void resendLastResponseAsBytes() throws IOException {
if(lastResponse != null) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("resend last response " + lastResponse);
}
sendMessage(lastResponse);
} else if (lastResponseAsBytes != null) {
// Send the message to the client
// if(!checkStateTimers(lastResponseStatusCode)) {
// return;
// }
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("resend last response " + new String(lastResponseAsBytes));
}
if(isReliable()) {
if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) {
// Issue 343 : we have to log the retransmission
try {
SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null);
lastReparsedResponse.setRemoteAddress(
this.getPeerInetAddress());
lastReparsedResponse.setRemotePort(this.getPeerPort());
lastReparsedResponse.setLocalPort(
getMessageChannel().getPort());
lastReparsedResponse.setLocalAddress(
getMessageChannel()
.getMessageProcessor().getIpAddress());
getMessageChannel().logMessage(lastReparsedResponse, this.getPeerInetAddress(), this.getPeerPort(), System.currentTimeMillis());
} catch (ParseException e) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes), e);
}
}
}
getMessageChannel().sendMessage(lastResponseAsBytes, this.getPeerInetAddress(), this.getPeerPort(), false);
} else {
Hop hop = sipStack.addressResolver.resolveAddress(new HopImpl(lastResponseHost, lastResponsePort,
lastResponseTransport));
MessageChannel messageChannel = ((SIPTransactionStack) getSIPStack())
.createRawMessageChannel(this.getSipProvider().getListeningPoint(
hop.getTransport()).getIPAddress(), this.getPort(), hop);
if (messageChannel != null) {
if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) {
// Issue 343 : we have to log the retransmission
try {
SIPResponse lastReparsedResponse = (SIPResponse) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(lastResponseAsBytes, true, false, null);
lastReparsedResponse.setRemoteAddress(messageChannel.getPeerInetAddress());
lastReparsedResponse.setRemotePort(messageChannel.getPeerPort());
lastReparsedResponse.setLocalPort(messageChannel.getPort());
lastReparsedResponse.setLocalAddress(messageChannel.getMessageProcessor().getIpAddress());
messageChannel.logMessage(lastReparsedResponse, messageChannel.getPeerInetAddress(), messageChannel.getPeerPort(), System.currentTimeMillis());
} catch (ParseException e) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("couldn't reparse last response " + new String(lastResponseAsBytes), e);
}
}
}
messageChannel.sendMessage(lastResponseAsBytes, InetAddress.getByName(hop.getHost()), hop.getPort(), false);
} else {
throw new IOException("Could not create a message channel for " + hop + " with source IP:Port "+
this.getSipProvider().getListeningPoint(
hop.getTransport()).getIPAddress() + ":" + this.getPort());
}
}
}
}
private void fireReliableResponseRetransmissionTimer() {
try {
resendLastResponseAsBytes();
} catch (IOException e) {
if (logger.isLoggingEnabled())
logger.logException(e);
this.setState(TransactionState._TERMINATED);
raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR);
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPTransaction#fireTimeoutTimer()
*/
public void fireTimeoutTimer() {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("SIPServerTransaction.fireTimeoutTimer this = " + this
+ " current state = " + this.getRealState() + " method = "
+ this.getMethod());
if (isInviteTransaction() && sipStack.removeTransactionPendingAck(this) ) {
if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) ) {
logger.logDebug("Found tx pending ACK - timer H has kicked");
}
}
SIPDialog dialog = (SIPDialog) getDialog();
if (SIPTransactionStack.isDialogCreated(getMethod())
&& (TransactionState._CALLING == this.getRealState() || TransactionState._TRYING == this
.getRealState())) {
dialog.setState(SIPDialog.TERMINATED_STATE);
} else if (getMethod().equals(Request.BYE)) {
if (dialog != null && dialog.isTerminatedOnBye())
dialog.setState(SIPDialog.TERMINATED_STATE);
}
if (TransactionState._COMPLETED == this.getRealState() && isInviteTransaction()) {
raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR);
this.setState(TransactionState._TERMINATED);
sipStack.removeTransaction(this);
} else if (TransactionState._COMPLETED == this.getRealState() && !isInviteTransaction()) {
this.setState(TransactionState._TERMINATED);
if(!getMethod().equals(Request.CANCEL)) {
cleanUp();
} else {
sipStack.removeTransaction(this);
}
} else if (TransactionState._CONFIRMED == this.getRealState() && isInviteTransaction()) {
// TIMER_I should not generate a timeout
// exception to the application when the
// Invite transaction is in Confirmed state.
// Just transition to Terminated state.
this.setState(TransactionState._TERMINATED);
sipStack.removeTransaction(this);
} else if (!isInviteTransaction()
&& (TransactionState._COMPLETED == this.getRealState() || TransactionState._CONFIRMED == this
.getRealState())) {
this.setState(TransactionState._TERMINATED);
} else if (isInviteTransaction() && TransactionState._TERMINATED == this.getRealState()) {
// This state could be reached when retransmitting
raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR);
// TODO -- check this. This does not look right.
if (dialog != null)
dialog.setState(SIPDialog.TERMINATED_STATE);
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getLastResponseStatusCode()
*/
@Override
public int getLastResponseStatusCode() {
return this.lastResponseStatusCode;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setOriginalRequest(android.gov.nist.javax.sip.message.SIPRequest)
*/
@Override
public void setOriginalRequest(SIPRequest originalRequest) {
super.setOriginalRequest(originalRequest);
}
/*
* (non-Javadoc)
*
* @see android.javax.sip.ServerTransaction#sendResponse(android.javax.sip.message.Response)
*/
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#sendResponse(android.javax.sip.message.Response)
*/
@Override
public void sendResponse(Response response) throws SipException {
SIPResponse sipResponse = (SIPResponse) response;
SIPDialog dialog = (SIPDialog) getDialog();
if (response == null)
throw new NullPointerException("null response");
try {
sipResponse.checkHeaders();
} catch (ParseException ex) {
throw new IllegalTransactionStateException(ex.getMessage(), Reason.MissingRequiredHeader);
}
// check for meaningful response.
final String responseMethod = sipResponse.getCSeq().getMethod();
if (!responseMethod.equals(this.getMethod())) {
throw new IllegalTransactionStateException(
"CSeq method does not match Request method of request that created the tx.", Reason.UnmatchingCSeq);
}
/*
* 200-class responses to SUBSCRIBE requests also MUST contain an "Expires" header. The
* period of time in the response MAY be shorter but MUST NOT be longer than specified in
* the request.
*/
final int statusCode = response.getStatusCode();
if (this.getMethod().equals(Request.SUBSCRIBE) && statusCode / 100 == 2) {
if (response.getHeader(ExpiresHeader.NAME) == null) {
throw new IllegalTransactionStateException("Expires header is mandatory in 2xx response of SUBSCRIBE", Reason.ExpiresHeaderMandatory);
} else {
Expires requestExpires = (Expires) this.getOriginalRequest().getExpires();
Expires responseExpires = (Expires) response.getExpires();
/*
* If no "Expires" header is present in a SUBSCRIBE request, the implied default
* is defined by the event package being used.
*/
if (requestExpires != null
&& responseExpires.getExpires() > requestExpires.getExpires()) {
throw new SipException(
"Response Expires time exceeds request Expires time : See RFC 3265 3.1.1");
}
}
}
// Check for mandatory header.
if (statusCode == 200
&& responseMethod.equals(Request.INVITE)
&& sipResponse.getHeader(ContactHeader.NAME) == null)
throw new IllegalTransactionStateException("Contact Header is mandatory for the OK to the INVITE", Reason.ContactHeaderMandatory);
if (!this.isMessagePartOfTransaction((SIPMessage) response)) {
throw new SipException("Response does not belong to this transaction.");
}
// Fix up the response if the dialog has already been established.
try {
/*
* The UAS MAY send a final response to the initial request before
* having received PRACKs for all unacknowledged reliable provisional responses,
* unless the final response is 2xx and any of the unacknowledged reliable provisional
* responses contained a session description. In that case, it MUST NOT send a final
* response until those provisional responses are acknowledged.
*/
final ContentTypeHeader contentTypeHeader = ((SIPResponse)response).getContentTypeHeader();
if (this.pendingReliableResponseAsBytes != null
&& this.getDialog() != null
&& this.getInternalState() != TransactionState._TERMINATED
&& statusCode / 100 == 2
&& contentTypeHeader != null
&& contentTypeHeader.getContentType()
.equalsIgnoreCase(CONTENT_TYPE_APPLICATION)
&& contentTypeHeader.getContentSubType()
.equalsIgnoreCase(CONTENT_SUBTYPE_SDP)) {
if (!interlockProvisionalResponses ) {
throw new SipException("cannot send response -- unacked provisional");
} else {
try {
boolean acquired = this.provisionalResponseSem.tryAcquire(1,TimeUnit.SECONDS);
if (!acquired ) {
throw new SipException("cannot send response -- unacked provisional");
}
} catch (InterruptedException ex) {
logger.logError ("Interrupted acuqiring PRACK sem");
throw new SipException("Cannot aquire PRACK sem");
}
}
} else {
// Sending the final response cancels the
// pending response task.
if (this.pendingReliableResponseAsBytes != null && sipResponse.isFinalResponse()) {
sipStack.getTimer().cancel(provisionalResponseTask);
this.provisionalResponseTask = null;
}
}
// Dialog checks. These make sure that the response
// being sent makes sense.
if (dialog != null) {
if (statusCode / 100 == 2
&& SIPTransactionStack.isDialogCreated(responseMethod)) {
if (dialog.getLocalTag() == null && sipResponse.getToTag() == null) {
// Trying to send final response and user forgot to set
// to
// tag on the response -- be nice and assign the tag for
// the user.
sipResponse.getTo().setTag(Utils.getInstance().generateTag());
} else if (dialog.getLocalTag() != null && sipResponse.getToTag() == null) {
if ( logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("assigning toTag : serverTransaction = " + this + " dialog "
+ dialog + " tag = " + dialog.getLocalTag());
}
sipResponse.setToTag(dialog.getLocalTag());
} else if (dialog.getLocalTag() != null && sipResponse.getToTag() != null
&& !dialog.getLocalTag().equals(sipResponse.getToTag())) {
throw new SipException("Tag mismatch dialogTag is "
+ dialog.getLocalTag() + " responseTag is "
+ sipResponse.getToTag());
}
}
if (!sipResponse.getCallId().getCallId().equals(dialog.getCallId().getCallId())) {
throw new SipException("Dialog mismatch!");
}
}
// Backward compatibility slippery slope....
// Only set the from tag in the response when the
// incoming request has a from tag.
String fromTag = originalRequestFromTag;
if(getRequest() != null) {
fromTag = ((SIPRequest) this.getRequest()).getFromTag();
}
if (fromTag != null && sipResponse.getFromTag() != null
&& !sipResponse.getFromTag().equals(fromTag)) {
throw new SipException("From tag of request does not match response from tag");
} else if (fromTag != null) {
sipResponse.getFrom().setTag(fromTag);
} else {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("WARNING -- Null From tag in request!!");
}
// See if the dialog needs to be inserted into the dialog table
// or if the state of the dialog needs to be changed.
if (dialog != null && statusCode != 100) {
dialog.setResponseTags(sipResponse);
DialogState oldState = dialog.getState();
dialog.setLastResponse(this, (SIPResponse) response);
if (oldState == null && dialog.getState() == DialogState.TERMINATED) {
DialogTerminatedEvent event = new DialogTerminatedEvent(dialog
.getSipProvider(), dialog);
// Provide notification to the listener that the dialog has
// ended.
dialog.getSipProvider().handleEvent(event, this);
}
} else if (dialog == null && isInviteTransaction()
&& this.retransmissionAlertEnabled
&& this.retransmissionAlertTimerTask == null
&& response.getStatusCode() / 100 == 2) {
String dialogId = ((SIPResponse) response).getDialogId(true);
this.retransmissionAlertTimerTask = new RetransmissionAlertTimerTask(dialogId);
sipStack.retransmissionAlertTransactions.put(dialogId, this);
sipStack.getTimer().scheduleWithFixedDelay(this.retransmissionAlertTimerTask, 0,
SIPTransactionStack.BASE_TIMER_INTERVAL);
}
// Send message after possibly inserting the Dialog
// into the dialog table to avoid a possible race condition.
this.sendMessage((SIPResponse) response);
if ( dialog != null ) {
dialog.startRetransmitTimer(this, (SIPResponse)response);
}
} catch (IOException ex) {
if (logger.isLoggingEnabled())
logger.logException(ex);
this.setState(TransactionState._TERMINATED);
raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR);
throw new SipException(ex.getMessage(), ex);
} catch (java.text.ParseException ex1) {
if (logger.isLoggingEnabled())
logger.logException(ex1);
this.setState(TransactionState._TERMINATED);
throw new SipException(ex1.getMessage(), ex1);
}
}
/**
* Return the book-keeping information that we actually use.
*/
protected int getRealState() {
return super.getInternalState();
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getState()
*/
@Override
public TransactionState getState() {
// Trying is a pseudo state for INVITE transactions.
if (this.isInviteTransaction() && TransactionState._TRYING == super.getInternalState())
return TransactionState.PROCEEDING;
else
return super.getState();
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setState(int)
*/
@Override
public void setState(int newState) {
// Set this timer for connection caching
// of incoming connections.
if (newState == TransactionState._TERMINATED && this.isReliable()
&& (!getSIPStack().cacheServerConnections)) {
// Set a time after which the connection
// is closed.
this.collectionTime = TIMER_J;
this.terminationSemaphore.release();
}
super.setState(newState);
}
/**
* @see android.gov.nist.javax.sip.stack.SIPTransaction#startTransactionTimer()
*/
@Override
public void startTransactionTimer() {
if(getMethod().equalsIgnoreCase(Request.INVITE) || getMethod().equalsIgnoreCase(Request.CANCEL) || getMethod().equalsIgnoreCase(Request.ACK)) {
if (this.transactionTimerStarted.compareAndSet(false, true)) {
if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) {
// The timer is set to null when the Stack is
// shutting down.
SIPStackTimerTask myTimer = new TransactionTimer();
// Do not schedule when the stack is not alive.
if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) {
sipStack.getTimer().scheduleWithFixedDelay(myTimer, baseTimerInterval, baseTimerInterval);
}
myTimer = null;
}
}
}
}
/**
* Start the timer task.
*/
protected void startTransactionTimerJ(long time) {
if (this.transactionTimerStarted.compareAndSet(false, true)) {
if (sipStack.getTimer() != null && sipStack.getTimer().isStarted() ) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("starting TransactionTimerJ() : " + getTransactionId() + " time " + time);
}
// The timer is set to null when the Stack is
// shutting down.
SIPStackTimerTask task = new SIPStackTimerTask () {
public void runTask() {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("executing TransactionTimerJ() : " + getTransactionId());
}
fireTimeoutTimer();
cleanUp();
if(originalRequest != null) {
originalRequest.cleanUp();
}
}
@Override
public Object getThreadHash() {
Request request = getRequest();
if (request != null && request instanceof SIPRequest) {
return ((SIPRequest)request).getCallIdHeader().getCallId();
} else {
return null;
}
}
};
if(time > 0) {
sipStack.getTimer().schedule(task, time * T1 * baseTimerInterval);
} else {
task.runTask();
}
}
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#equals(java.lang.Object)
*/
@Override
public boolean equals(Object other) {
if(other == null) return false;
if (!other.getClass().equals(this.getClass())) {
return false;
}
SIPServerTransaction sst = (SIPServerTransaction) other;
return this.getBranch().equalsIgnoreCase(sst.getBranch());
}
/*
* (non-Javadoc)
*
* @see android.gov.nist.javax.sip.stack.SIPTransaction#getDialog()
*/
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getDialog()
*/
@Override
public Dialog getDialog() {
if(dialog == null && dialogId != null) {
return sipStack.getDialog(dialogId);
}
return dialog;
}
/*
* (non-Javadoc)
*
* @see android.gov.nist.javax.sip.stack.SIPTransaction#setDialog(android.gov.nist.javax.sip.stack.SIPDialog,
* android.gov.nist.javax.sip.message.SIPMessage)
*/
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setDialog(android.gov.nist.javax.sip.stack.SIPDialog, java.lang.String)
*/
@Override
public void setDialog(SIPDialog sipDialog, String dialogId) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("setDialog " + this + " dialog = " + sipDialog);
this.dialog = sipDialog;
this.dialogId = dialogId;
if (dialogId != null)
sipDialog.setAssigned();
if (this.retransmissionAlertEnabled && this.retransmissionAlertTimerTask != null) {
sipStack.getTimer().cancel(retransmissionAlertTimerTask);
if (this.retransmissionAlertTimerTask.dialogId != null) {
sipStack.retransmissionAlertTransactions
.remove(this.retransmissionAlertTimerTask.dialogId);
}
this.retransmissionAlertTimerTask = null;
}
this.retransmissionAlertEnabled = false;
}
/*
* (non-Javadoc)
*
* @see android.javax.sip.Transaction#terminate()
*/
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#terminate()
*/
@Override
public void terminate() throws ObjectInUseException {
this.setState(TransactionState._TERMINATED);
if (this.retransmissionAlertTimerTask != null) {
sipStack.getTimer().cancel(retransmissionAlertTimerTask);
if (retransmissionAlertTimerTask.dialogId != null) {
this.sipStack.retransmissionAlertTransactions
.remove(retransmissionAlertTimerTask.dialogId);
}
this.retransmissionAlertTimerTask = null;
}
if(!transactionTimerStarted.get()) {
// if no transaction timer was started just remove the tx without firing a transaction terminated event
testAndSetTransactionTerminatedEvent();
sipStack.removeTransaction(this);
}
}
@Override
public void sendReliableProvisionalResponse(Response relResponse) throws SipException {
/*
* After the first reliable provisional response for a request has been acknowledged, the
* UAS MAY send additional reliable provisional responses. The UAS MUST NOT send a second
* reliable provisional response until the first is acknowledged.
*/
if (this.pendingReliableResponseAsBytes != null) {
throw new SipException("Unacknowledged response");
} else {
SIPResponse reliableResponse = (SIPResponse) relResponse;
this.pendingReliableResponseAsBytes = reliableResponse.encodeAsBytes(this.getTransport());
this.pendingReliableResponseMethod = reliableResponse.getCSeq().getMethod();
this.pendingReliableCSeqNumber = reliableResponse.getCSeq().getSeqNumber();
}
/*
* In addition, it MUST contain a Require header field containing the option tag 100rel,
* and MUST include an RSeq header field.
*/
RSeq rseq = (RSeq) relResponse.getHeader(RSeqHeader.NAME);
if (relResponse.getHeader(RSeqHeader.NAME) == null) {
rseq = new RSeq();
relResponse.setHeader(rseq);
}
try {
if(rseqNumber < 0) {
this.rseqNumber = (int) (Math.random() * 1000);
}
this.rseqNumber++;
rseq.setSeqNumber(this.rseqNumber);
this.pendingReliableRSeqNumber = rseq.getSeqNumber();
// start the timer task which will retransmit the reliable response
// until the PRACK is received. Cannot send a second provisional.
this.lastResponse = (SIPResponse) relResponse;
if ( this.getDialog() != null && interlockProvisionalResponses ) {
boolean acquired = this.provisionalResponseSem.tryAcquire(1, TimeUnit.SECONDS);
if (!acquired) {
throw new SipException("Unacknowledged reliable response");
}
}
//moved the task scheduling before the sending of the message to overcome
// Issue 265 : https://jain-sip.dev.java.net/issues/show_bug.cgi?id=265
this.provisionalResponseTask = new ProvisionalResponseTask();
this.sipStack.getTimer().scheduleWithFixedDelay(provisionalResponseTask, 0,
SIPTransactionStack.BASE_TIMER_INTERVAL);
this.sendMessage((SIPMessage) relResponse);
} catch (Exception ex) {
InternalErrorHandler.handleException(ex);
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getReliableProvisionalResponse()
*/
@Override
public byte[] getReliableProvisionalResponse() {
return this.pendingReliableResponseAsBytes;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#prackRecieved()
*/
@Override
public boolean prackRecieved() {
if (this.pendingReliableResponseAsBytes == null)
return false;
if(provisionalResponseTask != null) {
sipStack.getTimer().cancel(provisionalResponseTask);
this.provisionalResponseTask = null;
}
this.pendingReliableResponseAsBytes = null;
if ( interlockProvisionalResponses && getDialog() != null ) {
this.provisionalResponseSem.release();
}
return true;
}
/*
* (non-Javadoc)
*
* @see android.javax.sip.ServerTransaction#enableRetransmissionAlerts()
*/
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#enableRetransmissionAlerts()
*/
@Override
public void enableRetransmissionAlerts() throws SipException {
if (this.getDialog() != null)
throw new SipException("Dialog associated with tx");
else if (!isInviteTransaction())
throw new SipException("Request Method must be INVITE");
this.retransmissionAlertEnabled = true;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#isRetransmissionAlertEnabled()
*/
@Override
public boolean isRetransmissionAlertEnabled() {
return this.retransmissionAlertEnabled;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#disableRetransmissionAlerts()
*/
@Override
public void disableRetransmissionAlerts() {
if (this.retransmissionAlertTimerTask != null && this.retransmissionAlertEnabled) {
sipStack.getTimer().cancel(retransmissionAlertTimerTask);
this.retransmissionAlertEnabled = false;
String dialogId = this.retransmissionAlertTimerTask.dialogId;
if (dialogId != null) {
sipStack.retransmissionAlertTransactions.remove(dialogId);
}
this.retransmissionAlertTimerTask = null;
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setAckSeen()
*/
@Override
public void setAckSeen() {
this.isAckSeen = true;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#ackSeen()
*/
@Override
public boolean ackSeen() {
return this.isAckSeen;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setMapped(boolean)
*/
@Override
public void setMapped(boolean b) {
this.isMapped = true;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#setPendingSubscribe(android.gov.nist.javax.sip.stack.SIPClientTransaction)
*/
@Override
public void setPendingSubscribe(SIPClientTransaction pendingSubscribeClientTx) {
this.pendingSubscribeTransaction = pendingSubscribeClientTx;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#releaseSem()
*/
@Override
public void releaseSem() {
if (this.pendingSubscribeTransaction != null) {
/*
* When a notify is being processed we take a lock on the subscribe to avoid racing
* with the OK of the subscribe.
*/
if (! sipStack.isDeliverUnsolicitedNotify()) {
pendingSubscribeTransaction.releaseSem();
}
} else if (this.inviteTransaction != null && this.getMethod().equals(Request.CANCEL)) {
/*
* When a CANCEL is being processed we take a nested lock on the associated INVITE
* server tx.
*/
this.inviteTransaction.releaseSem();
}
super.releaseSem();
}
/**
* The INVITE Server Transaction corresponding to a CANCEL Server Transaction.
*
* @param st -- the invite server tx corresponding to the cancel server transaction.
*/
@Override
public void setInviteTransaction(SIPServerTransaction st) {
this.inviteTransaction = st;
}
/**
* TODO -- this method has to be added to the api.
*
* @return
*/
@Override
public SIPServerTransaction getCanceledInviteTransaction() {
return this.inviteTransaction;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#scheduleAckRemoval()
*/
@Override
public void scheduleAckRemoval() throws IllegalStateException {
if (this.getMethod() == null || !this.getMethod().equals(Request.ACK)) {
throw new IllegalStateException("Method is null[" + (getMethod() == null)
+ "] or method is not ACK[" + this.getMethod() + "]");
}
this.startTransactionTimer();
}
// jeand cleanup the state of the stx to help GC
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#cleanUp()
*/
@Override
public void cleanUp() {
// Remove it from the set
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug("removing" + this);
if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) {
// release the connection associated with this transaction.
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("cleanup : "
+ getTransactionId());
}
// we keep the request in a byte array to be able to recreate it
// no matter what to keep API backward compatibility
if(originalRequest == null && originalRequestBytes != null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) {
try {
originalRequest = (SIPRequest) sipStack.getMessageParserFactory().createMessageParser(sipStack).parseSIPMessage(originalRequestBytes, true, false, null);
// originalRequestBytes = null;
} catch (ParseException e) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("message " + originalRequestBytes + "could not be reparsed !", e);
}
}
} else if (originalRequest != null && originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) {
originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport());
}
// http://java.net/jira/browse/JSIP-429
// store the merge id from the tx to avoid reparsing of request on aggressive cleanup
if (originalRequest != null && originalRequestBytes == null) {
super.mergeId = ((SIPRequest)originalRequest).getMergeId();
}
sipStack.removeTransaction(this);
cleanUpOnTimer();
// commented out because the application can hold on a ref to the tx
// after it has been removed from the stack
// and want to get the request or branch from it
// originalRequestBytes = null;
// originalRequestBranch = null;
originalRequestFromTag = null;
originalRequestSentBy = null;
// it should be available in the processTxTerminatedEvent, so we can nullify it only here
if(originalRequest != null) {
// originalRequestSentBy = originalRequest.getTopmostVia().getSentBy();
// originalRequestFromTag = originalRequest.getFromTag();
originalRequest = null;
}
if(!isReliable() && inviteTransaction != null) {
inviteTransaction = null;
}
// Application Data has to be cleared by the application
// applicationData = null;
lastResponse = null;
// Issue 318 : (https://jain-sip.dev.java.net/issues/show_bug.cgi?id=318)
// Re-transmission of 200 to INVITE terminates prematurely :
// don't nullify since the transaction may be terminated
// but the ack not received so the 200 retransmissions should continue
// lastResponseAsBytes = null;
// don't clean up because on sending 200 OK to CANCEL otherwise we try to start the transaction timer
// but due to timer J it has already been cleaned up
// transactionTimerStarted = null;
} else {
sipStack.removeTransaction(this);
}
// Uncache the server tx
if ((!sipStack.cacheServerConnections) && isReliable()
&& --getMessageChannel().useCount <= 0) {
// Close the encapsulated socket if stack is configured
close();
} else {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)
&& (!sipStack.cacheServerConnections)
&& isReliable()) {
int useCount = getMessageChannel().useCount;
logger.logDebug("Use Count = " + useCount);
}
}
}
// clean up the state of the stx when it goes to completed or terminated to help GC
protected void cleanUpOnTimer() {
if(getReleaseReferencesStrategy() != ReleaseReferencesStrategy.None) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("cleanup on timer : "
+ getTransactionId());
}
if(dialog != null && getMethod().equals(Request.CANCEL)) {
// used to deal with getting the dialog on cancel tx after the 200 OK to CANCEL has been sent
dialogId = dialog.getDialogId();
}
dialog = null;
// we don't nullify the inviteTx for CANCEL since the app can get it from getCanceledInviteTransaction
if(inviteTransaction != null && !getMethod().equals(Request.CANCEL)) {
// we release the semaphore for Cancel processing
inviteTransaction.releaseSem();
inviteTransaction = null;
}
if(originalRequest != null) {
// http://java.net/jira/browse/JSIP-429
// store the merge id from the tx to avoid reparsing of request on aggressive cleanup
super.mergeId = ((SIPRequest)originalRequest).getMergeId();
originalRequest.setTransaction(null);
originalRequest.setInviteTransaction(null);
if(!getMethod().equalsIgnoreCase(Request.INVITE)) {
if(originalRequestSentBy == null) {
originalRequestSentBy = originalRequest.getTopmostVia().getSentBy();
}
if(originalRequestFromTag == null) {
originalRequestFromTag = originalRequest.getFromTag();
}
}
// we keep the request in a byte array to be able to recreate it
// no matter what to keep API backward compatibility
if(originalRequestBytes == null && getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) {
originalRequestBytes = originalRequest.encodeAsBytes(this.getTransport());
}
if(!getMethod().equalsIgnoreCase(Request.INVITE) && !getMethod().equalsIgnoreCase(Request.CANCEL)) {
originalRequest = null;
}
}
if(lastResponse != null) {
if(getReleaseReferencesStrategy() == ReleaseReferencesStrategy.Normal) {
lastResponseAsBytes = lastResponse.encodeAsBytes(this.getTransport());
}
lastResponse = null;
}
pendingReliableResponseAsBytes = null;
pendingReliableResponseMethod = null;
pendingSubscribeTransaction = null;
provisionalResponseSem = null;
retransmissionAlertTimerTask = null;
requestOf = null;
}
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableResponseMethod()
*/
@Override
public String getPendingReliableResponseMethod() {
return pendingReliableResponseMethod;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableCSeqNumber()
*/
@Override
public long getPendingReliableCSeqNumber() {
return pendingReliableCSeqNumber;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#getPendingReliableRSeqNumber()
*/
@Override
public long getPendingReliableRSeqNumber() {
return pendingReliableRSeqNumber;
}
/**
* @see android.gov.nist.javax.sip.stack.SIPServerTransaction#waitForTermination()
*/
@Override
public void waitForTermination() {
try {
this.terminationSemaphore.acquire();
} catch (InterruptedException e) {
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy