/* * 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 Advanced Networking Technologies Division */ /**************************************************************************/ package gov.nist.javax.sip.stack; import gov.nist.core.InternalErrorHandler; import gov.nist.core.NameValueList; import gov.nist.javax.sip.DialogExt; import gov.nist.javax.sip.ListeningPointImpl; import gov.nist.javax.sip.SipListenerExt; import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.Utils; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.header.Authorization; import gov.nist.javax.sip.header.CSeq; import gov.nist.javax.sip.header.Contact; import gov.nist.javax.sip.header.ContactList; import gov.nist.javax.sip.header.From; import gov.nist.javax.sip.header.MaxForwards; import gov.nist.javax.sip.header.RAck; import gov.nist.javax.sip.header.RSeq; import gov.nist.javax.sip.header.Reason; import gov.nist.javax.sip.header.RecordRoute; import gov.nist.javax.sip.header.RecordRouteList; import gov.nist.javax.sip.header.Require; import gov.nist.javax.sip.header.Route; import gov.nist.javax.sip.header.RouteList; import gov.nist.javax.sip.header.SIPHeader; import gov.nist.javax.sip.header.TimeStamp; import gov.nist.javax.sip.header.To; import gov.nist.javax.sip.header.Via; import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.net.InetAddress; import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import javax.sip.ClientTransaction; import javax.sip.DialogDoesNotExistException; import javax.sip.DialogState; import javax.sip.IOExceptionEvent; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.SipException; import javax.sip.Transaction; import javax.sip.TransactionDoesNotExistException; import javax.sip.TransactionState; import javax.sip.address.Address; import javax.sip.address.Hop; import javax.sip.address.SipURI; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.EventHeader; import javax.sip.header.OptionTag; import javax.sip.header.RAckHeader; import javax.sip.header.RSeqHeader; import javax.sip.header.ReasonHeader; import javax.sip.header.RequireHeader; import javax.sip.header.RouteHeader; import javax.sip.header.SupportedHeader; import javax.sip.header.TimeStampHeader; import javax.sip.message.Request; import javax.sip.message.Response; /* * Acknowledgements: * * Bugs in this class were reported by Antonis Karydas, Brad Templeton, Jeff Adams, Alex Rootham , * Martin Le Clerk, Christophe Anzille, Andreas Bystrom, Lebing Xie, Jeroen van Bemmel. Hagai Sela * reported a bug in updating the route set (on RE-INVITE). Jens Tinfors submitted a bug fix and * the .equals method. Jan Schaumloeffel contributed a buf fix ( memory leak was happening when * 180 contained a To tag. * */ /** * Tracks dialogs. A dialog is a peer to peer association of communicating SIP entities. For * INVITE transactions, a Dialog is created when a success message is received (i.e. a response * that has a To tag). The SIP Protocol stores enough state in the message structure to extract a * dialog identifier that can be used to retrieve this structure from the SipStack. * * @version 1.2 $Revision: 1.159 $ $Date: 2010/01/08 15:14:12 $ * * @author M. Ranganathan * * */ public class SIPDialog implements javax.sip.Dialog, DialogExt { private static final long serialVersionUID = -1429794423085204069L; private transient boolean dialogTerminatedEventDelivered; // prevent duplicate private transient String stackTrace; // for semaphore debugging. private String method; // delivery of the event private transient boolean isAssigned; private boolean reInviteFlag; private transient Object applicationData; // Opaque pointer to application data. private transient SIPRequest originalRequest; // Last response (JvB: either sent or received). private SIPResponse lastResponse; // Should be transient, in case the dialog is serialized it will be null // so when a subsequent request will be sent it will be set and a new message channel can be // created private transient SIPTransaction firstTransaction; private transient SIPTransaction lastTransaction; private String dialogId; private transient String earlyDialogId; private long localSequenceNumber; private long remoteSequenceNumber; protected String myTag; protected String hisTag; private RouteList routeList; private transient SIPTransactionStack sipStack; private int dialogState; protected transient boolean ackSeen; private transient SIPRequest lastAckSent; private SIPRequest lastAckReceived; // could be set on recovery by examining the method looks like a duplicate of ackSeen protected transient boolean ackProcessed; protected transient DialogTimerTask timerTask; protected transient Long nextSeqno; private transient int retransmissionTicksLeft; private transient int prevRetransmissionTicks; private long originalLocalSequenceNumber; // This is for debugging only. private transient int ackLine; // Audit tag used by the SIP Stack audit public transient long auditTag = 0; // The following fields are extracted from the request that created the // Dialog. protected javax.sip.address.Address localParty; protected javax.sip.address.Address remoteParty; protected CallIdHeader callIdHeader; public final static int NULL_STATE = -1; public final static int EARLY_STATE = DialogState._EARLY; public final static int CONFIRMED_STATE = DialogState._CONFIRMED; public final static int TERMINATED_STATE = DialogState._TERMINATED; // the amount of time to keep this dialog around before the stack GC's it private static final int DIALOG_LINGER_TIME = 8; private boolean serverTransactionFlag; private transient SipProviderImpl sipProvider; private boolean terminateOnBye; private transient boolean byeSent; // Flag set when BYE is sent, to disallow new // requests private Address remoteTarget; private EventHeader eventHeader; // for Subscribe notify // Stores the last OK for the INVITE // Used in createAck. private transient long lastInviteOkReceived; private transient Semaphore ackSem = new Semaphore(1); private transient int reInviteWaitTime = 100; private transient DialogDeleteTask dialogDeleteTask; private transient DialogDeleteIfNoAckSentTask dialogDeleteIfNoAckSentTask; private transient boolean isAcknowledged; private transient long highestSequenceNumberAcknowledged = -1; private boolean isBackToBackUserAgent; private boolean sequenceNumberValidation = true; // List of event listeners for this dialog private transient Set eventListeners; // added for Issue 248 : https://jain-sip.dev.java.net/issues/show_bug.cgi?id=248 private Semaphore timerTaskLock = new Semaphore(1); // We store here the useful data from the first transaction without having to // keep the whole transaction object for the duration of the dialog. It also // contains the non-transient information used in the replication of dialogs. protected boolean firstTransactionSecure; protected boolean firstTransactionSeen; protected String firstTransactionMethod; protected String firstTransactionId; protected boolean firstTransactionIsServerTransaction; protected int firstTransactionPort = 5060; protected Contact contactHeader; // ////////////////////////////////////////////////////// // Inner classes // ////////////////////////////////////////////////////// /** * This task waits till a pending ACK has been recorded and then sends out a re-INVITE. This * is to prevent interleaving INVITEs ( which will result in a 493 from the UA that receives * the out of order INVITE). This is primarily for B2BUA support. A B2BUA may send a delayed * ACK while it does mid call codec renegotiation. In the meanwhile, it cannot send an intervening * re-INVITE otherwise the othr end will respond with a REQUEST_PENDING. We want to avoid this * condition. Hence we wait till the ACK for the previous re-INVITE has been sent before * sending the next re-INVITE. */ public class ReInviteSender implements Runnable, Serializable { private static final long serialVersionUID = 1019346148741070635L; ClientTransaction ctx; public void terminate() { try { ctx.terminate(); Thread.currentThread().interrupt(); } catch (ObjectInUseException e) { sipStack.getStackLogger().logError("unexpected error", e); } } public ReInviteSender(ClientTransaction ctx) { this.ctx = ctx; } public void run() { try { long timeToWait = 0; long startTime = System.currentTimeMillis(); if (!SIPDialog.this.takeAckSem()) { /* * Could not send re-INVITE fire a timeout on the INVITE. */ if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError( "Could not send re-INVITE time out ClientTransaction"); ((SIPClientTransaction) ctx).fireTimeoutTimer(); /* * Send BYE to the Dialog. */ if ( sipProvider.getSipListener() != null && sipProvider.getSipListener() instanceof SipListenerExt ) { raiseErrorEvent(SIPDialogErrorEvent.DIALOG_REINVITE_TIMEOUT); } else { Request byeRequest = SIPDialog.this.createRequest(Request.BYE); if ( MessageFactoryImpl.getDefaultUserAgentHeader() != null ) { byeRequest.addHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); } ReasonHeader reasonHeader = new Reason(); reasonHeader.setCause(1024); reasonHeader.setText("Timed out waiting to re-INVITE"); byeRequest.addHeader(reasonHeader); ClientTransaction byeCtx = SIPDialog.this.getSipProvider().getNewClientTransaction(byeRequest); SIPDialog.this.sendRequest(byeCtx); return; } } if (getState() != DialogState.TERMINATED) { timeToWait = System.currentTimeMillis() - startTime; } /* * If we had to wait for ACK then wait for the ACK to actually get to the other * side. Wait for any ACK retransmissions to finish. Then send out the request. * This is a hack in support of some UA that want re-INVITEs to be spaced out in * time ( else they return a 400 error code ). */ try { if (timeToWait != 0) { Thread.sleep(SIPDialog.this.reInviteWaitTime); } } catch (InterruptedException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Interrupted sleep"); return; } if (SIPDialog.this.getState() != DialogState.TERMINATED) { SIPDialog.this.sendRequest(ctx, true); } if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("re-INVITE successfully sent"); } catch (Exception ex) { sipStack.getStackLogger().logError("Error sending re-INVITE", ex); } finally { this.ctx = null; } } } class LingerTimer extends SIPStackTimerTask implements Serializable { public LingerTimer() { } protected void runTask() { SIPDialog dialog = SIPDialog.this; if(eventListeners != null) { eventListeners.clear(); } timerTaskLock = null; sipStack.removeDialog(dialog); } } class DialogTimerTask extends SIPStackTimerTask implements Serializable { int nRetransmissions; SIPServerTransaction transaction; public DialogTimerTask(SIPServerTransaction transaction) { this.transaction = transaction; this.nRetransmissions = 0; } protected void runTask() { // If I ACK has not been seen on Dialog, // resend last response. SIPDialog dialog = SIPDialog.this; if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Running dialog timer"); nRetransmissions++; SIPServerTransaction transaction = this.transaction; /* * Issue 106. Section 13.3.1.4 RFC 3261 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 If the server retransmits the 2xx response for 64*T1 * seconds without receiving an ACK, the dialog is confirmed, but the session SHOULD * be terminated. */ if (nRetransmissions > 64 * SIPTransaction.T1) { if (sipProvider.getSipListener() != null && sipProvider.getSipListener() instanceof SipListenerExt ) { raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_RECEIVED_TIMEOUT); } else { dialog.delete(); } if (transaction != null && transaction.getState() != javax.sip.TransactionState.TERMINATED) { transaction.raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR); } } else if ((!dialog.ackSeen) && (transaction != null)) { // Retransmit to 200 until ack receivedialog. SIPResponse response = transaction.getLastResponse(); if (response.getStatusCode() == 200) { try { // resend the last response. if (dialog.toRetransmitFinalResponse(transaction.T2)) transaction.sendMessage(response); } catch (IOException ex) { raiseIOException(transaction.getPeerAddress(), transaction.getPeerPort(), transaction.getPeerProtocol()); } finally { // Need to fire the timer so // transaction will eventually // time out whether or not // the IOException occurs // Note that this firing also // drives Listener timeout. SIPTransactionStack stack = dialog.sipStack; if (stack.isLoggingEnabled()) { stack.getStackLogger().logDebug("resend 200 response from " + dialog); } transaction.fireTimer(); } } } // Stop running this timer if the dialog is in the // confirmed state or ack seen if retransmit filter on. if (dialog.isAckSeen() || dialog.dialogState == TERMINATED_STATE) { this.transaction = null; this.cancel(); } } } /** * This timer task is used to garbage collect the dialog after some time. * */ class DialogDeleteTask extends SIPStackTimerTask implements Serializable { protected void runTask() { delete(); } } /** * This timer task is used to garbage collect the dialog after some time. * */ class DialogDeleteIfNoAckSentTask extends SIPStackTimerTask implements Serializable { private long seqno; public DialogDeleteIfNoAckSentTask(long seqno) { this.seqno = seqno; } protected void runTask() { if (SIPDialog.this.highestSequenceNumberAcknowledged < seqno) { /* * Did not send ACK so we need to delete the dialog. * B2BUA NOTE: we may want to send BYE to the Dialog at this * point. Do we want to make this behavior tailorable? */ dialogDeleteIfNoAckSentTask = null; if ( !SIPDialog.this.isBackToBackUserAgent) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("ACK Was not sent. killing dialog"); if ( ((SipProviderImpl)sipProvider).getSipListener() instanceof SipListenerExt ){ raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT); } else { delete(); } } else { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("ACK Was not sent. Sending BYE"); if ( ((SipProviderImpl)sipProvider).getSipListener() instanceof SipListenerExt ){ raiseErrorEvent(SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT); } else { /* * Send BYE to the Dialog. * This will be removed for the next spec revision. */ try { Request byeRequest = SIPDialog.this.createRequest(Request.BYE); if ( MessageFactoryImpl.getDefaultUserAgentHeader() != null ) { byeRequest.addHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); } ReasonHeader reasonHeader = new Reason(); reasonHeader.setProtocol("SIP"); reasonHeader.setCause(1025); reasonHeader.setText("Timed out waiting to send ACK"); byeRequest.addHeader(reasonHeader); ClientTransaction byeCtx = SIPDialog.this.getSipProvider().getNewClientTransaction(byeRequest); SIPDialog.this.sendRequest(byeCtx); return; } catch (Exception ex) { SIPDialog.this.delete(); } } } } } } // /////////////////////////////////////////////////////////// // Constructors. // /////////////////////////////////////////////////////////// /** * Protected Dialog constructor. */ private SIPDialog(SipProviderImpl provider) { this.terminateOnBye = true; this.routeList = new RouteList(); this.dialogState = NULL_STATE; // not yet initialized. localSequenceNumber = 0; remoteSequenceNumber = -1; this.sipProvider = provider; eventListeners = new CopyOnWriteArraySet(); } private void recordStackTrace() { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); new Exception().printStackTrace(writer); this.stackTrace = stringWriter.getBuffer().toString(); } /** * Constructor given the first transaction. * * @param transaction is the first transaction. */ public SIPDialog(SIPTransaction transaction) { this(transaction.getSipProvider()); SIPRequest sipRequest = (SIPRequest) transaction.getRequest(); this.callIdHeader = sipRequest.getCallId(); this.earlyDialogId = sipRequest.getDialogId(false); if (transaction == null) throw new NullPointerException("Null tx"); this.sipStack = transaction.sipStack; // this.defaultRouter = new DefaultRouter((SipStack) sipStack, // sipStack.outboundProxy); this.sipProvider = (SipProviderImpl) transaction.getSipProvider(); if (sipProvider == null) throw new NullPointerException("Null Provider!"); this.addTransaction(transaction); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("Creating a dialog : " + this); sipStack.getStackLogger().logDebug( "provider port = " + this.sipProvider.getListeningPoint().getPort()); sipStack.getStackLogger().logStackTrace(); } this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent; addEventListener(sipStack); } /** * Constructor given a transaction and a response. * * @param transaction -- the transaction ( client/server) * @param sipResponse -- response with the appropriate tags. */ public SIPDialog(SIPClientTransaction transaction, SIPResponse sipResponse) { this(transaction); if (sipResponse == null) throw new NullPointerException("Null SipResponse"); this.setLastResponse(transaction, sipResponse); this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent; } /** * create a sip dialog with a response ( no tx) */ public SIPDialog(SipProviderImpl sipProvider, SIPResponse sipResponse) { this(sipProvider); this.sipStack = (SIPTransactionStack) sipProvider.getSipStack(); this.setLastResponse(null, sipResponse); this.localSequenceNumber = sipResponse.getCSeq().getSeqNumber(); this.originalLocalSequenceNumber = localSequenceNumber; this.myTag = sipResponse.getFrom().getTag(); this.hisTag = sipResponse.getTo().getTag(); this.localParty = sipResponse.getFrom().getAddress(); this.remoteParty = sipResponse.getTo().getAddress(); this.method = sipResponse.getCSeq().getMethod(); this.callIdHeader = sipResponse.getCallId(); this.serverTransactionFlag = false; if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("Creating a dialog : " + this); sipStack.getStackLogger().logStackTrace(); } this.isBackToBackUserAgent = sipStack.isBackToBackUserAgent; addEventListener(sipStack); } // /////////////////////////////////////////////////////////// // Private methods // /////////////////////////////////////////////////////////// /** * A debugging print routine. */ private void printRouteList() { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("this : " + this); sipStack.getStackLogger().logDebug("printRouteList : " + this.routeList.encode()); } } /** * Return true if this is a client dialog. * * @return true if the transaction that created this dialog is a client transaction and false * otherwise. */ private boolean isClientDialog() { SIPTransaction transaction = (SIPTransaction) this.getFirstTransaction(); return transaction instanceof SIPClientTransaction; } /** * Raise an io exception for asyncrhonous retransmission of responses * * @param host -- host to where the io was headed * @param port -- remote port * @param protocol -- protocol (udp/tcp/tls) */ private void raiseIOException(String host, int port, String protocol) { // Error occured in retransmitting response. // Deliver the error event to the listener // Kill the dialog. IOExceptionEvent ioError = new IOExceptionEvent(this, host, port, protocol); sipProvider.handleEvent(ioError, null); setState(SIPDialog.TERMINATED_STATE); } /** * Raise a dialog timeout if an ACK has not been sent or received * * @param dialogTimeoutError */ private void raiseErrorEvent(int dialogTimeoutError) { // Error event to send to all listeners SIPDialogErrorEvent newErrorEvent; // Iterator through the list of listeners Iterator listenerIterator; // Next listener in the list SIPDialogEventListener nextListener; // Create the error event newErrorEvent = new SIPDialogErrorEvent(this, dialogTimeoutError); // Loop through all listeners of this transaction synchronized (eventListeners) { listenerIterator = eventListeners.iterator(); while (listenerIterator.hasNext()) { // Send the event to the next listener nextListener = (SIPDialogEventListener) listenerIterator.next(); nextListener.dialogErrorEvent(newErrorEvent); } } // Clear the event listeners after propagating the error. eventListeners.clear(); // Errors always terminate a dialog except if a timeout has occured because an ACK was not sent or received, then it is the responsibility of the app to terminate // the dialog, either by sending a BYE or by calling delete() on the dialog if(dialogTimeoutError != SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT && dialogTimeoutError != SIPDialogErrorEvent.DIALOG_ACK_NOT_RECEIVED_TIMEOUT && dialogTimeoutError != SIPDialogErrorEvent.DIALOG_REINVITE_TIMEOUT ) { delete(); } // we stop the timer in any case stopTimer(); } /** * Set the remote party for this Dialog. * * @param sipMessage -- SIP Message to extract the relevant information from. */ private void setRemoteParty(SIPMessage sipMessage) { if (!isServer()) { this.remoteParty = sipMessage.getTo().getAddress(); } else { this.remoteParty = sipMessage.getFrom().getAddress(); } if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("settingRemoteParty " + this.remoteParty); } } /** * Add a route list extracted from a record route list. If this is a server dialog then we * assume that the record are added to the route list IN order. If this is a client dialog * then we assume that the record route headers give us the route list to add in reverse * order. * * @param recordRouteList -- the record route list from the incoming message. */ private void addRoute(RecordRouteList recordRouteList) { try { if (this.isClientDialog()) { // This is a client dialog so we extract the record // route from the response and reverse its order to // careate a route list. this.routeList = new RouteList(); // start at the end of the list and walk backwards ListIterator li = recordRouteList.listIterator(recordRouteList.size()); boolean addRoute = true; while (li.hasPrevious()) { RecordRoute rr = (RecordRoute) li.previous(); if (addRoute) { Route route = new Route(); AddressImpl address = ((AddressImpl) ((AddressImpl) rr.getAddress()) .clone()); route.setAddress(address); route.setParameters((NameValueList) rr.getParameters().clone()); this.routeList.add(route); } } } else { // This is a server dialog. The top most record route // header is the one that is closest to us. We extract the // route list in the same order as the addresses in the // incoming request. this.routeList = new RouteList(); ListIterator li = recordRouteList.listIterator(); boolean addRoute = true; while (li.hasNext()) { RecordRoute rr = (RecordRoute) li.next(); if (addRoute) { Route route = new Route(); AddressImpl address = ((AddressImpl) ((AddressImpl) rr.getAddress()) .clone()); route.setAddress(address); route.setParameters((NameValueList) rr.getParameters().clone()); routeList.add(route); } } } } finally { if (sipStack.getStackLogger().isLoggingEnabled()) { Iterator it = routeList.iterator(); while (it.hasNext()) { SipURI sipUri = (SipURI) (((Route) it.next()).getAddress().getURI()); if (!sipUri.hasLrParam()) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logWarning( "NON LR route in Route set detected for dialog : " + this); sipStack.getStackLogger().logStackTrace(); } } } } } } /** * Add a route list extacted from the contact list of the incoming message. * * @param contactList -- contact list extracted from the incoming message. * */ void setRemoteTarget(ContactHeader contact) { this.remoteTarget = contact.getAddress(); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("Dialog.setRemoteTarget: " + this.remoteTarget); sipStack.getStackLogger().logStackTrace(); } } /** * Extract the route information from this SIP Message and add the relevant information to the * route set. * * @param sipMessage is the SIP message for which we want to add the route. */ private synchronized void addRoute(SIPResponse sipResponse) { try { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "setContact: dialogState: " + this + "state = " + this.getState()); } if (sipResponse.getStatusCode() == 100) { // Do nothing for trying messages. return; } else if (this.dialogState == TERMINATED_STATE) { // Do nothing if the dialog state is terminated. return; } else if (this.dialogState == CONFIRMED_STATE) { // cannot add route list after the dialog is initialized. // Remote target is updated on RE-INVITE but not // the route list. if (sipResponse.getStatusCode() / 100 == 2 && !this.isServer()) { ContactList contactList = sipResponse.getContactHeaders(); if (contactList != null && SIPRequest.isTargetRefresh(sipResponse.getCSeq().getMethod())) { this.setRemoteTarget((ContactHeader) contactList.getFirst()); } } return; } // Update route list on response if I am a client dialog. if (!isServer()) { // only update the route set if the dialog is not in the confirmed state. if (this.getState() != DialogState.CONFIRMED && this.getState() != DialogState.TERMINATED) { RecordRouteList rrlist = sipResponse.getRecordRouteHeaders(); // Add the route set from the incoming response in reverse // order for record route headers. if (rrlist != null) { this.addRoute(rrlist); } else { // Set the rotue list to the last seen route list. this.routeList = new RouteList(); } } ContactList contactList = sipResponse.getContactHeaders(); if (contactList != null) { this.setRemoteTarget((ContactHeader) contactList.getFirst()); } } } finally { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logStackTrace(); } } } /** * Get a cloned copy of route list for the Dialog. * * @return -- a cloned copy of the dialog route list. */ private synchronized RouteList getRouteList() { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("getRouteList " + this); // Find the top via in the route list. ListIterator li; RouteList retval = new RouteList(); retval = new RouteList(); if (this.routeList != null) { li = routeList.listIterator(); while (li.hasNext()) { Route route = (Route) li.next(); retval.add((Route) route.clone()); } } if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("----- "); sipStack.getStackLogger().logDebug("getRouteList for " + this); if (retval != null) sipStack.getStackLogger().logDebug("RouteList = " + retval.encode()); if (routeList != null) sipStack.getStackLogger().logDebug("myRouteList = " + routeList.encode()); sipStack.getStackLogger().logDebug("----- "); } return retval; } void setRouteList(RouteList routeList) { this.routeList = routeList; } /** * Sends ACK Request to the remote party of this Dialogue. * * * @param request the new ACK Request message to send. * @param throwIOExceptionAsSipException - throws SipException if IOEx encountered. Otherwise, * no exception is propagated. * @param releaseAckSem - release ack semaphore. * @throws SipException if implementation cannot send the ACK Request for any other reason * */ private void sendAck(Request request, boolean throwIOExceptionAsSipException) throws SipException { SIPRequest ackRequest = (SIPRequest) request; if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("sendAck" + this); if (!ackRequest.getMethod().equals(Request.ACK)) throw new SipException("Bad request method -- should be ACK"); if (this.getState() == null || this.getState().getValue() == EARLY_STATE) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logError( "Bad Dialog State for " + this + " dialogID = " + this.getDialogId()); } throw new SipException("Bad dialog state " + this.getState()); } if (!this.getCallId().getCallId().equals(((SIPRequest) request).getCallId().getCallId())) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logError("CallID " + this.getCallId()); sipStack.getStackLogger().logError( "RequestCallID = " + ackRequest.getCallId().getCallId()); sipStack.getStackLogger().logError("dialog = " + this); } throw new SipException("Bad call ID in request"); } try { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "setting from tag For outgoing ACK= " + this.getLocalTag()); sipStack.getStackLogger().logDebug( "setting To tag for outgoing ACK = " + this.getRemoteTag()); sipStack.getStackLogger().logDebug("ack = " + ackRequest); } if (this.getLocalTag() != null) ackRequest.getFrom().setTag(this.getLocalTag()); if (this.getRemoteTag() != null) ackRequest.getTo().setTag(this.getRemoteTag()); } catch (ParseException ex) { throw new SipException(ex.getMessage()); } Hop hop = sipStack.getNextHop(ackRequest); // Hop hop = defaultRouter.getNextHop(ackRequest); if (hop == null) throw new SipException("No route!"); try { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("hop = " + hop); ListeningPointImpl lp = (ListeningPointImpl) this.sipProvider.getListeningPoint(hop .getTransport()); if (lp == null) throw new SipException("No listening point for this provider registered at " + hop); InetAddress inetAddress = InetAddress.getByName(hop.getHost()); MessageChannel messageChannel = lp.getMessageProcessor().createMessageChannel( inetAddress, hop.getPort()); boolean releaseAckSem = false; long cseqNo = ((SIPRequest)request).getCSeq().getSeqNumber(); if (!this.isAckSent(cseqNo)) { releaseAckSem = true; } this.setLastAckSent(ackRequest); messageChannel.sendMessage(ackRequest); // Sent atleast one ACK. this.isAcknowledged = true; this.highestSequenceNumberAcknowledged = Math.max(this.highestSequenceNumberAcknowledged, ((SIPRequest)ackRequest).getCSeq().getSeqNumber()); if (releaseAckSem && this.isBackToBackUserAgent) { this.releaseAckSem(); } else { if ( sipStack.isLoggingEnabled() ) { sipStack.getStackLogger().logDebug("Not releasing ack sem for " + this + " isAckSent " + releaseAckSem ); } } } catch (IOException ex) { if (throwIOExceptionAsSipException) throw new SipException("Could not send ack", ex); this.raiseIOException(hop.getHost(), hop.getPort(), hop.getTransport()); } catch (SipException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logException(ex); throw ex; } catch (Exception ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logException(ex); throw new SipException("Could not create message channel", ex); } if (this.dialogDeleteTask != null) { this.dialogDeleteTask.cancel(); this.dialogDeleteTask = null; } this.ackSeen = true; } // ///////////////////////////////////////////////////////////// // Package local methods // ///////////////////////////////////////////////////////////// /** * Set the stack address. Prevent us from routing messages to ourselves. * * @param sipStack the address of the SIP stack. * */ void setStack(SIPTransactionStack sipStack) { this.sipStack = sipStack; } /** * Get the stack . * * @return sipStack the SIP stack of the dialog. * */ SIPTransactionStack getStack() { return sipStack; } /** * Return True if this dialog is terminated on BYE. * */ boolean isTerminatedOnBye() { return this.terminateOnBye; } /** * Mark that the dialog has seen an ACK. */ void ackReceived(SIPRequest sipRequest) { // Suppress retransmission of the final response if (this.ackSeen) return; SIPServerTransaction tr = this.getInviteTransaction(); if (tr != null) { if (tr.getCSeq() == sipRequest.getCSeq().getSeqNumber()) { acquireTimerTaskSem(); try { if (this.timerTask != null) { this.timerTask.cancel(); this.timerTask = null; } } finally { releaseTimerTaskSem(); } this.ackSeen = true; if (this.dialogDeleteTask != null) { this.dialogDeleteTask.cancel(); this.dialogDeleteTask = null; } this.setLastAckReceived(sipRequest); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "ackReceived for " + ((SIPTransaction) tr).getMethod()); this.ackLine = sipStack.getStackLogger().getLineCount(); this.printDebugInfo(); } if (this.isBackToBackUserAgent) { this.releaseAckSem(); } this.setState(CONFIRMED_STATE); } } } /** * Return true if a terminated event was delivered to the application as a result of the * dialog termination. * */ synchronized boolean testAndSetIsDialogTerminatedEventDelivered() { boolean retval = this.dialogTerminatedEventDelivered; this.dialogTerminatedEventDelivered = true; return retval; } // ///////////////////////////////////////////////////////// // Public methods // ///////////////////////////////////////////////////////// /** * Adds a new event listener to this dialog. * * @param newListener * Listener to add. */ public void addEventListener(SIPDialogEventListener newListener) { eventListeners.add(newListener); } /** * Removed an event listener from this dialog. * * @param oldListener * Listener to remove. */ public void removeEventListener(SIPDialogEventListener oldListener) { eventListeners.remove(oldListener); } /* * @see javax.sip.Dialog#setApplicationData() */ public void setApplicationData(Object applicationData) { this.applicationData = applicationData; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getApplicationData() */ public Object getApplicationData() { return this.applicationData; } /** * Updates the next consumable seqno. * */ public synchronized void requestConsumed() { this.nextSeqno = Long.valueOf(this.getRemoteSeqNumber() + 1); if (sipStack.isLoggingEnabled()) { this.sipStack.getStackLogger().logDebug( "Request Consumed -- next consumable Request Seqno = " + this.nextSeqno); } } /** * Return true if this request can be consumed by the dialog. * * @param dialogRequest is the request to check with the dialog. * @return true if the dialogRequest sequence number matches the next consumable seqno. */ public synchronized boolean isRequestConsumable(SIPRequest dialogRequest) { // have not yet set remote seqno - this is a fresh if (dialogRequest.getMethod().equals(Request.ACK)) throw new RuntimeException("Illegal method"); // For loose validation this function is delegated to the application if (!this.isSequnceNumberValidation()) { return true; } // JvB: Acceptable iff remoteCSeq < cseq. remoteCSeq==-1 // when not defined yet, so that works too return remoteSequenceNumber < dialogRequest.getCSeq().getSeqNumber(); } /** * This method is called when a forked dialog is created from the client side. It starts a * timer task. If the timer task expires before an ACK is sent then the dialog is cancelled * (i.e. garbage collected ). * */ public void doDeferredDelete() { if (sipStack.getTimer() == null) this.setState(TERMINATED_STATE); else { this.dialogDeleteTask = new DialogDeleteTask(); // Delete the transaction after the max ack timeout. sipStack.getTimer().schedule(this.dialogDeleteTask, SIPTransaction.TIMER_H * SIPTransactionStack.BASE_TIMER_INTERVAL); } } /** * Set the state for this dialog. * * @param state is the state to set for the dialog. */ public void setState(int state) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "Setting dialog state for " + this + "newState = " + state); sipStack.getStackLogger().logStackTrace(); if (state != NULL_STATE && state != this.dialogState) if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( this + " old dialog state is " + this.getState()); sipStack.getStackLogger().logDebug( this + " New dialog state is " + DialogState.getObject(state)); } } this.dialogState = state; // Dialog is in terminated state set it up for GC. if (state == TERMINATED_STATE) { if (sipStack.getTimer() != null) { // may be null after shutdown sipStack.getTimer().schedule(new LingerTimer(), DIALOG_LINGER_TIME * 1000); } this.stopTimer(); } } /** * Debugging print for the dialog. */ public void printDebugInfo() { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("isServer = " + isServer()); sipStack.getStackLogger().logDebug("localTag = " + getLocalTag()); sipStack.getStackLogger().logDebug("remoteTag = " + getRemoteTag()); sipStack.getStackLogger().logDebug("localSequenceNumer = " + getLocalSeqNumber()); sipStack.getStackLogger().logDebug("remoteSequenceNumer = " + getRemoteSeqNumber()); sipStack.getStackLogger().logDebug("ackLine:" + this.getRemoteTag() + " " + ackLine); } } /** * Return true if the dialog has already seen the ack. * * @return flag that records if the ack has been seen. */ public boolean isAckSeen() { return this.ackSeen; } /** * Get the last ACK for this transaction. */ public SIPRequest getLastAckSent() { return this.lastAckSent; } /** * Return true if ACK was sent ( for client tx ). For server tx, this is a NO-OP ( we dont * send ACK). */ public boolean isAckSent(long cseqNo) { if (this.getLastTransaction() == null) return true; if (this.getLastTransaction() instanceof ClientTransaction) { if (this.getLastAckSent() == null) { return false; } else { return cseqNo <=((SIPRequest) this.getLastAckSent()).getCSeq().getSeqNumber(); } } else { return true; } } /** * Get the transaction that created this dialog. */ public Transaction getFirstTransaction() { return this.firstTransaction; } /** * Gets the route set for the dialog. When acting as an User Agent Server the route set MUST * be set to the list of URIs in the Record-Route header field from the request, taken in * order and preserving all URI parameters. When acting as an User Agent Client the route set * MUST be set to the list of URIs in the Record-Route header field from the response, taken * in reverse order and preserving all URI parameters. If no Record-Route header field is * present in the request or response, the route set MUST be set to the empty set. This route * set, even if empty, overrides any pre-existing route set for future requests in this * dialog. *

* Requests within a dialog MAY contain Record-Route and Contact header fields. However, these * requests do not cause the dialog's route set to be modified. *

* The User Agent Client uses the remote target and route set to build the Request-URI and * Route header field of the request. * * @return an Iterator containing a list of route headers to be used for forwarding. Empty * iterator is returned if route has not been established. */ public Iterator getRouteSet() { if (this.routeList == null) { return new LinkedList().listIterator(); } else { return this.getRouteList().listIterator(); } } /** * Add a Route list extracted from a SIPRequest to this Dialog. * * @param sipRequest */ public synchronized void addRoute(SIPRequest sipRequest) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "setContact: dialogState: " + this + "state = " + this.getState()); } if (this.dialogState == CONFIRMED_STATE && SIPRequest.isTargetRefresh(sipRequest.getMethod())) { this.doTargetRefresh(sipRequest); } if (this.dialogState == CONFIRMED_STATE || this.dialogState == TERMINATED_STATE) { return; } // Fix for issue #225: mustn't learn Route set from mid-dialog requests if ( sipRequest.getToTag()!=null ) return; // Incoming Request has the route list RecordRouteList rrlist = sipRequest.getRecordRouteHeaders(); // Add the route set from the incoming response in reverse // order if (rrlist != null) { this.addRoute(rrlist); } else { // Set the rotue list to the last seen route list. this.routeList = new RouteList(); } // put the contact header from the incoming request into // the route set. JvB: some duplication here, ref. doTargetRefresh ContactList contactList = sipRequest.getContactHeaders(); if (contactList != null) { this.setRemoteTarget((ContactHeader) contactList.getFirst()); } } /** * Set the dialog identifier. */ public void setDialogId(String dialogId) { this.dialogId = dialogId; } /** * Creates a new dialog based on a received NOTIFY. The dialog state is initialized * appropriately. The NOTIFY differs in the From tag * * Made this a separate method to clearly distinguish what's happening here - this is a * non-trivial case * * @param subscribeTx - the transaction started with the SUBSCRIBE that we sent * @param notifyST - the ServerTransaction created for an incoming NOTIFY * @return -- a new dialog created from the subscribe original SUBSCRIBE transaction. * * */ public static SIPDialog createFromNOTIFY(SIPClientTransaction subscribeTx, SIPTransaction notifyST) { SIPDialog d = new SIPDialog(notifyST); // // The above sets d.firstTransaction to NOTIFY (ST), correct that // d.serverTransactionFlag = false; // they share this one d.lastTransaction = subscribeTx; storeFirstTransactionInfo(d, subscribeTx); d.terminateOnBye = false; d.localSequenceNumber = subscribeTx.getCSeq(); SIPRequest not = (SIPRequest) notifyST.getRequest(); d.remoteSequenceNumber = not.getCSeq().getSeqNumber(); d.setDialogId(not.getDialogId(true)); d.setLocalTag(not.getToTag()); d.setRemoteTag(not.getFromTag()); // to properly create the Dialog object. // If not the stack will throw an exception when creating the response. d.setLastResponse(subscribeTx, subscribeTx.getLastResponse()); // Dont use setLocal / setRemote here, they make other assumptions d.localParty = not.getTo().getAddress(); d.remoteParty = not.getFrom().getAddress(); // initialize d's route set based on the NOTIFY. Any proxies must have // Record-Routed d.addRoute(not); d.setState(CONFIRMED_STATE); // set state, *after* setting route set! return d; } /** * Return true if is server. * * @return true if is server transaction created this dialog. */ public boolean isServer() { if (this.firstTransactionSeen == false) return this.serverTransactionFlag; else return this.firstTransactionIsServerTransaction; } /** * Return true if this is a re-establishment of the dialog. * * @return true if the reInvite flag is set. */ protected boolean isReInvite() { return this.reInviteFlag; } /** * Get the id for this dialog. * * @return the string identifier for this dialog. * */ public String getDialogId() { if (this.dialogId == null && this.lastResponse != null) this.dialogId = this.lastResponse.getDialogId(isServer()); return this.dialogId; } private static void storeFirstTransactionInfo(SIPDialog dialog, SIPTransaction transaction) { dialog.firstTransaction = transaction; dialog.firstTransactionSeen = true; dialog.firstTransactionIsServerTransaction = transaction.isServerTransaction(); dialog.firstTransactionSecure = transaction.getRequest().getRequestURI().getScheme() .equalsIgnoreCase("sips"); dialog.firstTransactionPort = transaction.getPort(); dialog.firstTransactionId = transaction.getBranchId(); dialog.firstTransactionMethod = transaction.getMethod(); if (dialog.isServer()) { SIPServerTransaction st = (SIPServerTransaction) transaction; SIPResponse response = st.getLastResponse(); dialog.contactHeader = response != null ? response.getContactHeader() : null; } else { SIPClientTransaction ct = (SIPClientTransaction) transaction; if (ct != null){ SIPRequest sipRequest = ct.getOriginalRequest(); dialog.contactHeader = sipRequest.getContactHeader(); } } } /** * Add a transaction record to the dialog. * * @param transaction is the transaction to add to the dialog. */ public void addTransaction(SIPTransaction transaction) { SIPRequest sipRequest = (SIPRequest) transaction.getOriginalRequest(); // Proessing a re-invite. if (firstTransactionSeen && !firstTransactionId.equals(transaction.getBranchId()) && transaction.getMethod().equals(firstTransactionMethod)) { this.reInviteFlag = true; } if (firstTransactionSeen == false) { // Record the local and remote sequenc // numbers and the from and to tags for future // use on this dialog. storeFirstTransactionInfo(this, transaction); if (sipRequest.getMethod().equals(Request.SUBSCRIBE)) this.eventHeader = (EventHeader) sipRequest.getHeader(EventHeader.NAME); this.setLocalParty(sipRequest); this.setRemoteParty(sipRequest); this.setCallId(sipRequest); if (this.originalRequest == null) { this.originalRequest = sipRequest; } if (this.method == null) { this.method = sipRequest.getMethod(); } if (transaction instanceof SIPServerTransaction) { this.hisTag = sipRequest.getFrom().getTag(); // My tag is assigned when sending response } else { setLocalSequenceNumber(sipRequest.getCSeq().getSeqNumber()); this.originalLocalSequenceNumber = localSequenceNumber; this.myTag = sipRequest.getFrom().getTag(); if (myTag == null) if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError( "The request's From header is missing the required Tag parameter."); } } else if (transaction.getMethod().equals(firstTransactionMethod) && firstTransactionIsServerTransaction != transaction.isServerTransaction()) { // This case occurs when you are processing a re-invite. // Switch from client side to server side for re-invite // (put the other side on hold). storeFirstTransactionInfo(this, transaction); this.setLocalParty(sipRequest); this.setRemoteParty(sipRequest); this.setCallId(sipRequest); this.originalRequest = sipRequest; this.method = sipRequest.getMethod(); } if (transaction instanceof SIPServerTransaction) setRemoteSequenceNumber(sipRequest.getCSeq().getSeqNumber()); // If this is a server transaction record the remote // sequence number to avoid re-processing of requests // with the same sequence number directed towards this // dialog. this.lastTransaction = transaction; // set a back ptr in the incoming dialog. // CHECKME -- why is this here? // transaction.setDialog(this,sipRequest); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger() .logDebug("Transaction Added " + this + myTag + "/" + hisTag); sipStack.getStackLogger().logDebug( "TID = " + transaction.getTransactionId() + "/" + transaction.isServerTransaction()); sipStack.getStackLogger().logStackTrace(); } } /** * Set the remote tag. * * @param hisTag is the remote tag to set. */ private void setRemoteTag(String hisTag) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "setRemoteTag(): " + this + " remoteTag = " + this.hisTag + " new tag = " + hisTag); } if (this.hisTag != null && hisTag != null && !hisTag.equals(this.hisTag)) { if (this.getState() != DialogState.EARLY) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "Dialog is already established -- ignoring remote tag re-assignment"); return; } else if (sipStack.isRemoteTagReassignmentAllowed()) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "UNSAFE OPERATION ! tag re-assignment " + this.hisTag + " trying to set to " + hisTag + " can cause unexpected effects "); boolean removed = false; if (this.sipStack.getDialog(dialogId) == this) { this.sipStack.removeDialog(dialogId); removed = true; } // Force recomputation of Dialog ID; this.dialogId = null; this.hisTag = hisTag; if (removed) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("ReInserting Dialog"); this.sipStack.putDialog(this); } } } else { if (hisTag != null) { this.hisTag = hisTag; } else { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logWarning("setRemoteTag : called with null argument "); } } } /** * Get the last transaction from the dialog. */ public SIPTransaction getLastTransaction() { return this.lastTransaction; } /** * Get the INVITE transaction (null if no invite transaction). */ public SIPServerTransaction getInviteTransaction() { DialogTimerTask t = this.timerTask; if (t != null) return t.transaction; else return null; } /** * Set the local sequece number for the dialog (defaults to 1 when the dialog is created). * * @param lCseq is the local cseq number. * */ private void setLocalSequenceNumber(long lCseq) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "setLocalSequenceNumber: original " + this.localSequenceNumber + " new = " + lCseq); if (lCseq <= this.localSequenceNumber) throw new RuntimeException("Sequence number should not decrease !"); this.localSequenceNumber = lCseq; } /** * Set the remote sequence number for the dialog. * * @param rCseq is the remote cseq number. * */ public void setRemoteSequenceNumber(long rCseq) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("setRemoteSeqno " + this + "/" + rCseq); this.remoteSequenceNumber = rCseq; } /** * Increment the local CSeq # for the dialog. This is useful for if you want to create a hole * in the sequence number i.e. route a request outside the dialog and then resume within the * dialog. */ public void incrementLocalSequenceNumber() { ++this.localSequenceNumber; } /** * Get the remote sequence number (for cseq assignment of outgoing requests within this * dialog). * * @deprecated * @return local sequence number. */ public int getRemoteSequenceNumber() { return (int) this.remoteSequenceNumber; } /** * Get the local sequence number (for cseq assignment of outgoing requests within this * dialog). * * @deprecated * @return local sequence number. */ public int getLocalSequenceNumber() { return (int) this.localSequenceNumber; } /** * Get the sequence number for the request that origianlly created the Dialog. * * @return -- the original starting sequence number for this dialog. */ public long getOriginalLocalSequenceNumber() { return this.originalLocalSequenceNumber; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getLocalSequenceNumberLong() */ public long getLocalSeqNumber() { return this.localSequenceNumber; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getRemoteSequenceNumberLong() */ public long getRemoteSeqNumber() { return this.remoteSequenceNumber; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getLocalTag() */ public String getLocalTag() { return this.myTag; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getRemoteTag() */ public String getRemoteTag() { return hisTag; } /** * Set local tag for the transaction. * * @param mytag is the tag to use in From headers client transactions that belong to this * dialog and for generating To tags for Server transaction requests that belong to * this dialog. */ private void setLocalTag(String mytag) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("set Local tag " + mytag + " " + this.dialogId); sipStack.getStackLogger().logStackTrace(); } this.myTag = mytag; } /* * (non-Javadoc) * * @see javax.sip.Dialog#delete() */ public void delete() { // the reaper will get him later. this.setState(TERMINATED_STATE); } /* * (non-Javadoc) * * @see javax.sip.Dialog#getCallId() */ public CallIdHeader getCallId() { return this.callIdHeader; } /** * set the call id header for this dialog. */ private void setCallId(SIPRequest sipRequest) { this.callIdHeader = sipRequest.getCallId(); } /* * (non-Javadoc) * * @see javax.sip.Dialog#getLocalParty() */ public javax.sip.address.Address getLocalParty() { return this.localParty; } private void setLocalParty(SIPMessage sipMessage) { if (!isServer()) { this.localParty = sipMessage.getFrom().getAddress(); } else { this.localParty = sipMessage.getTo().getAddress(); } } /** * Returns the Address identifying the remote party. This is the value of the To header of * locally initiated requests in this dialogue when acting as an User Agent Client. *

