| /* |
| * 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 |
| * |
| * . |
| * |
| */ |
| /****************************************************************************** |
| * Product of NIST/ITL Advanced Networking Technologies Division (ANTD). * |
| ******************************************************************************/ |
| package gov.nist.javax.sip; |
| |
| import gov.nist.core.InternalErrorHandler; |
| import gov.nist.javax.sip.address.SipUri; |
| import gov.nist.javax.sip.header.Contact; |
| import gov.nist.javax.sip.header.Event; |
| import gov.nist.javax.sip.header.ReferTo; |
| import gov.nist.javax.sip.header.RetryAfter; |
| import gov.nist.javax.sip.header.Route; |
| import gov.nist.javax.sip.header.RouteList; |
| import gov.nist.javax.sip.header.Server; |
| import gov.nist.javax.sip.message.MessageFactoryImpl; |
| import gov.nist.javax.sip.message.SIPRequest; |
| import gov.nist.javax.sip.message.SIPResponse; |
| import gov.nist.javax.sip.stack.MessageChannel; |
| import gov.nist.javax.sip.stack.SIPClientTransaction; |
| import gov.nist.javax.sip.stack.SIPDialog; |
| import gov.nist.javax.sip.stack.SIPServerTransaction; |
| import gov.nist.javax.sip.stack.SIPTransaction; |
| import gov.nist.javax.sip.stack.ServerRequestInterface; |
| import gov.nist.javax.sip.stack.ServerResponseInterface; |
| |
| import java.io.IOException; |
| import java.util.TimerTask; |
| |
| import javax.sip.ClientTransaction; |
| import javax.sip.DialogState; |
| import javax.sip.InvalidArgumentException; |
| import javax.sip.ObjectInUseException; |
| import javax.sip.RequestEvent; |
| import javax.sip.ResponseEvent; |
| import javax.sip.ServerTransaction; |
| import javax.sip.SipException; |
| import javax.sip.SipProvider; |
| import javax.sip.TransactionState; |
| import javax.sip.header.CSeqHeader; |
| import javax.sip.header.EventHeader; |
| import javax.sip.header.ReferToHeader; |
| import javax.sip.header.ServerHeader; |
| import javax.sip.message.Request; |
| import javax.sip.message.Response; |
| |
| /* |
| * Bug fix Contributions by Lamine Brahimi, Andreas Bystrom, Bill Roome, John Martin, Daniel |
| * Machin Vasquez-Illa, Antonis Karydas, Joe Provino, Bruce Evangelder, Jeroen van Bemmel, Robert |
| * S. Rosen. |
| */ |
| /** |
| * An adapter class from the JAIN implementation objects to the NIST-SIP stack. The primary |
| * purpose of this class is to do early rejection of bad messages and deliver meaningful messages |
| * to the application. This class is essentially a Dialog filter. It is a helper for the UAC Core. |
| * It checks for and rejects requests and responses which may be filtered out because of sequence |
| * number, Dialog not found, etc. Note that this is not part of the JAIN-SIP spec (it does not |
| * implement a JAIN-SIP interface). This is part of the glue that ties together the NIST-SIP stack |
| * and event model with the JAIN-SIP stack. This is strictly an implementation class. |
| * |
| * @version 1.2 $Revision: 1.64 $ $Date: 2010/01/14 18:58:30 $ |
| * |
| * @author M. Ranganathan |
| */ |
| class DialogFilter implements ServerRequestInterface, ServerResponseInterface { |
| |
| protected SIPTransaction transactionChannel; |
| |
| protected ListeningPointImpl listeningPoint; |
| |
| private SipStackImpl sipStack; |
| |
| public DialogFilter(SipStackImpl sipStack) { |
| this.sipStack = sipStack; |
| |
| } |
| |
| /** |
| * Send back a Request Pending response. |
| * |
| * @param sipRequest |
| * @param transaction |
| */ |
| private void sendRequestPendingResponse(SIPRequest sipRequest, |
| SIPServerTransaction transaction) { |
| SIPResponse sipResponse = sipRequest.createResponse(Response.REQUEST_PENDING); |
| ServerHeader serverHeader = MessageFactoryImpl.getDefaultServerHeader(); |
| if (serverHeader != null) { |
| sipResponse.setHeader(serverHeader); |
| } |
| try { |
| RetryAfter retryAfter = new RetryAfter(); |
| retryAfter.setRetryAfter(1); |
| sipResponse.setHeader(retryAfter); |
| if (sipRequest.getMethod().equals(Request.INVITE)) { |
| sipStack.addTransactionPendingAck(transaction); |
| } |
| transaction.sendResponse(sipResponse); |
| transaction.releaseSem(); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Problem sending error response", ex); |
| transaction.releaseSem(); |
| sipStack.removeTransaction(transaction); |
| } |
| } |
| |
| /** |
| * Send a BAD REQUEST response. |
| * |
| * @param sipRequest |
| * @param transaction |
| * @param reasonPhrase |
| */ |
| |
| private void sendBadRequestResponse(SIPRequest sipRequest, SIPServerTransaction transaction, |
| String reasonPhrase) { |
| SIPResponse sipResponse = sipRequest.createResponse(Response.BAD_REQUEST); |
| if (reasonPhrase != null) |
| sipResponse.setReasonPhrase(reasonPhrase); |
| ServerHeader serverHeader = MessageFactoryImpl.getDefaultServerHeader(); |
| if (serverHeader != null) { |
| sipResponse.setHeader(serverHeader); |
| } |
| try { |
| if (sipRequest.getMethod().equals(Request.INVITE)) { |
| sipStack.addTransactionPendingAck(transaction); |
| } |
| transaction.sendResponse(sipResponse); |
| transaction.releaseSem(); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Problem sending error response", ex); |
| transaction.releaseSem(); |
| sipStack.removeTransaction(transaction); |
| |
| } |
| } |
| |
| /** |
| * Send a CALL OR TRANSACTION DOES NOT EXIST response. |
| * |
| * @param sipRequest |
| * @param transaction |
| */ |
| |
| private void sendCallOrTransactionDoesNotExistResponse(SIPRequest sipRequest, |
| SIPServerTransaction transaction) { |
| |
| SIPResponse sipResponse = sipRequest |
| .createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); |
| |
| ServerHeader serverHeader = MessageFactoryImpl.getDefaultServerHeader(); |
| if (serverHeader != null) { |
| sipResponse.setHeader(serverHeader); |
| } |
| try { |
| if (sipRequest.getMethod().equals(Request.INVITE)) { |
| sipStack.addTransactionPendingAck(transaction); |
| } |
| transaction.sendResponse(sipResponse); |
| transaction.releaseSem(); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Problem sending error response", ex); |
| transaction.releaseSem(); |
| sipStack.removeTransaction(transaction); |
| |
| } |
| |
| } |
| |
| /** |
| * Send back a LOOP Detected Response. |
| * |
| * @param sipRequest |
| * @param transaction |
| * |
| */ |
| private void sendLoopDetectedResponse(SIPRequest sipRequest, SIPServerTransaction transaction) { |
| SIPResponse sipResponse = sipRequest.createResponse(Response.LOOP_DETECTED); |
| |
| ServerHeader serverHeader = MessageFactoryImpl.getDefaultServerHeader(); |
| if (serverHeader != null) { |
| sipResponse.setHeader(serverHeader); |
| } |
| try { |
| sipStack.addTransactionPendingAck(transaction); |
| transaction.sendResponse(sipResponse); |
| transaction.releaseSem(); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Problem sending error response", ex); |
| transaction.releaseSem(); |
| sipStack.removeTransaction(transaction); |
| |
| } |
| |
| } |
| |
| /** |
| * Send back an error Response. |
| * |
| * @param sipRequest |
| * @param transaction |
| */ |
| |
| private void sendServerInternalErrorResponse(SIPRequest sipRequest, |
| SIPServerTransaction transaction) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger() |
| .logDebug("Sending 500 response for out of sequence message"); |
| SIPResponse sipResponse = sipRequest.createResponse(Response.SERVER_INTERNAL_ERROR); |
| sipResponse.setReasonPhrase("Request out of order"); |
| if (MessageFactoryImpl.getDefaultServerHeader() != null) { |
| ServerHeader serverHeader = MessageFactoryImpl.getDefaultServerHeader(); |
| sipResponse.setHeader(serverHeader); |
| } |
| |
| try { |
| RetryAfter retryAfter = new RetryAfter(); |
| retryAfter.setRetryAfter(10); |
| sipResponse.setHeader(retryAfter); |
| sipStack.addTransactionPendingAck(transaction); |
| transaction.sendResponse(sipResponse); |
| transaction.releaseSem(); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Problem sending response", ex); |
| transaction.releaseSem(); |
| sipStack.removeTransaction(transaction); |
| } |
| } |
| |
| /** |
| * Process a request. Check for various conditions in the dialog that can result in the |
| * message being dropped. Possibly return errors for these conditions. |
| * |
| * @exception SIPServerException is thrown when there is an error processing the request. |
| */ |
| public void processRequest(SIPRequest sipRequest, MessageChannel incomingMessageChannel) { |
| // Generate the wrapper JAIN-SIP object. |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "PROCESSING INCOMING REQUEST " + sipRequest + " transactionChannel = " |
| + transactionChannel + " listening point = " |
| + listeningPoint.getIPAddress() + ":" + listeningPoint.getPort()); |
| if (listeningPoint == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Dropping message: No listening point registered!"); |
| return; |
| } |
| |
| SipStackImpl sipStack = (SipStackImpl) transactionChannel.getSIPStack(); |
| |
| SipProviderImpl sipProvider = listeningPoint.getProvider(); |
| if (sipProvider == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("No provider - dropping !!"); |
| return; |
| } |
| |
| 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 (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "transaction state = " + transaction.getState()); |
| } |
| 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()) { |
| 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 (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().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.isAutomaticDialogSupportEnabled() |
| && sipProvider.isDialogErrorsAutomaticallyHandled() |
| && sipRequest.getToTag() == null) { |
| SIPServerTransaction sipServerTransaction = sipStack |
| .findMergedTransaction(sipRequest); |
| if (sipServerTransaction != null) { |
| this.sendLoopDetectedResponse(sipRequest, transaction); |
| return; |
| } |
| } |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("dialogId = " + dialogId); |
| sipStack.getStackLogger().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) { |
| RouteList routes = sipRequest.getRouteHeaders(); |
| Route route = (Route) routes.getFirst(); |
| SipUri uri = (SipUri) route.getAddress().getURI(); |
| int port; |
| if (uri.getHostPort().hasPort()) { |
| port = uri.getHostPort().getPort(); |
| } else { |
| if (listeningPoint.getTransport().equalsIgnoreCase("TLS")) |
| port = 5061; |
| else |
| port = 5060; |
| } |
| String host = uri.getHost(); |
| if ((host.equals(listeningPoint.getIPAddress()) || host |
| .equalsIgnoreCase(listeningPoint.getSentBy())) |
| && port == listeningPoint.getPort()) { |
| if (routes.size() == 1) |
| sipRequest.removeHeader(Route.NAME); |
| else |
| routes.removeFirst(); |
| } |
| } |
| |
| if (sipRequest.getMethod().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 we have either sent or received an ACK. |
| */ |
| SIPTransaction lastTransaction = ((SIPDialog) dialog).getLastTransaction(); |
| if (lastTransaction != null && sipProvider.isDialogErrorsAutomaticallyHandled()) { |
| SIPRequest lastRequest = (SIPRequest) lastTransaction.getRequest(); |
| if (lastTransaction instanceof SIPServerTransaction) { |
| if (!((SIPDialog) dialog).isAckSeen() |
| && lastRequest.getMethod().equals(Request.INVITE)) { |
| this.sendRequestPendingResponse(sipRequest, transaction); |
| return; |
| } |
| } else if (lastTransaction instanceof SIPClientTransaction) { |
| long cseqno = lastRequest.getCSeqHeader().getSeqNumber(); |
| String method = lastRequest.getMethod(); |
| if (method.equals(Request.INVITE) && !dialog.isAckSent(cseqno)) { |
| this.sendRequestPendingResponse(sipRequest, transaction); |
| return; |
| } |
| } |
| } |
| |
| } else if (sipRequest.getMethod().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 (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.getStackLogger().logDebug("Processing ACK for INVITE Tx "); |
| |
| } else { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Processing ACK for dialog " + dialog); |
| |
| if (dialog == null) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().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 (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Found Tx pending ACK"); |
| try { |
| ackTransaction.setAckSeen(); |
| sipStack.removeTransaction(ackTransaction); |
| sipStack.removeTransactionPendingAck(ackTransaction); |
| } catch (Exception ex) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError( |
| "Problem terminating transaction", ex); |
| } |
| } |
| return; |
| } |
| |
| } else { |
| if (!dialog.handleAck(transaction)) { |
| if (!dialog.isSequnceNumberValidation()) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().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 { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Dropping ACK - cannot find a transaction or dialog"); |
| } |
| SIPServerTransaction ackTransaction = sipStack |
| .findTransactionPendingAck(sipRequest); |
| if (ackTransaction != null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Found Tx pending ACK"); |
| try { |
| ackTransaction.setAckSeen(); |
| sipStack.removeTransaction(ackTransaction); |
| sipStack.removeTransactionPendingAck(ackTransaction); |
| } catch (Exception ex) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError( |
| "Problem terminating transaction", ex); |
| } |
| } |
| } |
| return; |
| } |
| } else { |
| transaction.passToListener(); |
| dialog.addTransaction(transaction); |
| 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.deliverTerminatedEventForAck) { |
| try { |
| sipStack.addTransaction(transaction); |
| transaction.scheduleAckRemoval(); |
| } catch (IOException ex) { |
| |
| } |
| } else { |
| transaction.setMapped(true); |
| } |
| |
| } |
| } |
| } |
| } 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.getStackLogger().logDebug("Processing PRACK for dialog " + dialog); |
| |
| if (dialog == null && sipProvider.isAutomaticDialogSupportEnabled()) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Dialog does not exist " + sipRequest.getFirstLine() |
| + " isServerTransaction = " + true); |
| |
| } |
| if (sipStack.isLoggingEnabled()) { |
| sipStack |
| .getStackLogger() |
| .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.getStackLogger().logError("error sending response", e); |
| } |
| if (transaction != null) { |
| sipStack.removeTransaction(transaction); |
| transaction.releaseSem(); |
| } |
| return; |
| |
| } else if (dialog != null) { |
| if (!dialog.handlePrack(sipRequest)) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().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 (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().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.getStackLogger().logDebug( |
| "Dropping out of sequence BYE " + dialog.getRemoteSeqNumber() + " " |
| + sipRequest.getCSeq().getSeqNumber()); |
| |
| if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq().getSeqNumber() |
| && transaction.getState() == 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 (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "dropping request -- automatic dialog " |
| + "support enabled and dialog does not exist!"); |
| try { |
| transaction.sendResponse(response); |
| } catch (SipException ex) { |
| sipStack.getStackLogger().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.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "BYE Tx = " + transaction + " isMapped =" |
| + transaction.isTransactionMapped()); |
| } |
| |
| } else if (sipRequest.getMethod().equals(Request.CANCEL)) { |
| |
| SIPServerTransaction st = (SIPServerTransaction) sipStack.findCancelTransaction( |
| sipRequest, true); |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().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.getStackLogger().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.getStackLogger().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.getStackLogger().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 |
| && sipProvider.isDialogErrorsAutomaticallyHandled() |
| && dialog.isSequnceNumberValidation() |
| && lastTransaction.isInviteTransaction() |
| && lastTransaction.getState() != TransactionState.COMPLETED |
| && lastTransaction.getState() != TransactionState.TERMINATED |
| && lastTransaction.getState() != TransactionState.CONFIRMED) { |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Sending 500 response for out of sequence message"); |
| } |
| this.sendServerInternalErrorResponse(sipRequest, transaction); |
| return; |
| |
| } |
| |
| /* |
| * Saw an interleaved invite before ACK was sent. 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 |
| && sipProvider.isDialogErrorsAutomaticallyHandled() |
| && lastTransaction != null |
| && lastTransaction.isInviteTransaction() |
| && lastTransaction instanceof ClientTransaction |
| && lastTransaction.getLastResponse() != null |
| && lastTransaction.getLastResponse().getStatusCode() == 200 |
| && !dialog.isAckSent(lastTransaction.getLastResponse().getCSeq() |
| .getSeqNumber())) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Sending 491 response for client Dialog ACK not sent."); |
| } |
| this.sendRequestPendingResponse(sipRequest, transaction); |
| return; |
| } |
| |
| if (dialog != null && lastTransaction != null |
| && sipProvider.isDialogErrorsAutomaticallyHandled() |
| && lastTransaction.isInviteTransaction() |
| && lastTransaction instanceof ServerTransaction && !dialog.isAckSeen()) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Sending 491 response for server Dialog ACK not seen."); |
| } |
| this.sendRequestPendingResponse(sipRequest, transaction); |
| 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. |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().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.getStackLogger().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() |
| && sipProvider.isDialogErrorsAutomaticallyHandled() |
| && (transaction.getState() == TransactionState.TRYING || transaction |
| .getState() == TransactionState.PROCEEDING)) { |
| this.sendServerInternalErrorResponse(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.isLoggingEnabled()) { |
| sipStack.getStackLogger().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.isLoggingEnabled()) { |
| sipStack.getStackLogger().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 -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.getStackLogger().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.getStackLogger().logError( |
| "Exception while sending error response statelessly", ex); |
| return; |
| } |
| |
| } |
| |
| // If the server transaction cannot be found or if it |
| // aleady has a dialog attached to it then just assign the |
| // notify to this dialog and pass it up. |
| if (pendingSubscribeClientTx != null) { |
| // The response to the pending subscribe tx can try to create |
| // a dialog at the same time that the notify is trying to |
| // create a dialog. Thus we cannot process both at the |
| // same time. |
| |
| transaction.setPendingSubscribe(pendingSubscribeClientTx); |
| // The transaction gets assigned to the dialog from the |
| // outgoing subscribe. First see if anybody claimed the |
| // default Dialog for the outgoing Subscribe request. |
| SIPDialog subscriptionDialog = (SIPDialog) pendingSubscribeClientTx |
| .getDefaultDialog(); |
| |
| // TODO -- refactor this. Can probably be written far cleaner. |
| if (subscriptionDialog == null || subscriptionDialog.getDialogId() == null |
| || !subscriptionDialog.getDialogId().equals(dialogId)) { |
| // Notify came in before you could assign a response to |
| // the subscribe. |
| // grab the default dialog and assign it to the tags in |
| // the notify. |
| if (subscriptionDialog != null && subscriptionDialog.getDialogId() == null) { |
| subscriptionDialog.setDialogId(dialogId); |
| |
| } else { |
| subscriptionDialog = pendingSubscribeClientTx.getDialog(dialogId); |
| } |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "PROCESSING NOTIFY Subscribe DIALOG " + subscriptionDialog); |
| } |
| |
| // The user could have createed a dialog before sending out |
| // the SUBSCRIBE on the subscribe tx. |
| if (subscriptionDialog == null |
| && (sipProvider.isAutomaticDialogSupportEnabled() || pendingSubscribeClientTx |
| .getDefaultDialog() != null)) { |
| Event event = (Event) sipRequest.getHeader(EventHeader.NAME); |
| if (sipStack.isEventForked(event.getEventType())) { |
| |
| subscriptionDialog = SIPDialog.createFromNOTIFY( |
| pendingSubscribeClientTx, transaction); |
| |
| } |
| |
| } |
| if (subscriptionDialog != null) { |
| transaction.setDialog(subscriptionDialog, dialogId); |
| subscriptionDialog.setState(DialogState.CONFIRMED.getValue()); |
| sipStack.putDialog(subscriptionDialog); |
| pendingSubscribeClientTx.setDialog(subscriptionDialog, dialogId); |
| if (!transaction.isTransactionMapped()) { |
| this.sipStack.mapTransaction(transaction); |
| // Let the listener see it if it just got |
| // created. |
| // otherwise, we have already processed the tx |
| // so |
| // we dont want the listener to see it. |
| transaction.setPassToListener(); |
| try { |
| this.sipStack.addTransaction(transaction); |
| } catch (Exception ex) { |
| } |
| } |
| } |
| } else { |
| // The subscription default dialog is our dialog. |
| // Found a subscrbe dialog for the NOTIFY |
| // So map the tx. |
| transaction.setDialog(subscriptionDialog, dialogId); |
| dialog = subscriptionDialog; |
| if (!transaction.isTransactionMapped()) { |
| this.sipStack.mapTransaction(transaction); |
| // Let the listener see it if it just got created. |
| // otherwise, we have already processed the tx so |
| // we dont want the listener to see it. |
| transaction.setPassToListener(); |
| try { |
| this.sipStack.addTransaction(transaction); |
| } catch (Exception ex) { |
| } |
| } |
| sipStack.putDialog(subscriptionDialog); |
| if (pendingSubscribeClientTx != null) { |
| subscriptionDialog.addTransaction(pendingSubscribeClientTx); |
| pendingSubscribeClientTx.setDialog(subscriptionDialog, dialogId); |
| |
| } |
| } |
| if (transaction != null |
| && ((SIPServerTransaction) transaction).isTransactionMapped()) { |
| // Shadow transaction has been created and the stack |
| // knows |
| // about it. |
| sipEvent = new RequestEvent((SipProvider) sipProvider, |
| (ServerTransaction) transaction, subscriptionDialog, |
| (Request) sipRequest); |
| } else { |
| // Shadow transaction has been created but the stack |
| // does |
| // not know |
| // about it. |
| sipEvent = new RequestEvent((SipProvider) sipProvider, null, |
| subscriptionDialog, (Request) sipRequest); |
| } |
| |
| } else { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("could not find subscribe tx"); |
| } |
| |
| // Got a notify out of the blue - just pass it up |
| // for stateless handling by the application. |
| sipEvent = new RequestEvent(sipProvider, null, null, (Request) sipRequest); |
| } |
| |
| } else { |
| |
| // For a dialog creating event - set the transaction to null. |
| // The listener can create the dialog if needed. |
| if (transaction != null |
| && (((SIPServerTransaction) transaction).isTransactionMapped())) { |
| sipEvent = new RequestEvent(sipProvider, (ServerTransaction) transaction, dialog, |
| (Request) sipRequest); |
| } else { |
| sipEvent = new RequestEvent(sipProvider, null, dialog, (Request) sipRequest); |
| } |
| } |
| sipProvider.handleEvent(sipEvent, transaction); |
| |
| } |
| |
| /** |
| * Process the response. |
| * |
| * @exception SIPServerException is thrown when there is an error processing the response |
| * @param incomingMessageChannel -- message channel on which the response is received. |
| */ |
| public void processResponse(SIPResponse response, MessageChannel incomingMessageChannel, |
| SIPDialog dialog) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "PROCESSING INCOMING RESPONSE" + response.encodeMessage()); |
| } |
| if (listeningPoint == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logError( |
| "Dropping message: No listening point" + " registered!"); |
| return; |
| } |
| |
| if (sipStack.checkBranchId() && !Utils.getInstance().responseBelongsToUs(response)) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack |
| .getStackLogger() |
| .logError( |
| "Dropping response - topmost VIA header does not originate from this stack"); |
| } |
| return; |
| } |
| |
| SipProviderImpl sipProvider = listeningPoint.getProvider(); |
| if (sipProvider == null) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError("Dropping message: no provider"); |
| } |
| return; |
| } |
| if (sipProvider.getSipListener() == null) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError("No listener -- dropping response!"); |
| } |
| return; |
| } |
| |
| SIPClientTransaction transaction = (SIPClientTransaction) this.transactionChannel; |
| SipStackImpl sipStackImpl = sipProvider.sipStack; |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStackImpl.getStackLogger().logDebug("Transaction = " + transaction); |
| } |
| |
| if (transaction == null) { |
| // Transaction is null but the dialog is not null. This means that |
| // the transaction has been removed by the stack. |
| // If the dialog exists, then it may need to retransmit ACK so |
| // we cannot drop the response. |
| if (dialog != null) { |
| if (response.getStatusCode() / 100 != 2) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack |
| .getStackLogger() |
| .logDebug( |
| "Response is not a final response and dialog is found for response -- dropping response!"); |
| } |
| return; |
| } else if (dialog.getState() == DialogState.TERMINATED) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Dialog is terminated -- dropping response!"); |
| } |
| return; |
| } else { |
| boolean ackAlreadySent = false; |
| if (dialog.isAckSeen() && dialog.getLastAckSent() != null) { |
| if (dialog.getLastAckSent().getCSeq().getSeqNumber() == response |
| .getCSeq().getSeqNumber()) { |
| // the last ack sent corresponded to this 200 |
| ackAlreadySent = true; |
| } |
| } |
| // 200 retransmission for the final response. |
| if (ackAlreadySent |
| && response.getCSeq().getMethod().equals(dialog.getMethod())) { |
| try { |
| // Found the dialog - resend the ACK and |
| // dont pass up the null transaction |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Retransmission of OK detected: Resending last ACK"); |
| } |
| dialog.resendAck(); |
| return; |
| } catch (SipException ex) { |
| // What to do here ?? kill the dialog? |
| sipStack.getStackLogger().logError("could not resend ack", ex); |
| } |
| } |
| } |
| } |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "could not find tx, handling statelessly Dialog = " + dialog); |
| } |
| // Pass the response up to the application layer to handle |
| // statelessly. |
| |
| ResponseEventExt sipEvent = new ResponseEventExt(sipProvider, transaction, dialog, |
| (Response) response); |
| |
| if (response.getCSeqHeader().getMethod().equals(Request.INVITE)) { |
| SIPClientTransaction forked = this.sipStack.getForkedTransaction(response |
| .getTransactionId()); |
| sipEvent.setOriginalTransaction(forked); |
| } |
| |
| sipProvider.handleEvent(sipEvent, transaction); |
| return; |
| } |
| |
| ResponseEventExt responseEvent = null; |
| |
| // Here if there is an assigned dialog |
| responseEvent = new ResponseEventExt(sipProvider, (ClientTransactionExt) transaction, |
| dialog, (Response) response); |
| if (response.getCSeqHeader().getMethod().equals(Request.INVITE)) { |
| SIPClientTransaction forked = this.sipStack.getForkedTransaction(response |
| .getTransactionId()); |
| responseEvent.setOriginalTransaction(forked); |
| } |
| |
| // Set the Dialog for the response. |
| if (dialog != null && response.getStatusCode() != 100) { |
| // set the last response for the dialog. |
| dialog.setLastResponse(transaction, response); |
| transaction.setDialog(dialog, dialog.getDialogId()); |
| } |
| |
| sipProvider.handleEvent(responseEvent, transaction); |
| |
| } |
| |
| /** |
| * Just a placeholder. This is called from the stack for message logging. Auxiliary processing |
| * information can be passed back to be written into the log file. |
| * |
| * @return auxiliary information that we may have generated during the message processing |
| * which is retrieved by the message logger. |
| */ |
| public String getProcessingInfo() { |
| return null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see gov.nist.javax.sip.stack.ServerResponseInterface#processResponse(gov.nist.javax.sip.message.SIPResponse, |
| * gov.nist.javax.sip.stack.MessageChannel) |
| */ |
| public void processResponse(SIPResponse sipResponse, MessageChannel incomingChannel) { |
| String dialogID = sipResponse.getDialogId(false); |
| SIPDialog sipDialog = this.sipStack.getDialog(dialogID); |
| |
| String method = sipResponse.getCSeq().getMethod(); |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "PROCESSING INCOMING RESPONSE: " + sipResponse.encodeMessage()); |
| } |
| |
| if (sipStack.checkBranchId() && !Utils.getInstance().responseBelongsToUs(sipResponse)) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError("Detected stray response -- dropping"); |
| } |
| return; |
| } |
| |
| if (listeningPoint == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Dropping message: No listening point" + " registered!"); |
| return; |
| } |
| |
| SipProviderImpl sipProvider = listeningPoint.getProvider(); |
| if (sipProvider == null) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug("Dropping message: no provider"); |
| } |
| return; |
| } |
| |
| if (sipProvider.getSipListener() == null) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Dropping message: no sipListener registered!"); |
| } |
| return; |
| } |
| |
| SIPClientTransaction transaction = (SIPClientTransaction) this.transactionChannel; |
| // This may be a dialog creating method for which the ACK has not yet |
| // been sent |
| // but the dialog has already been assigned ( happens this way for |
| // 3PCC). |
| if (sipDialog == null && transaction != null) { |
| sipDialog = transaction.getDialog(dialogID); |
| if (sipDialog != null && sipDialog.getState() == DialogState.TERMINATED) |
| sipDialog = null; |
| } |
| |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Transaction = " + transaction + " sipDialog = " + sipDialog); |
| |
| if (this.transactionChannel != null) { |
| String originalFrom = ((SIPRequest) this.transactionChannel.getRequest()) |
| .getFromTag(); |
| if (originalFrom == null ^ sipResponse.getFrom().getTag() == null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("From tag mismatch -- dropping response"); |
| return; |
| } |
| if (originalFrom != null |
| && !originalFrom.equalsIgnoreCase(sipResponse.getFrom().getTag())) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("From tag mismatch -- dropping response"); |
| return; |
| } |
| |
| } |
| if (sipStack.isDialogCreated(method) && sipResponse.getStatusCode() != 100 |
| && sipResponse.getFrom().getTag() != null && sipResponse.getTo().getTag() != null |
| && sipDialog == null) { |
| if (sipProvider.isAutomaticDialogSupportEnabled()) { |
| if (this.transactionChannel != null) { |
| if (sipDialog == null) { |
| // There could be an existing dialog for this response. |
| sipDialog = sipStack.createDialog( |
| (SIPClientTransaction) this.transactionChannel, sipResponse); |
| |
| this.transactionChannel.setDialog(sipDialog, sipResponse |
| .getDialogId(false)); |
| } |
| } else { |
| sipDialog = this.sipStack.createDialog(sipProvider, sipResponse); |
| } |
| } |
| |
| } else { |
| // Have a dialog but could not find transaction. |
| if (sipDialog != null && transaction == null |
| && sipDialog.getState() != DialogState.TERMINATED) { |
| if (sipResponse.getStatusCode() / 100 != 2) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "status code != 200 ; statusCode = " |
| + sipResponse.getStatusCode()); |
| } else if (sipDialog.getState() == DialogState.TERMINATED) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Dialog is terminated -- dropping response!"); |
| } |
| // Dialog exists but was terminated - just create and send an ACK for the OK. |
| // It could be late arriving. |
| if (sipResponse.getStatusCode() / 100 == 2 |
| && sipResponse.getCSeq().getMethod().equals(Request.INVITE)) { |
| try { |
| Request ackRequest = sipDialog.createAck(sipResponse.getCSeq() |
| .getSeqNumber()); |
| sipDialog.sendAck(ackRequest); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logError("Error creating ack", ex); |
| } |
| } |
| return; |
| } else { |
| boolean ackAlreadySent = false; |
| if (sipDialog.isAckSeen() && sipDialog.getLastAckSent() != null) { |
| if (sipDialog.getLastAckSent().getCSeq().getSeqNumber() == sipResponse |
| .getCSeq().getSeqNumber() |
| && sipResponse.getDialogId(false).equals( |
| sipDialog.getLastAckSent().getDialogId(false))) { |
| // the last ack sent corresponded to this 200 |
| ackAlreadySent = true; |
| } |
| } |
| // 200 retransmission for the final response. |
| if (ackAlreadySent |
| && sipResponse.getCSeq().getMethod().equals(sipDialog.getMethod())) { |
| try { |
| // Found the dialog - resend the ACK and |
| // dont pass up the null transaction |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("resending ACK"); |
| |
| sipDialog.resendAck(); |
| return; |
| } catch (SipException ex) { |
| // What to do here ?? kill the dialog? |
| } |
| } |
| } |
| } |
| // Pass the response up to the application layer to handle |
| // statelessly. |
| |
| } |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("sending response to TU for processing "); |
| |
| if (sipDialog != null && sipResponse.getStatusCode() != 100 |
| && sipResponse.getTo().getTag() != null) { |
| sipDialog.setLastResponse(transaction, sipResponse); |
| } |
| |
| ResponseEventExt responseEvent = new ResponseEventExt(sipProvider, |
| (ClientTransactionExt) transaction, sipDialog, (Response) sipResponse); |
| |
| if (sipResponse.getCSeq().getMethod().equals(Request.INVITE)) { |
| ClientTransactionExt originalTx = this.sipStack.getForkedTransaction(sipResponse |
| .getTransactionId()); |
| responseEvent.setOriginalTransaction(originalTx); |
| } |
| |
| sipProvider.handleEvent(responseEvent, transaction); |
| |
| } |
| } |