* 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) {
Response notExist = sipRequest
.createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
try {
sipProvider.sendResponse(notExist);
} catch (SipException e) {
sipStack.getLogWriter().logError("error sending response", e);
}
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
}
return;
}
} else if (sipRequest.getMethod().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 (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug("Processing ACK for INVITE Tx ");
} else {
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug("Processing ACK for dialog " + dialog);
if (dialog == null) {
if (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().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
*
*/
} else {
if (!dialog.handleAck(transaction)) {
if(sipStack.isLooseDialogValidation()) {
if (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().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();
}
} else {
return;
}
} else {
transaction.passToListener();
dialog.addTransaction(transaction);
dialog.addRoute(sipRequest);
transaction.setDialog(dialog, dialogId);
if (sipStack.isDialogCreated(sipRequest.getMethod())) {
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.deliverTerminatedEventForAck) {
try {
sipStack.addTransaction(transaction);
transaction.scheduleAckRemoval();
} catch (IOException ex) {
}
} else {
transaction.setMapped(true);
}
/*
* try { sipStack.addTransaction(transaction); } catch (IOException ex) { //
* should never happen. }
*/
}
}
}
} else if (sipRequest.getMethod().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 (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug("Processing PRACK for dialog " + dialog);
if (dialog == null && sipProvider.isAutomaticDialogSupportEnabled()) {
if (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
"Dialog does not exist " + sipRequest.getFirstLine()
+ " isServerTransaction = " + true);
}
if (sipStack.isLoggingEnabled()) {
sipStack
.getLogWriter()
.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) {
sipStack.getLogWriter().logError("error sending response", e);
}
if (transaction != null) {
sipStack.removeTransaction(transaction);
transaction.releaseSem();
}
return;
} else if (dialog != null) {
if (!dialog.handlePrack(sipRequest)) {
sipStack.getLogWriter().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 {
sipStack.getLogWriter().logDebug(
"Processing PRACK without a DIALOG -- this must be a proxy element");
}
} else if (sipRequest.getMethod().equals(Request.BYE)) {
// Check for correct sequence numbering of the BYE
if (dialog != null && !dialog.isRequestConsumable(sipRequest)) {
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug(
"Dropping out of sequence BYE " + dialog.getRemoteSeqNumber() + " "
+ sipRequest.getCSeq().getSeqNumber());
if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq().getSeqNumber()
&& transaction.getState() == TransactionState.TRYING) {
this.send500Response(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");
sipStack.getLogWriter().logDebug(
"dropping request -- automatic dialog "
+ "support enabled and dialog does not exist!");
try {
transaction.sendResponse(response);
} catch (SipException ex) {
sipStack.getLogWriter().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 (sipStack.getLogWriter().isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
"BYE Tx = " + transaction + " isMapped ="
+ transaction.isTransactionMapped());
}
} else if (sipRequest.getMethod().equals(Request.CANCEL)) {
SIPServerTransaction st = (SIPServerTransaction) sipStack.findCancelTransaction(
sipRequest, true);
if (sipStack.getLogWriter().isLoggingEnabled()) {
sipStack.getLogWriter().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.getState() == SIPTransaction.TERMINATED_STATE) {
// If transaction already exists but it is
// too late to cancel the transaction then
// just respond OK to the CANCEL and bail.
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().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 (sipStack.isLoggingEnabled())
sipStack.getLogWriter().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 (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().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);
}
}
} else if (sipRequest.getMethod().equals(Request.INVITE)) {
SIPTransaction lastTransaction = dialog == null ? null : dialog
.getInviteTransaction();
/*
* RFC 3261 Chapter 14. A UAS that receives a second INVITE before it sends the final
* response to a first INVITE with a lower CSeq sequence number on the same dialog
* MUST return a 500 (Server Internal Error) response to the second INVITE and MUST
* include a Retry-After header field with a randomly chosen value of between 0 and 10
* seconds.
*/
if (dialog != null && transaction != null && lastTransaction != null
&& sipRequest.getCSeq().getSeqNumber() > dialog.getRemoteSeqNumber()
&& lastTransaction instanceof SIPServerTransaction
&& lastTransaction.isInviteTransaction()
&& lastTransaction.getState() != TransactionState.COMPLETED
&& lastTransaction.getState() != TransactionState.TERMINATED
&& lastTransaction.getState() != TransactionState.CONFIRMED) {
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug(
"Sending 500 response for out of sequence message");
SIPResponse sipResponse = sipRequest
.createResponse(Response.SERVER_INTERNAL_ERROR);
RetryAfter retryAfter = new RetryAfter();
try {
retryAfter.setRetryAfter((int) (10 * Math.random()));
} catch (InvalidArgumentException ex) {
ex.printStackTrace();
}
sipResponse.addHeader(retryAfter);
try {
transaction.sendMessage(sipResponse);
} catch (IOException ex) {
transaction.raiseIOExceptionEvent();
}
return;
}
/*
* RFC 3261 Chapter 14. A UAS that receives an INVITE on a dialog while an INVITE it
* had sent on that dialog is in progress MUST return a 491 (Request Pending) response
* to the received INVITE.
*/
lastTransaction = (dialog == null ? null : dialog.getLastTransaction());
if (dialog != null && lastTransaction != null
&& lastTransaction.isInviteTransaction()
&& lastTransaction instanceof SIPClientTransaction
&& lastTransaction.getState() != TransactionState.COMPLETED
&& lastTransaction.getState() != TransactionState.TERMINATED) {
if (dialog.getRemoteSeqNumber() + 1 == sipRequest.getCSeq().getSeqNumber()) {
dialog.setRemoteSequenceNumber(sipRequest.getCSeq().getSeqNumber());
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug(
"Sending 491 response for out of sequence message");
SIPResponse sipResponse = sipRequest.createResponse(Response.REQUEST_PENDING);
try {
transaction.sendMessage(sipResponse);
} catch (IOException ex) {
transaction.raiseIOExceptionEvent();
}
dialog.requestConsumed();
} else {
if (sipStack.isLoggingEnabled())
sipStack.getLogWriter().logDebug(
"Dropping message -- sequence number is too high!");
}
return;
}
}
// Sequence numbers are supposed to be incremented
// sequentially within a dialog for RFC 3261
// Note BYE, CANCEL and ACK is handled above - so no check here.
sipStack.getLogWriter().logDebug(
"CHECK FOR OUT OF SEQ MESSAGE " + dialog + " transaction " + transaction);
if (dialog != null && transaction != null && !sipRequest.getMethod().equals(Request.BYE)
&& !sipRequest.getMethod().equals(Request.CANCEL)
&& !sipRequest.getMethod().equals(Request.ACK)
&& !sipRequest.getMethod().equals(Request.PRACK)) {
if (!dialog.isRequestConsumable(sipRequest)) {
/*
* RFC 3261: "UAS Behavior" section (12.2.2): If the remote sequence number was
* not empty, but the sequence number of the request is lower than the remote
* sequence number, the request is out of order and MUST be rejected with a 500
* (Server Internal Error) response.
*/
// Drop the request
if (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
"Dropping out of sequence message " + dialog.getRemoteSeqNumber()
+ " " + sipRequest.getCSeq());
}
// send error when stricly higher, ignore when ==
// (likely still processing, error would interrupt that)
if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq().getSeqNumber()
&& (transaction.getState() == TransactionState.TRYING || transaction
.getState() == TransactionState.PROCEEDING)) {
this.send500Response(sipRequest, transaction);
}
return;
}
try {
if (sipProvider == dialog.getSipProvider()) {
sipStack.addTransaction(transaction);
// This will set the remote sequence number.
dialog.addTransaction(transaction);
dialog.addRoute(sipRequest);
transaction.setDialog(dialog, dialogId);
}
} catch (IOException ex) {
transaction.raiseIOExceptionEvent();
sipStack.removeTransaction(transaction);
return;
}
}
RequestEvent sipEvent;
if (sipStack.getLogWriter().isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
sipRequest.getMethod() + " transaction.isMapped = "
+ transaction.isTransactionMapped());
}
/*
* RFC 3265: Each event package MUST specify whether forked SUBSCRIBE requests are allowed
* to install multiple subscriptions. If such behavior is not allowed, the first potential
* dialog- establishing message will create a dialog. All subsequent NOTIFY messages which
* correspond to the SUBSCRIBE message (i.e., match "To", "From", "From" header "tag"
* parameter, "Call-ID", "CSeq", "Event", and "Event" header "id" parameter) but which do
* not match the dialog would be rejected with a 481 response. Note that the 200-class
* response to the SUBSCRIBE can arrive after a matching NOTIFY has been received; such
* responses might not correlate to the same dialog established by the NOTIFY. Except as
* required to complete the SUBSCRIBE transaction, such non-matching 200-class responses
* are ignored.
*/
if (dialog == null && sipRequest.getMethod().equals(Request.NOTIFY)) {
SIPClientTransaction pendingSubscribeClientTx = sipStack.findSubscribeTransaction(
sipRequest, listeningPoint);
if (sipStack.getLogWriter().isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
"PROCESSING NOTIFY DIALOG == null " + pendingSubscribeClientTx);
}
/*
* RFC 3265: Upon receiving a NOTIFY request, the subscriber should check that it
* matches at least one of its outstanding subscriptions; if not, it MUST return a
* "481 Subscription does not exist" response unless another 400- or 500-class
* response is more appropriate.
*/
if (sipProvider.isAutomaticDialogSupportEnabled() && pendingSubscribeClientTx == null
&& !sipStack.deliverUnsolicitedNotify) {
/*
* This is the case of the UAC receiving a Stray NOTIFY for which it has not
* previously sent out a SUBSCRIBE and for which it does not have an established
* dialog.
*/
try {
if (sipStack.isLoggingEnabled()) {
sipStack.getLogWriter().logDebug(
"Could not find Subscription for Notify Tx.");
}
Response errorResponse = sipRequest
.createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
errorResponse.setReasonPhrase("Subscription does not exist");
sipProvider.sendResponse(errorResponse);
return;
} catch (Exception ex) {
sipStack.getLogWriter().logError(