* This is the value of the From header of recieved responses in this dialogue when acting as * an User Agent Server. * * @return the address object of the remote party. */ public javax.sip.address.Address getRemoteParty() { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("gettingRemoteParty " + this.remoteParty); } return this.remoteParty; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getRemoteTarget() */ public javax.sip.address.Address getRemoteTarget() { return this.remoteTarget; } /* * (non-Javadoc) * * @see javax.sip.Dialog#getState() */ public DialogState getState() { if (this.dialogState == NULL_STATE) return null; // not yet initialized return DialogState.getObject(this.dialogState); } /** * Returns true if this Dialog is secure i.e. if the request arrived over TLS, and the * Request-URI contained a SIPS URI, the "secure" flag is set to TRUE. * * @return true if this dialogue was established using a sips URI over TLS, and * false otherwise. */ public boolean isSecure() { return this.firstTransactionSecure; } /* * (non-Javadoc) * * @see javax.sip.Dialog#sendAck(javax.sip.message.Request) */ public void sendAck(Request request) throws SipException { this.sendAck(request, true); } /* * (non-Javadoc) * * @see javax.sip.Dialog#createRequest(java.lang.String) */ public Request createRequest(String method) throws SipException { if (method.equals(Request.ACK) || method.equals(Request.PRACK)) { throw new SipException("Invalid method specified for createRequest:" + method); } if (lastResponse != null) return this.createRequest(method, this.lastResponse); else throw new SipException("Dialog not yet established -- no response!"); } /** * The method that actually does the work of creating a request. * * @param method * @param response * @return * @throws SipException */ private Request createRequest(String method, SIPResponse sipResponse) throws SipException { /* * Check if the dialog is in the right state (RFC 3261 section 15). The caller's UA MAY * send a BYE for either CONFIRMED or EARLY dialogs, and the callee's UA MAY send a BYE on * CONFIRMED dialogs, but MUST NOT send a BYE on EARLY dialogs. * * Throw out cancel request. */ if (method == null || sipResponse == null) throw new NullPointerException("null argument"); if (method.equals(Request.CANCEL)) throw new SipException("Dialog.createRequest(): Invalid request"); if (this.getState() == null || (this.getState().getValue() == TERMINATED_STATE && !method .equalsIgnoreCase(Request.BYE)) || (this.isServer() && this.getState().getValue() == EARLY_STATE && method .equalsIgnoreCase(Request.BYE))) throw new SipException("Dialog " + getDialogId() + " not yet established or terminated " + this.getState()); SipUri sipUri = null; if (this.getRemoteTarget() != null) sipUri = (SipUri) this.getRemoteTarget().getURI().clone(); else { sipUri = (SipUri) this.getRemoteParty().getURI().clone(); sipUri.clearUriParms(); } CSeq cseq = new CSeq(); try { cseq.setMethod(method); cseq.setSeqNumber(this.getLocalSeqNumber()); } catch (Exception ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("Unexpected error"); InternalErrorHandler.handleException(ex); } /* * Add a via header for the outbound request based on the transport of the message * processor. */ ListeningPointImpl lp = (ListeningPointImpl) this.sipProvider .getListeningPoint(sipResponse.getTopmostVia().getTransport()); if (lp == null) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError( "Cannot find listening point for transport " + sipResponse.getTopmostVia().getTransport()); throw new SipException("Cannot find listening point for transport " + sipResponse.getTopmostVia().getTransport()); } Via via = lp.getViaHeader(); From from = new From(); from.setAddress(this.localParty); To to = new To(); to.setAddress(this.remoteParty); SIPRequest sipRequest = sipResponse.createRequest(sipUri, via, cseq, from, to); /* * The default contact header is obtained from the provider. The application can override * this. * * JvB: Should only do this for target refresh requests, ie not for BYE, PRACK, etc */ if (SIPRequest.isTargetRefresh(method)) { ContactHeader contactHeader = ((ListeningPointImpl) this.sipProvider .getListeningPoint(lp.getTransport())).createContactHeader(); ((SipURI) contactHeader.getAddress().getURI()).setSecure(this.isSecure()); sipRequest.setHeader(contactHeader); } try { /* * Guess of local sequence number - this is being re-set when the request is actually * dispatched */ cseq = (CSeq) sipRequest.getCSeq(); cseq.setSeqNumber(this.localSequenceNumber + 1); } catch (InvalidArgumentException ex) { InternalErrorHandler.handleException(ex); } if (method.equals(Request.SUBSCRIBE)) { if (eventHeader != null) sipRequest.addHeader(eventHeader); } /* * RFC3261, section 12.2.1.1: * * The URI in the To field of the request MUST be set to the remote URI from the dialog * state. The tag in the To header field of the request MUST be set to the remote tag of * the dialog ID. The From URI of the request MUST be set to the local URI from the dialog * state. The tag in the From header field of the request MUST be set to the local tag of * the dialog ID. If the value of the remote or local tags is null, the tag parameter MUST * be omitted from the To or From header fields, respectively. */ try { if (this.getLocalTag() != null) { from.setTag(this.getLocalTag()); } else { from.removeTag(); } if (this.getRemoteTag() != null) { to.setTag(this.getRemoteTag()); } else { to.removeTag(); } } catch (ParseException ex) { InternalErrorHandler.handleException(ex); } // get the route list from the dialog. this.updateRequest(sipRequest); return sipRequest; } /* * (non-Javadoc) * * @see javax.sip.Dialog#sendRequest(javax.sip.ClientTransaction) */ public void sendRequest(ClientTransaction clientTransactionId) throws TransactionDoesNotExistException, SipException { this.sendRequest(clientTransactionId, !this.isBackToBackUserAgent); } public void sendRequest(ClientTransaction clientTransactionId, boolean allowInterleaving) throws TransactionDoesNotExistException, SipException { if ( (!allowInterleaving) && clientTransactionId.getRequest().getMethod().equals(Request.INVITE)) { new Thread((new ReInviteSender(clientTransactionId))).start(); return; } SIPRequest dialogRequest = ((SIPClientTransaction) clientTransactionId) .getOriginalRequest(); if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "dialog.sendRequest " + " dialog = " + this + "\ndialogRequest = \n" + dialogRequest); if (clientTransactionId == null) throw new NullPointerException("null parameter"); if (dialogRequest.getMethod().equals(Request.ACK) || dialogRequest.getMethod().equals(Request.CANCEL)) throw new SipException("Bad Request Method. " + dialogRequest.getMethod()); // JvB: added, allow re-sending of BYE after challenge if (byeSent && isTerminatedOnBye() && !dialogRequest.getMethod().equals(Request.BYE)) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logError("BYE already sent for " + this); throw new SipException("Cannot send request; BYE already sent"); } if (dialogRequest.getTopmostVia() == null) { Via via = ((SIPClientTransaction) clientTransactionId).getOutgoingViaHeader(); dialogRequest.addHeader(via); } if (!this.getCallId().getCallId().equalsIgnoreCase(dialogRequest.getCallId().getCallId())) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logError("CallID " + this.getCallId()); sipStack.getStackLogger().logError( "RequestCallID = " + dialogRequest.getCallId().getCallId()); sipStack.getStackLogger().logError("dialog = " + this); } throw new SipException("Bad call ID in request"); } // Set the dialog back pointer. ((SIPClientTransaction) clientTransactionId).setDialog(this, this.dialogId); this.addTransaction((SIPTransaction) clientTransactionId); // Enable the retransmission filter for the transaction ((SIPClientTransaction) clientTransactionId).isMapped = true; From from = (From) dialogRequest.getFrom(); To to = (To) dialogRequest.getTo(); // Caller already did the tag assignment -- check to see if the // tag assignment is OK. if (this.getLocalTag() != null && from.getTag() != null && !from.getTag().equals(this.getLocalTag())) throw new SipException("From tag mismatch expecting " + this.getLocalTag()); if (this.getRemoteTag() != null && to.getTag() != null && !to.getTag().equals(this.getRemoteTag())) { if (sipStack.isLoggingEnabled()) this.sipStack.getStackLogger().logWarning( "To header tag mismatch expecting " + this.getRemoteTag()); } /* * The application is sending a NOTIFY before sending the response of the dialog. */ if (this.getLocalTag() == null && dialogRequest.getMethod().equals(Request.NOTIFY)) { if (!this.getMethod().equals(Request.SUBSCRIBE)) throw new SipException("Trying to send NOTIFY without SUBSCRIBE Dialog!"); this.setLocalTag(from.getTag()); } try { if (this.getLocalTag() != null) from.setTag(this.getLocalTag()); if (this.getRemoteTag() != null) to.setTag(this.getRemoteTag()); } catch (ParseException ex) { InternalErrorHandler.handleException(ex); } Hop hop = ((SIPClientTransaction) clientTransactionId).getNextHop(); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "Using hop = " + hop.getHost() + " : " + hop.getPort()); } try { MessageChannel messageChannel = sipStack.createRawMessageChannel(this .getSipProvider().getListeningPoint(hop.getTransport()).getIPAddress(), this.firstTransactionPort, hop); MessageChannel oldChannel = ((SIPClientTransaction) clientTransactionId).getMessageChannel(); // Remove this from the connection cache if it is in the // connection // cache and is not yet active. oldChannel.uncache(); // Not configured to cache client connections. if (!sipStack.cacheClientConnections) { oldChannel.useCount--; if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "oldChannel: useCount " + oldChannel.useCount); } if (messageChannel == null) { /* * At this point the procedures of 8.1.2 and 12.2.1.1 of RFC3261 have been tried * but the resulting next hop cannot be resolved (recall that the exception thrown * is caught and ignored in SIPStack.createMessageChannel() so we end up here with * a null messageChannel instead of the exception handler below). All else * failing, try the outbound proxy in accordance with 8.1.2, in particular: This * ensures that outbound proxies that do not add Record-Route header field values * will drop out of the path of subsequent requests. It allows endpoints that * cannot resolve the first Route URI to delegate that task to an outbound proxy. * * if one considers the 'first Route URI' of a request constructed according to * 12.2.1.1 to be the request URI when the route set is empty. */ if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "Null message channel using outbound proxy !"); Hop outboundProxy = sipStack.getRouter(dialogRequest).getOutboundProxy(); if (outboundProxy == null) throw new SipException("No route found! hop=" + hop); messageChannel = sipStack.createRawMessageChannel(this.getSipProvider() .getListeningPoint(outboundProxy.getTransport()).getIPAddress(), this.firstTransactionPort, outboundProxy); if (messageChannel != null) ((SIPClientTransaction) clientTransactionId) .setEncapsulatedChannel(messageChannel); } else { ((SIPClientTransaction) clientTransactionId) .setEncapsulatedChannel(messageChannel); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("using message channel " + messageChannel); } } if (messageChannel != null) messageChannel.useCount++; // See if we need to release the previously mapped channel. if ((!sipStack.cacheClientConnections) && oldChannel != null && oldChannel.useCount <= 0) oldChannel.close(); } catch (Exception ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logException(ex); throw new SipException("Could not create message channel", ex); } try { // Increment before setting!! localSequenceNumber++; dialogRequest.getCSeq().setSeqNumber(getLocalSeqNumber()); } catch (InvalidArgumentException ex) { sipStack.getStackLogger().logFatalError(ex.getMessage()); } try { ((SIPClientTransaction) clientTransactionId).sendMessage(dialogRequest); /* * Note that if the BYE is rejected then the Dialog should bo back to the ESTABLISHED * state so we only set state after successful send. */ if (dialogRequest.getMethod().equals(Request.BYE)) { this.byeSent = true; /* * Dialog goes into TERMINATED state as soon as BYE is sent. ISSUE 182. */ if (isTerminatedOnBye()) { this.setState(DialogState._TERMINATED); } } } catch (IOException ex) { throw new SipException("error sending message", ex); } } /** * Return yes if the last response is to be retransmitted. */ private boolean toRetransmitFinalResponse(int T2) { if (--retransmissionTicksLeft == 0) { if (2 * prevRetransmissionTicks <= T2) this.retransmissionTicksLeft = 2 * prevRetransmissionTicks; else this.retransmissionTicksLeft = prevRetransmissionTicks; this.prevRetransmissionTicks = retransmissionTicksLeft; return true; } else return false; } protected void setRetransmissionTicks() { this.retransmissionTicksLeft = 1; this.prevRetransmissionTicks = 1; } /** * Resend the last ack. */ public void resendAck() throws SipException { // Check for null. if (this.getLastAckSent() != null) { if (getLastAckSent().getHeader(TimeStampHeader.NAME) != null && sipStack.generateTimeStampHeader) { TimeStamp ts = new TimeStamp(); try { ts.setTimeStamp(System.currentTimeMillis()); getLastAckSent().setHeader(ts); } catch (InvalidArgumentException e) { } } this.sendAck(getLastAckSent(), false); } } /** * Get the method of the request/response that resulted in the creation of the Dialog. * * @return -- the method of the dialog. */ public String getMethod() { // Method of the request or response used to create this dialog return this.method; } /** * Start the dialog timer. * * @param transaction */ protected void startTimer(SIPServerTransaction transaction) { if (this.timerTask != null && timerTask.transaction == transaction) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Timer already running for " + getDialogId()); return; } if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Starting dialog timer for " + getDialogId()); this.ackSeen = false; acquireTimerTaskSem(); try { if (this.timerTask != null) { this.timerTask.transaction = transaction; } else { this.timerTask = new DialogTimerTask(transaction); sipStack.getTimer().schedule(timerTask, SIPTransactionStack.BASE_TIMER_INTERVAL, SIPTransactionStack.BASE_TIMER_INTERVAL); } } finally { releaseTimerTaskSem(); } this.setRetransmissionTicks(); } /** * Stop the dialog timer. This is called when the dialog is terminated. * */ protected void stopTimer() { try { acquireTimerTaskSem(); try { if (this.timerTask != null) { this.timerTask.cancel(); this.timerTask = null; } } finally { releaseTimerTaskSem(); } } catch (Exception ex) { } } /* * (non-Javadoc) Retransmissions of the reliable provisional response cease when a matching * PRACK is received by the UA core. PRACK is like any other request within a dialog, and the * UAS core processes it according to the procedures of Sections 8.2 and 12.2.2 of RFC 3261. 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. * * @see javax.sip.Dialog#createPrack(javax.sip.message.Response) */ public Request createPrack(Response relResponse) throws DialogDoesNotExistException, SipException { if (this.getState() == null || this.getState().equals(DialogState.TERMINATED)) throw new DialogDoesNotExistException("Dialog not initialized or terminated"); if ((RSeq) relResponse.getHeader(RSeqHeader.NAME) == null) { throw new SipException("Missing RSeq Header"); } try { SIPResponse sipResponse = (SIPResponse) relResponse; SIPRequest sipRequest = (SIPRequest) this.createRequest(Request.PRACK, (SIPResponse) relResponse); String toHeaderTag = sipResponse.getTo().getTag(); sipRequest.setToTag(toHeaderTag); RAck rack = new RAck(); RSeq rseq = (RSeq) relResponse.getHeader(RSeqHeader.NAME); rack.setMethod(sipResponse.getCSeq().getMethod()); rack.setCSequenceNumber((int) sipResponse.getCSeq().getSeqNumber()); rack.setRSequenceNumber(rseq.getSeqNumber()); sipRequest.setHeader(rack); return (Request) sipRequest; } catch (Exception ex) { InternalErrorHandler.handleException(ex); return null; } } private void updateRequest(SIPRequest sipRequest) { RouteList rl = this.getRouteList(); if (rl.size() > 0) { sipRequest.setHeader(rl); } else { sipRequest.removeHeader(RouteHeader.NAME); } if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { sipRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); } } /* * (non-Javadoc) The UAC core MUST generate an ACK request for each 2xx received from the * transaction layer. The header fields of the ACK are constructed in the same way as for any * request sent within a dialog (see Section 12) with the exception of the CSeq and the header * fields related to authentication. The sequence number of the CSeq header field MUST be the * same as the INVITE being acknowledged, but the CSeq method MUST be ACK. The ACK MUST * contain the same credentials as the INVITE. If the 2xx contains an offer (based on the * rules above), the ACK MUST carry an answer in its body. If the offer in the 2xx response is * not acceptable, the UAC core MUST generate a valid answer in the ACK and then send a BYE * immediately. * * Note that for the case of forked requests, you can create multiple outgoing invites each * with a different cseq and hence you need to supply the invite. * * @see javax.sip.Dialog#createAck(long) */ public Request createAck(long cseqno) throws InvalidArgumentException, SipException { // JvB: strictly speaking it is allowed to start a dialog with // SUBSCRIBE, // then send INVITE+ACK later on if (!method.equals(Request.INVITE)) throw new SipException("Dialog was not created with an INVITE" + method); if (cseqno <= 0) throw new InvalidArgumentException("bad cseq <= 0 "); else if (cseqno > ((((long) 1) << 32) - 1)) throw new InvalidArgumentException("bad cseq > " + ((((long) 1) << 32) - 1)); if (this.remoteTarget == null) { throw new SipException("Cannot create ACK - no remote Target!"); } if (this.sipStack.isLoggingEnabled()) { this.sipStack.getStackLogger().logDebug("createAck " + this + " cseqno " + cseqno); } // MUST ack in the same order that the OKs were received. This traps // out of order ACK sending. Old ACKs seqno's can always be ACKed. if (lastInviteOkReceived < cseqno) { if (sipStack.isLoggingEnabled()) { this.sipStack.getStackLogger().logDebug( "WARNING : Attempt to crete ACK without OK " + this); this.sipStack.getStackLogger().logDebug("LAST RESPONSE = " + this.lastResponse); } throw new SipException("Dialog not yet established -- no OK response!"); } try { // JvB: Transport from first entry in route set, or remote Contact // if none // Only used to find correct LP & create correct Via SipURI uri4transport = null; if (this.routeList != null && !this.routeList.isEmpty()) { Route r = (Route) this.routeList.getFirst(); uri4transport = ((SipURI) r.getAddress().getURI()); } else { // should be !=null, checked above uri4transport = ((SipURI) this.remoteTarget.getURI()); } String transport = uri4transport.getTransportParam(); if (transport == null) { // JvB fix: also support TLS transport = uri4transport.isSecure() ? ListeningPoint.TLS : ListeningPoint.UDP; } ListeningPointImpl lp = (ListeningPointImpl) sipProvider.getListeningPoint(transport); if (lp == null) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logError( "remoteTargetURI " + this.remoteTarget.getURI()); sipStack.getStackLogger().logError("uri4transport = " + uri4transport); sipStack.getStackLogger().logError("No LP found for transport=" + transport); } throw new SipException( "Cannot create ACK - no ListeningPoint for transport towards next hop found:" + transport); } SIPRequest sipRequest = new SIPRequest(); sipRequest.setMethod(Request.ACK); sipRequest.setRequestURI((SipUri) getRemoteTarget().getURI().clone()); sipRequest.setCallId(this.callIdHeader); sipRequest.setCSeq(new CSeq(cseqno, Request.ACK)); List vias = new ArrayList(); // Via via = lp.getViaHeader(); // The user may have touched the sentby for the response. // so use the via header extracted from the response for the ACK => // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=205 // strip the params from the via of the response and use the params from the // original request Via via = this.lastResponse.getTopmostVia(); via.removeParameters(); if (originalRequest != null && originalRequest.getTopmostVia() != null) { NameValueList originalRequestParameters = originalRequest.getTopmostVia() .getParameters(); if (originalRequestParameters != null && originalRequestParameters.size() > 0) { via.setParameters((NameValueList) originalRequestParameters.clone()); } } via.setBranch(Utils.getInstance().generateBranchId()); // new branch vias.add(via); sipRequest.setVia(vias); From from = new From(); from.setAddress(this.localParty); from.setTag(this.myTag); sipRequest.setFrom(from); To to = new To(); to.setAddress(this.remoteParty); if (hisTag != null) to.setTag(this.hisTag); sipRequest.setTo(to); sipRequest.setMaxForwards(new MaxForwards(70)); if (this.originalRequest != null) { Authorization authorization = this.originalRequest.getAuthorization(); if (authorization != null) sipRequest.setHeader(authorization); } // ACKs for 2xx responses // use the Route values learned from the Record-Route of the 2xx // responses. this.updateRequest(sipRequest); return sipRequest; } catch (Exception ex) { InternalErrorHandler.handleException(ex); throw new SipException("unexpected exception ", ex); } } /** * Get the provider for this Dialog. * * SPEC_REVISION * * @return -- the SIP Provider associated with this transaction. */ public SipProviderImpl getSipProvider() { return this.sipProvider; } /** * @param sipProvider the sipProvider to set */ public void setSipProvider(SipProviderImpl sipProvider) { this.sipProvider = sipProvider; } /** * Check the tags of the response against the tags of the Dialog. Return true if the respnse * matches the tags of the dialog. We do this check wehn sending out a response. * * @param sipResponse -- the response to check. * */ public void setResponseTags(SIPResponse sipResponse) { if (this.getLocalTag() != null || this.getRemoteTag() != null) { return; } String responseFromTag = sipResponse.getFromTag(); if ( responseFromTag != null ) { if (responseFromTag.equals(this.getLocalTag())) { sipResponse.setToTag(this.getRemoteTag()); } else if (responseFromTag.equals(this.getRemoteTag())) { sipResponse.setToTag(this.getLocalTag()); } } else { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logWarning("No from tag in response! Not RFC 3261 compatible."); } } /** * Set the last response for this dialog. This method is called for updating the dialog state * when a response is either sent or received from within a Dialog. * * @param transaction -- the transaction associated with the response * @param sipResponse -- the last response to set. */ public void setLastResponse(SIPTransaction transaction, SIPResponse sipResponse) { this.callIdHeader = sipResponse.getCallId(); int statusCode = sipResponse.getStatusCode(); if (statusCode == 100) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logWarning( "Invalid status code - 100 in setLastResponse - ignoring"); return; } this.lastResponse = sipResponse; this.setAssigned(); // Adjust state of the Dialog state machine. if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "sipDialog: setLastResponse:" + this + " lastResponse = " + this.lastResponse.getFirstLine()); } if (this.getState() == DialogState.TERMINATED) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "sipDialog: setLastResponse -- dialog is terminated - ignoring "); } // Capture the OK response for later use in createAck // This is handy for late arriving OK's that we want to ACK. if (sipResponse.getCSeq().getMethod().equals(Request.INVITE) && statusCode == 200) { this.lastInviteOkReceived = Math.max(sipResponse.getCSeq().getSeqNumber(), this.lastInviteOkReceived); } return; } String cseqMethod = sipResponse.getCSeq().getMethod(); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logStackTrace(); sipStack.getStackLogger().logDebug("cseqMethod = " + cseqMethod); sipStack.getStackLogger().logDebug("dialogState = " + this.getState()); sipStack.getStackLogger().logDebug("method = " + this.getMethod()); sipStack.getStackLogger().logDebug("statusCode = " + statusCode); sipStack.getStackLogger().logDebug("transaction = " + transaction); } // JvB: don't use "!this.isServer" here // note that the transaction can be null for forked // responses. if (transaction == null || transaction instanceof ClientTransaction) { if (sipStack.isDialogCreated(cseqMethod)) { // Make a final tag assignment. if (getState() == null && (statusCode / 100 == 1)) { /* * Guard aginst slipping back into early state from confirmed state. */ // Was (sipResponse.getToTag() != null || sipStack.rfc2543Supported) setState(SIPDialog.EARLY_STATE); if ((sipResponse.getToTag() != null || sipStack.rfc2543Supported) && this.getRemoteTag() == null) { setRemoteTag(sipResponse.getToTag()); this.setDialogId(sipResponse.getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); } } else if (getState() != null && getState().equals(DialogState.EARLY) && statusCode / 100 == 1) { /* * This case occurs for forked dialog responses. The To tag can change as a * result of the forking. The remote target can also change as a result of the * forking. */ if (cseqMethod.equals(getMethod()) && transaction != null && (sipResponse.getToTag() != null || sipStack.rfc2543Supported)) { setRemoteTag(sipResponse.getToTag()); this.setDialogId(sipResponse.getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); } } else if (statusCode / 100 == 2) { // This is a dialog creating method (such as INVITE). // 2xx response -- set the state to the confirmed // state. To tag is MANDATORY for the response. // Only do this if method equals initial request! if (cseqMethod.equals(getMethod()) && (sipResponse.getToTag() != null || sipStack.rfc2543Supported) && this.getState() != DialogState.CONFIRMED) { setRemoteTag(sipResponse.getToTag()); this.setDialogId(sipResponse.getDialogId(false)); sipStack.putDialog(this); this.addRoute(sipResponse); setState(SIPDialog.CONFIRMED_STATE); } // Capture the OK response for later use in createAck if (cseqMethod.equals(Request.INVITE)) { this.lastInviteOkReceived = Math.max(sipResponse.getCSeq().getSeqNumber(), this.lastInviteOkReceived); } } else if (statusCode >= 300 && statusCode <= 699 && (getState() == null || (cseqMethod.equals(getMethod()) && getState() .getValue() == SIPDialog.EARLY_STATE))) { /* * This case handles 3xx, 4xx, 5xx and 6xx responses. RFC 3261 Section 12.3 - * dialog termination. Independent of the method, if a request outside of a * dialog generates a non-2xx final response, any early dialogs created * through provisional responses to that request are terminated. */ setState(SIPDialog.TERMINATED_STATE); } /* * This code is in support of "proxy" servers that are constructed as back to back * user agents. This could be a dialog in the middle of the call setup path * somewhere. Hence the incoming invite has record route headers in it. The * response will have additional record route headers. However, for this dialog * only the downstream record route headers matter. Ideally proxy servers should * not be constructed as Back to Back User Agents. Remove all the record routes * that are present in the incoming INVITE so you only have the downstream Route * headers present in the dialog. Note that for an endpoint - you will have no * record route headers present in the original request so the loop will not * execute. */ if ( this.getState() != DialogState.CONFIRMED && this.getState() != DialogState.TERMINATED ) { if (originalRequest != null) { RecordRouteList rrList = originalRequest.getRecordRouteHeaders(); if (rrList != null) { ListIterator it = rrList.listIterator(rrList.size()); while (it.hasPrevious()) { RecordRoute rr = (RecordRoute) it.previous(); Route route = (Route) routeList.getFirst(); if (route != null && rr.getAddress().equals(route.getAddress())) { routeList.removeFirst(); } else break; } } } } } else if (cseqMethod.equals(Request.NOTIFY) && (this.getMethod().equals(Request.SUBSCRIBE) || this.getMethod().equals( Request.REFER)) && sipResponse.getStatusCode() / 100 == 2 && this.getState() == null) { // This is a notify response. this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); this.setState(SIPDialog.CONFIRMED_STATE); } else if (cseqMethod.equals(Request.BYE) && statusCode / 100 == 2 && isTerminatedOnBye()) { // Dialog will be terminated when the transction is terminated. setState(SIPDialog.TERMINATED_STATE); } } else { // Processing Server Dialog. if (cseqMethod.equals(Request.BYE) && statusCode / 100 == 2 && this.isTerminatedOnBye()) { /* * Only transition to terminated state when 200 OK is returned for the BYE. Other * status codes just result in leaving the state in COMPLETED state. */ this.setState(SIPDialog.TERMINATED_STATE); } else { boolean doPutDialog = false; if (getLocalTag() == null && sipResponse.getTo().getTag() != null && sipStack.isDialogCreated(cseqMethod) && cseqMethod.equals(getMethod())) { setLocalTag(sipResponse.getTo().getTag()); doPutDialog = true; } if (statusCode / 100 != 2) { if (statusCode / 100 == 1) { if (doPutDialog) { setState(SIPDialog.EARLY_STATE); this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); } } else { /* * RFC 3265 chapter 3.1.4.1 "Non-200 class final responses indicate that * no subscription or dialog has been created, and no subsequent NOTIFY * message will be sent. All non-200 class" + responses (with the * exception of "489", described herein) have the same meanings and * handling as described in SIP" */ // Bug Fix by Jens tinfors // see https://jain-sip.dev.java.net/servlets/ReadMsg?list=users&msgNo=797 if (statusCode == 489 && (cseqMethod.equals(Request.NOTIFY) || cseqMethod .equals(Request.SUBSCRIBE))) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "RFC 3265 : Not setting dialog to TERMINATED for 489"); } else { // baranowb: simplest fix to // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=175 // application is responsible for terminating in this case // see rfc 5057 for better explanation if (!this.isReInvite() && getState() != DialogState.CONFIRMED) { this.setState(SIPDialog.TERMINATED_STATE); } } } } else { /* * JvB: RFC4235 says that when sending 2xx on UAS side, state should move to * CONFIRMED */ if (this.dialogState <= SIPDialog.EARLY_STATE && (cseqMethod.equals(Request.INVITE) || cseqMethod.equals(Request.SUBSCRIBE) || cseqMethod .equals(Request.REFER))) { this.setState(SIPDialog.CONFIRMED_STATE); } if (doPutDialog) { this.setDialogId(sipResponse.getDialogId(true)); sipStack.putDialog(this); } /* * We put the dialog into the table. We must wait for ACK before re-INVITE is * sent */ if (transaction.getState() != TransactionState.TERMINATED && sipResponse.getStatusCode() == Response.OK && cseqMethod.equals(Request.INVITE) && this.isBackToBackUserAgent) { /* * Acquire the flag for re-INVITE so that we cannot re-INVITE before * ACK is received. */ if (!this.takeAckSem()) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "Delete dialog -- cannot acquire ackSem"); } this.delete(); return; } } } } } } /** * Start the retransmit timer. * * @param sipServerTx -- server transaction on which the response was sent * @param response - response that was sent. */ public void startRetransmitTimer(SIPServerTransaction sipServerTx, Response response) { if (sipServerTx.getRequest().getMethod().equals(Request.INVITE) && response.getStatusCode() / 100 == 2) { this.startTimer(sipServerTx); } } /** * @return -- the last response associated with the dialog. */ public SIPResponse getLastResponse() { return lastResponse; } /** * Do taget refresh dialog state updates. * * RFC 3261: Requests within a dialog MAY contain Record-Route and Contact header fields. * However, these requests do not cause the dialog's route set to be modified, although they * may modify the remote target URI. Specifically, requests that are not target refresh * requests do not modify the dialog's remote target URI, and requests that are target refresh * requests do. For dialogs that have been established with an * * INVITE, the only target refresh request defined is re-INVITE (see Section 14). Other * extensions may define different target refresh requests for dialogs established in other * ways. */ private void doTargetRefresh(SIPMessage sipMessage) { ContactList contactList = sipMessage.getContactHeaders(); /* * INVITE is the target refresh for INVITE dialogs. SUBSCRIBE is the target refresh for * subscribe dialogs from the client side. This modifies the remote target URI potentially */ if (contactList != null) { Contact contact = (Contact) contactList.getFirst(); this.setRemoteTarget(contact); } } private static final boolean optionPresent(ListIterator l, String option) { while (l.hasNext()) { OptionTag opt = (OptionTag) l.next(); if (opt != null && option.equalsIgnoreCase(opt.getOptionTag())) return true; } return false; } /* * (non-Javadoc) * * @see javax.sip.Dialog#createReliableProvisionalResponse(int) */ public Response createReliableProvisionalResponse(int statusCode) throws InvalidArgumentException, SipException { if (!(firstTransactionIsServerTransaction)) { throw new SipException("Not a Server Dialog!"); } /* * A UAS MUST NOT attempt to send a 100 (Trying) response reliably. Only provisional * responses numbered 101 to 199 may be sent reliably. If the request did not include * either a Supported or Require header field indicating this feature, the UAS MUST NOT * send the provisional response reliably. */ if (statusCode <= 100 || statusCode > 199) throw new InvalidArgumentException("Bad status code "); SIPRequest request = this.originalRequest; if (!request.getMethod().equals(Request.INVITE)) throw new SipException("Bad method"); ListIterator list = request.getHeaders(SupportedHeader.NAME); if (list == null || !optionPresent(list, "100rel")) { list = request.getHeaders(RequireHeader.NAME); if (list == null || !optionPresent(list, "100rel")) { throw new SipException("No Supported/Require 100rel header in the request"); } } SIPResponse response = request.createResponse(statusCode); /* * The provisional response to be sent reliably is constructed by the UAS core according * to the procedures of Section 8.2.6 of RFC 3261. In addition, it MUST contain a Require * header field containing the option tag 100rel, and MUST include an RSeq header field. * The value of the header field for the first reliable provisional response in a * transaction MUST be between 1 and 2**31 - 1. It is RECOMMENDED that it be chosen * uniformly in this range. The RSeq numbering space is within a single transaction. This * means that provisional responses for different requests MAY use the same values for the * RSeq number. */ Require require = new Require(); try { require.setOptionTag("100rel"); } catch (Exception ex) { InternalErrorHandler.handleException(ex); } response.addHeader(require); RSeq rseq = new RSeq(); /* * set an arbitrary sequence number. This is actually set when the response is sent out */ rseq.setSeqNumber(1L); /* * Copy the record route headers from the request to the response ( Issue 160 ). Note that * other 1xx headers do not get their Record Route headers copied over but reliable * provisional responses do. See RFC 3262 Table 2. */ RecordRouteList rrl = request.getRecordRouteHeaders(); if (rrl != null) { RecordRouteList rrlclone = (RecordRouteList) rrl.clone(); response.setHeader(rrlclone); } return response; } /** * Do the processing necessary for the PRACK * * @param prackRequest * @return true if this is the first time the tx has seen the prack ( and hence needs to be * passed up to the TU) */ public boolean handlePrack(SIPRequest prackRequest) { /* * The RAck header is sent in a PRACK request to support reliability of provisional * responses. It contains two numbers and a method tag. The first number is the value from * the RSeq header in the provisional response that is being acknowledged. The next * number, and the method, are copied from the CSeq in the response that is being * acknowledged. The method name in the RAck header is case sensitive. */ if (!this.isServer()) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Dropping Prack -- not a server Dialog"); return false; } SIPServerTransaction sipServerTransaction = (SIPServerTransaction) this .getFirstTransaction(); SIPResponse sipResponse = sipServerTransaction.getReliableProvisionalResponse(); if (sipResponse == null) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger() .logDebug("Dropping Prack -- ReliableResponse not found"); return false; } RAck rack = (RAck) prackRequest.getHeader(RAckHeader.NAME); if (rack == null) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Dropping Prack -- rack header not found"); return false; } CSeq cseq = (CSeq) sipResponse.getCSeq(); if (!rack.getMethod().equals(cseq.getMethod())) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "Dropping Prack -- CSeq Header does not match PRACK"); return false; } if (rack.getCSeqNumberLong() != cseq.getSeqNumber()) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "Dropping Prack -- CSeq Header does not match PRACK"); return false; } RSeq rseq = (RSeq) sipResponse.getHeader(RSeqHeader.NAME); if (rack.getRSequenceNumber() != rseq.getSeqNumber()) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "Dropping Prack -- RSeq Header does not match PRACK"); return false; } return sipServerTransaction.prackRecieved(); } /* * (non-Javadoc) * * @see javax.sip.Dialog#sendReliableProvisionalResponse(javax.sip.message.Response) */ public void sendReliableProvisionalResponse(Response relResponse) throws SipException { if (!this.isServer()) { throw new SipException("Not a Server Dialog"); } SIPResponse sipResponse = (SIPResponse) relResponse; if (relResponse.getStatusCode() == 100) throw new SipException("Cannot send 100 as a reliable provisional response"); if (relResponse.getStatusCode() / 100 > 2) throw new SipException( "Response code is not a 1xx response - should be in the range 101 to 199 "); /* * Do a little checking on the outgoing response. */ if (sipResponse.getToTag() == null) { throw new SipException( "Badly formatted response -- To tag mandatory for Reliable Provisional Response"); } ListIterator requireList = (ListIterator) relResponse.getHeaders(RequireHeader.NAME); boolean found = false; if (requireList != null) { while (requireList.hasNext() && !found) { RequireHeader rh = (RequireHeader) requireList.next(); if (rh.getOptionTag().equalsIgnoreCase("100rel")) { found = true; } } } if (!found) { Require require = new Require("100rel"); relResponse.addHeader(require); if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "Require header with optionTag 100rel is needed -- adding one"); } } SIPServerTransaction serverTransaction = (SIPServerTransaction) this .getFirstTransaction(); /* * put into the dialog table before sending the response so as to avoid race condition * with PRACK */ this.setLastResponse(serverTransaction, sipResponse); this.setDialogId(sipResponse.getDialogId(true)); serverTransaction.sendReliableProvisionalResponse(relResponse); this.startRetransmitTimer(serverTransaction, relResponse); } /* * (non-Javadoc) * * @see javax.sip.Dialog#terminateOnBye(boolean) */ public void terminateOnBye(boolean terminateFlag) throws SipException { this.terminateOnBye = terminateFlag; } /** * Set the "assigned" flag to true. We do this when inserting the dialog into the dialog table * of the stack. * */ public void setAssigned() { this.isAssigned = true; } /** * Return true if the dialog has already been mapped to a transaction. * */ public boolean isAssigned() { return this.isAssigned; } /** * Get the contact header that the owner of this dialog assigned. Subsequent Requests are * considered to belong to the dialog if the dialog identifier matches and the contact header * matches the ip address and port on which the request is received. * * @return contact header belonging to the dialog. */ public Contact getMyContactHeader() { return contactHeader; } /** * Do the necessary processing to handle an ACK directed at this Dialog. * * @param ackTransaction -- the ACK transaction that was directed at this dialog. * @return -- true if the ACK was successfully consumed by the Dialog and resulted in the * dialog state being changed. */ public boolean handleAck(SIPServerTransaction ackTransaction) { SIPRequest sipRequest = ackTransaction.getOriginalRequest(); if (isAckSeen() && getRemoteSeqNumber() == sipRequest.getCSeq().getSeqNumber()) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "ACK already seen by dialog -- dropping Ack" + " retransmission"); } acquireTimerTaskSem(); try { if (this.timerTask != null) { this.timerTask.cancel(); this.timerTask = null; } } finally { releaseTimerTaskSem(); } return false; } else if (this.getState() == DialogState.TERMINATED) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("Dialog is terminated -- dropping ACK"); return false; } else { /* * This could be a re-invite processing. check to see if the ack matches with the last * transaction. s */ SIPServerTransaction tr = getInviteTransaction(); SIPResponse sipResponse = (tr != null ? tr.getLastResponse() : null); // Idiot check for sending ACK from the wrong side! if (tr != null && sipResponse != null && sipResponse.getStatusCode() / 100 == 2 && sipResponse.getCSeq().getMethod().equals(Request.INVITE) && sipResponse.getCSeq().getSeqNumber() == sipRequest.getCSeq() .getSeqNumber()) { ackTransaction.setDialog(this, sipResponse.getDialogId(false)); /* * record that we already saw an ACK for this dialog. */ ackReceived(sipRequest); if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug("ACK for 2XX response --- sending to TU "); return true; } else { /* * This happens when the ACK is re-transmitted and arrives too late to be * processed. */ if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( " INVITE transaction not found -- Discarding ACK"); return false; } } } void setEarlyDialogId(String earlyDialogId) { this.earlyDialogId = earlyDialogId; } String getEarlyDialogId() { return earlyDialogId; } /** * Release the semaphore for ACK processing so the next re-INVITE may proceed. */ void releaseAckSem() { if (this.isBackToBackUserAgent) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("releaseAckSem]" + this); } this.ackSem.release(); } } boolean takeAckSem() { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("[takeAckSem " + this); } try { if (!this.ackSem.tryAcquire(2, TimeUnit.SECONDS)) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logError("Cannot aquire ACK semaphore"); } if ( sipStack.isLoggingEnabled() ) { sipStack.getStackLogger().logDebug("Semaphore previously acquired at " + this.stackTrace); sipStack.getStackLogger().logStackTrace(); } return false; } if ( sipStack.isLoggingEnabled() ) { this.recordStackTrace(); } } catch (InterruptedException ex) { sipStack.getStackLogger().logError("Cannot aquire ACK semaphore"); return false; } return true; } /** * @param lastAckReceived the lastAckReceived to set */ private void setLastAckReceived(SIPRequest lastAckReceived) { this.lastAckReceived = lastAckReceived; } /** * @return the lastAckReceived */ protected SIPRequest getLastAckReceived() { return lastAckReceived; } /** * @param lastAckSent the lastAckSent to set */ private void setLastAckSent(SIPRequest lastAckSent) { this.lastAckSent = lastAckSent; } /** * @return true if an ack was ever sent for this Dialog */ public boolean isAtleastOneAckSent() { return this.isAcknowledged; } public boolean isBackToBackUserAgent() { return this.isBackToBackUserAgent; } public synchronized void doDeferredDeleteIfNoAckSent(long seqno) { if (sipStack.getTimer() == null) { this.setState(TERMINATED_STATE); } else if(dialogDeleteIfNoAckSentTask == null){ // Delete the transaction after the max ack timeout. dialogDeleteIfNoAckSentTask = new DialogDeleteIfNoAckSentTask(seqno); sipStack.getTimer().schedule( dialogDeleteIfNoAckSentTask, SIPTransaction.TIMER_J * SIPTransactionStack.BASE_TIMER_INTERVAL); } } /* * (non-Javadoc) * @see gov.nist.javax.sip.DialogExt#setBackToBackUserAgent(boolean) */ public void setBackToBackUserAgent() { this.isBackToBackUserAgent = true; } /** * @return the eventHeader */ EventHeader getEventHeader() { return eventHeader; } /** * @param eventHeader the eventHeader to set */ void setEventHeader(EventHeader eventHeader) { this.eventHeader = eventHeader; } /** * @param serverTransactionFlag the serverTransactionFlag to set */ void setServerTransactionFlag(boolean serverTransactionFlag) { this.serverTransactionFlag = serverTransactionFlag; } /** * @param reInviteFlag the reinviteFlag to set */ void setReInviteFlag(boolean reInviteFlag) { this.reInviteFlag = reInviteFlag; } public boolean isSequnceNumberValidation() { return this.sequenceNumberValidation; } public void disableSequenceNumberValidation() { this.sequenceNumberValidation = false; } public void acquireTimerTaskSem() { boolean acquired = false; try { acquired = this.timerTaskLock.tryAcquire(10, TimeUnit.SECONDS); } catch ( InterruptedException ex) { acquired = false; } if(!acquired) { throw new IllegalStateException("Impossible to acquire the dialog timer task lock"); } } public void releaseTimerTaskSem() { this.timerTaskLock.release(); } }