if (sipStack == null)
InternalErrorHandler.handleException("Egads! no sip stack!");
// Look for the registered SIPListener for the message channel.
SIPServerTransaction transaction = (SIPServerTransaction) this.transactionChannel;
if (transaction != null) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
logger.logDebug(
"transaction state = " + transaction.getState());
}
final String dialogId = sipRequest.getDialogId(true);
SIPDialog dialog = sipStack.getDialog(dialogId);
/*
* Check if we got this request on the contact address of the dialog If
* not the dialog does not belong to this request. We check this
* condition if a contact address has been assigned to the dialog.
* Forgive the sins of B2BUA's that like to record route ACK's
*/
if (dialog != null && sipProvider != dialog.getSipProvider()) {
final Contact contact = dialog.getMyContactHeader();
if (contact != null) {
SipUri contactUri = (SipUri) (contact.getAddress().getURI());
String ipAddress = contactUri.getHost();
int contactPort = contactUri.getPort();
String contactTransport = contactUri.getTransportParam();
if (contactTransport == null)
contactTransport = "udp";
if (contactPort == -1) {
if (contactTransport.equals("udp")
|| contactTransport.equals("tcp"))
contactPort = 5060;
else
contactPort = 5061;
}
// Check if the dialog contact is the same as the provider on
// which we got the request. Otherwise, dont assign this
// dialog to the request.
if (ipAddress != null
&& (!ipAddress.equals(listeningPoint.getIPAddress()) || contactPort != listeningPoint
.getPort())) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"nulling dialog -- listening point mismatch! "
+ contactPort + " lp port = "
+ listeningPoint.getPort());
}
dialog = null;
}
}
}
/*
* RFC 3261 8.2.2.2 Merged requests: If the request has no tag in the To
* header field, the UAS core MUST check the request against ongoing
* transactions. If the From tag, Call-ID, and CSeq exactly match those
* associated with an ongoing transaction, but the request does not
* match that transaction (based on the matching rules in Section
* 17.2.3), the UAS core SHOULD generate a 482 (Loop Detected) response
* and pass it to the server transaction. This support is only enabled
* when the stack has been instructed to function with Automatic Dialog
* Support.
*/
if (sipProvider.isDialogErrorsAutomaticallyHandled()
&& sipRequest.getToTag() == null) {
if (sipStack.findMergedTransaction(sipRequest)) {
this.sendLoopDetectedResponse(sipRequest, transaction);
return;
}
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug("dialogId = " + dialogId);
logger.logDebug("dialog = " + dialog);
}
/*
* RFC 3261 Section 16.4 If the first value in the Route header field
* indicates this proxy,the proxy MUST remove that value from the
* request .
*/
// If the message is being processed
// by a Proxy, then the proxy will take care of stripping the
// Route header. If the request is being processed by an
// endpoint, then the stack strips off the route header.
if (sipRequest.getHeader(Route.NAME) != null
&& transaction.getDialog() != null) {
final RouteList routes = sipRequest.getRouteHeaders();
final Route route = (Route) routes.getFirst();
final SipUri uri = (SipUri) route.getAddress().getURI();
final HostPort hostPort = uri.getHostPort();
int port;
if (hostPort.hasPort()) {
port = hostPort.getPort();
} else {
if (listeningPoint.getTransport().equalsIgnoreCase(
ListeningPoint.TLS))
port = 5061;
else
port = 5060;
}
String host = hostPort.getHost().encode();
if ((host.equals(listeningPoint.getIPAddress()) || host
.equalsIgnoreCase(listeningPoint.getSentBy()))
&& port == listeningPoint.getPort()) {
if (routes.size() == 1)
sipRequest.removeHeader(Route.NAME);
else
routes.removeFirst();
}
}
final String sipRequestMethod = sipRequest.getMethod();
if (sipRequestMethod.equals(Request.REFER) && dialog != null
&& sipProvider.isDialogErrorsAutomaticallyHandled()) {
/*
* An agent responding to a REFER method MUST return a 400 (Bad
* Request) if the request contained zero or more than one Refer-To
* header field values.
*/
ReferToHeader sipHeader = (ReferToHeader) sipRequest
.getHeader(ReferTo.NAME);
if (sipHeader == null) {
this.sendBadRequestResponse(sipRequest, transaction,
"Refer-To header is missing");
return;
}
/*
* A refer cannot be processed until previous transaction has been
* completed.
*/
SIPTransaction lastTransaction = ((SIPDialog) dialog)
.getLastTransaction();
if (lastTransaction != null
&& sipProvider.isDialogErrorsAutomaticallyHandled()) {
final String lastTransactionMethod = lastTransaction.getMethod();
if (lastTransaction instanceof SIPServerTransaction) {
// Handle Pseudo State Trying on Server Transaction
if ((lastTransaction.getInternalState() == TransactionState._PROCEEDING
|| lastTransaction.getInternalState() == TransactionState._TRYING)
&& lastTransactionMethod.equals(Request.INVITE)) {
this
.sendRequestPendingResponse(sipRequest,
transaction);
return;
}
} else if (lastTransaction instanceof SIPClientTransaction) {
if (lastTransactionMethod.equals(Request.INVITE)
&& lastTransaction.getInternalState() != TransactionState._TERMINATED
&& lastTransaction.getInternalState() != TransactionState._COMPLETED) {
this
.sendRequestPendingResponse(sipRequest,
transaction);
return;
}
}
}
} else if (sipRequestMethod.equals(Request.UPDATE)) {
/*
* Got an UPDATE method and the user dialog does not exist and the
* user wants to be a User agent.
*/
if (sipProvider.isAutomaticDialogSupportEnabled() && dialog == null) {
this.sendCallOrTransactionDoesNotExistResponse(sipRequest,
transaction);
return;
}
} else if (sipRequestMethod.equals(Request.ACK)) {
if (transaction != null && transaction.isInviteTransaction()) {
// This is an ack for a 3xx-6xx response. Just let the tx laer
// take care of it.
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Processing ACK for INVITE Tx ");
} else {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Processing ACK for dialog " + dialog);
if (dialog == null) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"Dialog does not exist "
+ sipRequest.getFirstLine()
+ " isServerTransaction = " + true);
}
SIPServerTransaction st = sipStack
.getRetransmissionAlertTransaction(dialogId);
if (st != null && st.isRetransmissionAlertEnabled()) {
st.disableRetransmissionAlerts();
}
/*
* JvB: must never drop ACKs that dont match a transaction!
* One cannot be sure if it isn't an ACK for a 2xx response
*/
SIPServerTransaction ackTransaction = sipStack
.findTransactionPendingAck(sipRequest);
/*
* Found a transaction ( that we generated ) which is
* waiting for ACK. So ACK it and return.
*/
if (ackTransaction != null) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Found Tx pending ACK");
try {
ackTransaction.setAckSeen();
sipStack.removeTransaction(ackTransaction);
sipStack
.removeTransactionPendingAck(ackTransaction);
} catch (Exception ex) {
if (logger.isLoggingEnabled()) {
logger.logError(
"Problem terminating transaction", ex);
}
}
return;
}
} else {
if (!dialog.handleAck(transaction)) {
if (!dialog.isSequnceNumberValidation()) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"Dialog exists with loose dialog validation "
+ sipRequest.getFirstLine()
+ " isServerTransaction = "
+ true + " dialog = "
+ dialog.getDialogId());
}
SIPServerTransaction st = sipStack
.getRetransmissionAlertTransaction(dialogId);
if (st != null && st.isRetransmissionAlertEnabled()) {
st.disableRetransmissionAlerts();
}
// Issue 319 : https://jain-sip.dev.java.net/issues/show_bug.cgi?id=319
// remove the pending ack to stop the transaction timer for transaction
// where the stack replied with a final error response.
SIPServerTransaction ackTransaction = sipStack
.findTransactionPendingAck(sipRequest);
/*
* Found a transaction ( that we generated ) which is
* waiting for ACK. So ACK it and return.
*/
if (ackTransaction != null) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Found Tx pending ACK");
try {
ackTransaction.setAckSeen();
sipStack.removeTransaction(ackTransaction);
sipStack
.removeTransactionPendingAck(ackTransaction);
} catch (Exception ex) {
if (logger.isLoggingEnabled()) {
logger.logError(
"Problem terminating transaction", ex);
}
}
}
} else {
if (logger
.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger
.logDebug(
"Dropping ACK - cannot find a transaction or dialog");
}
SIPServerTransaction ackTransaction = sipStack
.findTransactionPendingAck(sipRequest);
if (ackTransaction != null) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Found Tx pending ACK");
try {
ackTransaction.setAckSeen();
sipStack.removeTransaction(ackTransaction);
sipStack
.removeTransactionPendingAck(ackTransaction);
} catch (Exception ex) {
if (logger.isLoggingEnabled()) {
logger
.logError(
"Problem terminating transaction",
ex);
}
}
}
/*
* For test only we support a flag that will deliver
* retransmitted ACK for 200 OK responses to the
* listener.
*/
if ((!sipStack
.isDeliverRetransmittedAckToListener())
|| (ackTransaction != null && !sipStack
.isNon2XXAckPassedToListener())) {
return;
}
}
} else {
dialog.addTransaction(transaction);
transaction.passToListener();
dialog.addRoute(sipRequest);
transaction.setDialog(dialog, dialogId);
if (sipRequest.getMethod().equals(Request.INVITE)
&& sipProvider
.isDialogErrorsAutomaticallyHandled()) {
sipStack.putInMergeTable(transaction, sipRequest);
}
/*
* Note that ACK is a pseudo transaction. It is never
* added to the stack and you do not get transaction
* terminated events on ACK.
*/
if (sipStack.isDeliverTerminatedEventForAck()) {
try {
sipStack.addTransaction(transaction);
transaction.scheduleAckRemoval();
} catch (IOException ex) {
}
} else {
transaction.setMapped(true);
}
}
}
}
} else if (sipRequestMethod.equals(Request.PRACK)) {
/*
* RFC 3262: A matching PRACK is defined as one within the same
* dialog as the response, and whose method, CSeq-num, and
* response-num in the RAck header field match, respectively, the
* method from the CSeq, the sequence number from the CSeq, and the
* sequence number from the RSeq of the reliable provisional
* response.
*/
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Processing PRACK for dialog " + dialog);
if (dialog == null && sipProvider.isAutomaticDialogSupportEnabled()) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"Dialog does not exist "
+ sipRequest.getFirstLine()
+ " isServerTransaction = " + true);
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger
.logDebug(
"Sending 481 for PRACK - automatic dialog support is enabled -- cant find dialog!");
}
SIPResponse notExist = sipRequest
.createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
try {
sipProvider.sendResponse(notExist);
} catch (SipException e) {
logger.logError(
"error sending response", e);
}
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
}
return;
} else if (dialog != null) {
if (!dialog.handlePrack(sipRequest)) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Dropping out of sequence PRACK ");
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
}
return;
} else {
try {
sipStack.addTransaction(transaction);
dialog.addTransaction(transaction);
dialog.addRoute(sipRequest);
transaction.setDialog(dialog, dialogId);
} catch (Exception ex) {
InternalErrorHandler.handleException(ex);
}
}
} else {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger
.logDebug(
"Processing PRACK without a DIALOG -- this must be a proxy element");
}
} else if (sipRequestMethod.equals(Request.BYE)) {
// Check for correct sequence numbering of the BYE
if (dialog != null && !dialog.isRequestConsumable(sipRequest)) {
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Dropping out of sequence BYE "
+ dialog.getRemoteSeqNumber() + " "
+ sipRequest.getCSeq().getSeqNumber());
if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq()
.getSeqNumber()
&& transaction.getInternalState() == TransactionState._TRYING) {
this.sendServerInternalErrorResponse(sipRequest,
transaction);
}
// If the stack knows about the tx, then remove it.
if (transaction != null)
sipStack.removeTransaction(transaction);
return;
} else if (dialog == null
&& sipProvider.isAutomaticDialogSupportEnabled()) {
// Drop bye's with 481 if dialog does not exist.
// If dialog support is enabled then
// there must be a dialog associated with the bye
// No dialog could be found and requests on this
// provider. Must act like a user agent -- so drop the request.
// NOTE: if Automatic dialog support is not enabled,
// then it is the application's responsibility to
// take care of this error condition possibly.
SIPResponse response = sipRequest
.createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
response.setReasonPhrase("Dialog Not Found");
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger
.logDebug(
"dropping request -- automatic dialog "
+ "support enabled and dialog does not exist!");
try {
transaction.sendResponse(response);
} catch (SipException ex) {
logger.logError(
"Error in sending response", ex);
}
// If the stack knows about the tx, then remove it.
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
transaction = null;
}
return;
}
// note that the transaction may be null (which
// happens when no dialog for the bye was found.
// and automatic dialog support is disabled (i.e. the app wants
// to manage its own dialog layer.
if (transaction != null && dialog != null) {
try {
if (sipProvider == dialog.getSipProvider()) {
sipStack.addTransaction(transaction);
dialog.addTransaction(transaction);
transaction.setDialog(dialog, dialogId);
}
} catch (IOException ex) {
InternalErrorHandler.handleException(ex);
}
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"BYE Tx = " + transaction + " isMapped ="
+ transaction.isTransactionMapped());
}
} else if (sipRequestMethod.equals(Request.CANCEL)) {
SIPServerTransaction st = (SIPServerTransaction) sipStack
.findCancelTransaction(sipRequest, true);
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"Got a CANCEL, InviteServerTx = " + st
+ " cancel Server Tx ID = " + transaction
+ " isMapped = "
+ transaction.isTransactionMapped());
}
// Processing incoming CANCEL.
// Check if we can process the CANCEL request.
if (sipRequest.getMethod().equals(Request.CANCEL)) {
// If the CANCEL comes in too late, there's not
// much that the Listener can do so just do the
// default action and avoid bothering the listener.
if (st != null
&& st.getInternalState() == TransactionState._TERMINATED) {
// If transaction already exists but it is
// too late to cancel the transaction then
// just respond OK to the CANCEL and bail.
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Too late to cancel Transaction");
// send OK and just ignore the CANCEL.
try {
transaction.sendResponse(sipRequest
.createResponse(Response.OK));
} catch (Exception ex) {
if (ex.getCause() != null
&& ex.getCause() instanceof IOException) {
st.raiseIOExceptionEvent();
}
}
return;
}
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
logger.logDebug(
"Cancel transaction = " + st);
}
if (transaction != null && st != null && st.getDialog() != null) {
// Found an invite tx corresponding to the CANCEL.
// Set up the client tx and pass up to listener.
transaction.setDialog((SIPDialog) st.getDialog(), dialogId);
dialog = (SIPDialog) st.getDialog();
} else if (st == null
&& sipProvider.isAutomaticDialogSupportEnabled()
&& transaction != null) {
// Could not find a invite tx corresponding to the CANCEL.
// Automatic dialog support is enabled so I must behave like
// an endpoint on this provider.
// Send the error response for the cancel.
SIPResponse response = sipRequest
.createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
logger.logDebug(
"dropping request -- automatic dialog support "
+ "enabled and INVITE ST does not exist!");
}
try {
sipProvider.sendResponse(response);
} catch (SipException ex) {
InternalErrorHandler.handleException(ex);
}
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
}
return;
}
// INVITE was handled statefully so the CANCEL must also be
// statefully handled.
if (st != null) {
try {
if (transaction != null) {
sipStack.addTransaction(transaction);
transaction.setPassToListener();
transaction.setInviteTransaction(st);
// Dont let the INVITE and CANCEL be concurrently
// processed.
st.acquireSem();
}
} catch (Exception ex) {
InternalErrorHandler.handleException(ex);