/* * Conditions Of Use * * This software was developed by employees of the National Institute of * Standards and Technology (NIST), an agency of the Federal Government. * Pursuant to title 15 Untied States Code Section 105, works of NIST * employees are not subject to copyright protection in the United States * and are considered to be in the public domain. As a result, a formal * license is not needed to use the software. * * This software is provided by NIST as a service and is expressly * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT * AND DATA ACCURACY. NIST does not warrant or make any representations * regarding the use of the software or the results thereof, including but * not limited to the correctness, accuracy, reliability or usefulness of * the software. * * Permission to use this software is contingent upon your acceptance * of the terms of this agreement * * . * */ /****************************************************************************** * Product of NIST/ITL Advanced Networking Technologies Division (ANTD). * ******************************************************************************/ package gov.nist.javax.sip; import gov.nist.core.CommonLogger; import gov.nist.core.InternalErrorHandler; import gov.nist.core.LogLevels; import gov.nist.core.LogWriter; import gov.nist.core.StackLogger; import gov.nist.javax.sip.DialogTimeoutEvent.Reason; import gov.nist.javax.sip.address.RouterExt; import gov.nist.javax.sip.header.CallID; import gov.nist.javax.sip.header.Via; import gov.nist.javax.sip.message.RequestExt; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.stack.HopImpl; import gov.nist.javax.sip.stack.MessageChannel; import gov.nist.javax.sip.stack.SIPClientTransaction; import gov.nist.javax.sip.stack.SIPDialog; import gov.nist.javax.sip.stack.SIPDialogErrorEvent; import gov.nist.javax.sip.stack.SIPDialogEventListener; import gov.nist.javax.sip.stack.SIPServerTransaction; import gov.nist.javax.sip.stack.SIPTransaction; import gov.nist.javax.sip.stack.SIPTransactionErrorEvent; import gov.nist.javax.sip.stack.SIPTransactionEventListener; import gov.nist.javax.sip.stack.SIPTransactionStack; import java.io.IOException; import java.text.ParseException; import java.util.EventObject; import java.util.Iterator; import java.util.TooManyListenersException; import java.util.concurrent.ConcurrentHashMap; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogState; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.ServerTransaction; import javax.sip.SipException; import javax.sip.SipListener; import javax.sip.SipStack; import javax.sip.Timeout; import javax.sip.TimeoutEvent; import javax.sip.Transaction; import javax.sip.TransactionAlreadyExistsException; import javax.sip.TransactionState; import javax.sip.TransactionUnavailableException; import javax.sip.address.Hop; import javax.sip.header.CallIdHeader; import javax.sip.message.Request; import javax.sip.message.Response; /* * Contributions (bug fixes) made by: Daniel J. Martinez Manzano, Hagai Sela. * Bug reports by Shanti Kadiyala, Rhys Ulerich,Victor Hugo */ /** * Implementation of the JAIN-SIP provider interface. * * @version 1.2 $Revision: 1.89 $ $Date: 2010-12-02 22:04:19 $ * * @author M. Ranganathan <br/> * * */ public class SipProviderImpl implements javax.sip.SipProvider, gov.nist.javax.sip.SipProviderExt, SIPTransactionEventListener, SIPDialogEventListener { private static StackLogger logger = CommonLogger.getLogger(SipProviderImpl.class); private SipListener sipListener; protected SipStackImpl sipStack; /* * A set of listening points associated with the provider At most one LP per * transport */ private ConcurrentHashMap<String,ListeningPoint> listeningPoints; protected EventScanner eventScanner; private boolean automaticDialogSupportEnabled ; private boolean dialogErrorsAutomaticallyHandled = true; private SipProviderImpl() { } /** * Stop processing messages for this provider. Post an empty message to our * message processing queue that signals us to quit. */ protected void stop() { // Put an empty event in the queue and post ourselves a message. if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug("Exiting provider"); for (Iterator it = listeningPoints.values().iterator(); it.hasNext();) { ListeningPointImpl listeningPoint = (ListeningPointImpl) it.next(); listeningPoint.removeSipProvider(); } this.eventScanner.stop(); } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getListeningPoint(java.lang.String) */ public ListeningPoint getListeningPoint(String transport) { if (transport == null) throw new NullPointerException("Null transport param"); return (ListeningPoint) this.listeningPoints.get(transport .toUpperCase()); } /** * Handle the SIP event - because we have only one listener and we are * already in the context of a separate thread, we dont need to enque the * event and signal another thread. * * @param sipEvent * is the event to process. * */ public void handleEvent(EventObject sipEvent, SIPTransaction transaction) { if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { logger.logDebug( "handleEvent " + sipEvent + "currentTransaction = " + transaction + "this.sipListener = " + this.getSipListener() + "sipEvent.source = " + sipEvent.getSource()); if (sipEvent instanceof RequestEvent) { Dialog dialog = ((RequestEvent) sipEvent).getDialog(); if ( logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug("Dialog = " + dialog); } else if (sipEvent instanceof ResponseEvent) { Dialog dialog = ((ResponseEvent) sipEvent).getDialog(); if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG) ) logger.logDebug("Dialog = " + dialog); } logger.logStackTrace(); } EventWrapper eventWrapper = new EventWrapper(sipEvent, transaction); if (!sipStack.isReEntrantListener()) { // Run the event in the context of a single thread. this.eventScanner.addEvent(eventWrapper); } else { // just call the delivery method this.eventScanner.deliverEvent(eventWrapper); } } /** Creates a new instance of SipProviderImpl */ protected SipProviderImpl(SipStackImpl sipStack) { this.eventScanner = sipStack.getEventScanner(); // for quick access. this.sipStack = sipStack; this.eventScanner.incrementRefcount(); this.listeningPoints = new ConcurrentHashMap<String,ListeningPoint>(); this.automaticDialogSupportEnabled = this.sipStack .isAutomaticDialogSupportEnabled(); this.dialogErrorsAutomaticallyHandled = this.sipStack.isAutomaticDialogErrorHandlingEnabled(); } /* * (non-Javadoc) * * @see java.lang.Object#clone() */ protected Object clone() throws java.lang.CloneNotSupportedException { throw new java.lang.CloneNotSupportedException(); } /* * (non-Javadoc) * * @see javax.sip.SipProvider#addSipListener(javax.sip.SipListener) */ public void addSipListener(SipListener sipListener) throws TooManyListenersException { if (sipStack.sipListener == null) { sipStack.sipListener = sipListener; } else if (sipStack.sipListener != sipListener) { throw new TooManyListenersException( "Stack already has a listener. Only one listener per stack allowed"); } if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug("add SipListener " + sipListener); this.sipListener = sipListener; } /* * This method is deprecated (non-Javadoc) * * @see javax.sip.SipProvider#getListeningPoint() */ public ListeningPoint getListeningPoint() { if (this.listeningPoints.size() > 0) return (ListeningPoint) this.listeningPoints.values().iterator() .next(); else return null; } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getNewCallId() */ public CallIdHeader getNewCallId() { String callId = Utils.getInstance().generateCallIdentifier(this.getListeningPoint() .getIPAddress()); CallID callid = new CallID(); try { callid.setCallId(callId); } catch (java.text.ParseException ex) { } return callid; } /** * @param request * @param hop * @return * @throws TransactionUnavailableException */ protected SIPClientTransaction createClientTransaction(Request request, Hop hop) throws TransactionUnavailableException { if (request == null) throw new NullPointerException("null request"); if (hop == null) throw new NullPointerException("null hop"); if (!sipStack.isAlive()) throw new TransactionUnavailableException("Stack is stopped"); SIPRequest sipRequest = (SIPRequest) request; if (sipRequest.getTransaction() != null) throw new TransactionUnavailableException( "Transaction already assigned to request"); if ( sipRequest.getMethod().equals(Request.ACK)) { throw new TransactionUnavailableException ("Cannot create client transaction for " + Request.ACK); } // Be kind and assign a via header for this provider if the user is // sloppy if (sipRequest.getTopmostVia() == null) { String transport = null; transport = hop.getTransport(); if(transport == null) transport = "udp"; ListeningPointImpl lp = (ListeningPointImpl) this .getListeningPoint(transport); if(lp == null) { // last resort, instead of failing try to route anywhere lp = (ListeningPointImpl) this.getListeningPoints()[0]; } Via via = lp.getViaHeader(); sipRequest.setHeader(via); } // Give the request a quick check to see if all headers are assigned. try { sipRequest.checkHeaders(); } catch (ParseException ex) { throw new TransactionUnavailableException(ex.getMessage(), ex); } /* * User decided to give us his own via header branch. Lets see if it * results in a clash. If so reject the request. */ if (sipRequest.getTopmostVia().getBranch() != null && sipRequest.getTopmostVia().getBranch().startsWith( SIPConstants.BRANCH_MAGIC_COOKIE) && sipStack.findTransaction((SIPRequest) sipRequest, false) != null) { throw new TransactionUnavailableException( "Transaction already exists!"); } if (sipRequest.getMethod().equalsIgnoreCase(Request.CANCEL)) { SIPClientTransaction ct = (SIPClientTransaction) sipStack .findCancelTransaction((SIPRequest) sipRequest, false); if (ct != null) { SIPClientTransaction retval = sipStack.createClientTransaction(sipRequest, ct.getMessageChannel()); ((SIPTransaction) retval).addEventListener(this); sipStack.addTransaction( retval); if (ct.getDialog() != null) { retval.setDialog((SIPDialog) ct.getDialog(), sipRequest.getDialogId(false)); } return retval; } } if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug( "could not find existing transaction for " //+ sipRequest.getFirstLine() + sipRequest + " creating a new one "); // Could not find a dialog or the route is not set in dialog. String transport = hop.getTransport(); ListeningPointImpl listeningPoint = (ListeningPointImpl) this .getListeningPoint(transport); if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug( "Got following Listenin point " //+ sipRequest.getFirstLine() + listeningPoint + " for transport " + transport); // https://github.com/RestComm/jain-sip/issues/114 if(listeningPoint == null) { listeningPoint = (ListeningPointImpl) this .getListeningPoint(sipRequest.getTopmostVia().getTransport()); if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug( "Listenig point was null using new one from Via header " //+ sipRequest.getFirstLine() + listeningPoint + " for transport " + sipRequest.getTopmostVia().getTransport()); } String dialogId = sipRequest.getDialogId(false); SIPDialog dialog = sipStack.getDialog(dialogId); if (dialog != null && dialog.getState() == DialogState.TERMINATED) { // throw new TransactionUnavailableException // ("Found a terminated dialog -- possible re-use of old tag // parameters"); sipStack.removeDialog(dialog); } // An out of dialog route was found. Assign this to the // client transaction. try { // Set the brannch id before you ask for a tx. // If the user has set his own branch Id and the // branch id starts with a valid prefix, then take it. // otherwise, generate one. If branch ID checking has // been requested, set the branch ID. String branchId = null; if (sipRequest.getTopmostVia().getBranch() == null || !sipRequest.getTopmostVia().getBranch().startsWith( SIPConstants.BRANCH_MAGIC_COOKIE) || sipStack.checkBranchId() ) { branchId = Utils.getInstance().generateBranchId(); sipRequest.getTopmostVia().setBranch(branchId); } Via topmostVia = sipRequest.getTopmostVia(); //set port and transport if user hasn't already done this. if(topmostVia.getTransport() == null) topmostVia.setTransport(transport); if(topmostVia.getPort() == -1) topmostVia.setPort(listeningPoint.getPort()); branchId = sipRequest.getTopmostVia().getBranch(); MessageChannel messageChannel = sipStack .createMessageChannel(sipRequest, listeningPoint .getMessageProcessor(), hop); SIPClientTransaction ct = sipStack.createClientTransaction(sipRequest, messageChannel); if (ct == null) throw new TransactionUnavailableException("Cound not create tx"); ct.setNextHop(hop); ct.setOriginalRequest(sipRequest); ct.setBranch(branchId); // if the stack supports dialogs then if (SIPTransactionStack.isDialogCreated(sipRequest.getMethod())) { // create a new dialog to contain this transaction // provided this is necessary. // This could be a re-invite // in which case the dialog is re-used. // (but noticed by Brad Templeton) if (dialog != null) ct.setDialog(dialog, sipRequest.getDialogId(false)); else if (this.isAutomaticDialogSupportEnabled()) { SIPDialog sipDialog = sipStack.createDialog(ct); ct.setDialog(sipDialog, sipRequest.getDialogId(false)); } } else { if (dialog != null) { ct.setDialog(dialog, sipRequest.getDialogId(false)); } } // The provider is the event listener for all transactions. ct.addEventListener(this); return ct; } catch (IOException ex) { throw new TransactionUnavailableException( "Could not resolve next hop or listening point unavailable! ", ex); } catch (java.text.ParseException ex) { InternalErrorHandler.handleException(ex); throw new TransactionUnavailableException( "Unexpected Exception FIXME! ", ex); } catch (InvalidArgumentException ex) { InternalErrorHandler.handleException(ex); throw new TransactionUnavailableException( "Unexpected Exception FIXME! ", ex); } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getNewClientTransaction(javax.sip.message.Request) */ public ClientTransaction getNewClientTransaction(Request request) throws TransactionUnavailableException { Hop hop = null; try { hop = sipStack.getNextHop((SIPRequest) request); if (hop == null) throw new TransactionUnavailableException( "Cannot resolve next hop -- transaction unavailable"); } catch (SipException ex) { throw new TransactionUnavailableException( "Cannot resolve next hop -- transaction unavailable", ex); } SIPClientTransaction newClientTransaction = createClientTransaction(request, hop); sipStack.addTransaction(newClientTransaction); return newClientTransaction; } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getNewServerTransaction(javax.sip.message.Request) */ public ServerTransaction getNewServerTransaction(Request request) throws TransactionAlreadyExistsException, TransactionUnavailableException { if (!sipStack.isAlive()) throw new TransactionUnavailableException("Stack is stopped"); SIPServerTransaction transaction = null; SIPRequest sipRequest = (SIPRequest) request; try { sipRequest.checkHeaders(); } catch (ParseException ex) { throw new TransactionUnavailableException(ex.getMessage(), ex); } if ( request.getMethod().equals(Request.ACK)) { if ( logger.isLoggingEnabled()) logger.logError("Creating server transaction for ACK -- makes no sense!"); throw new TransactionUnavailableException("Cannot create Server transaction for ACK "); } /* * Got a notify. */ if (sipRequest.getMethod().equals(Request.NOTIFY) && sipRequest.getFromTag() != null && sipRequest.getToTag() == null) { SIPClientTransaction ct = sipStack.findSubscribeTransaction( sipRequest, (ListeningPointImpl) this.getListeningPoint()); /* Issue 104 */ if (ct == null && ! sipStack.isDeliverUnsolicitedNotify()) { throw new TransactionUnavailableException( "Cannot find matching Subscription (and gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY not set)"); } } if ( !sipStack.acquireSem()) { throw new TransactionUnavailableException( "Transaction not available -- could not acquire stack lock"); } try { if (SIPTransactionStack.isDialogCreated(sipRequest.getMethod())) { if (sipStack.findTransaction((SIPRequest) request, true) != null) throw new TransactionAlreadyExistsException( "server transaction already exists!"); transaction = (SIPServerTransaction) ((SIPRequest) request) .getTransaction(); if (transaction == null) throw new TransactionUnavailableException( "Transaction not available"); if (transaction.getOriginalRequest() == null) transaction.setOriginalRequest(sipRequest); try { sipStack.addTransaction(transaction); } catch (IOException ex) { throw new TransactionUnavailableException( "Error sending provisional response"); } // So I can handle timeouts. transaction.addEventListener(this); if (isAutomaticDialogSupportEnabled()) { // If automatic dialog support is enabled then // this tx gets his own dialog. String dialogId = sipRequest.getDialogId(true); SIPDialog dialog = sipStack.getDialog(dialogId); if (dialog == null) { dialog = sipStack.createDialog(transaction); } transaction.setDialog(dialog, sipRequest.getDialogId(true)); if (sipRequest.getMethod().equals(Request.INVITE) && this.isDialogErrorsAutomaticallyHandled()) { sipStack.putInMergeTable(transaction, sipRequest); } dialog.addRoute(sipRequest); if (dialog.getRemoteTag() != null && dialog.getLocalTag() != null) { this.sipStack.putDialog(dialog); } } } else { if (isAutomaticDialogSupportEnabled()) { /* * Under automatic dialog support, dialog is tied into a transaction. You cannot * create a server tx except for dialog creating transactions. After that, all * subsequent transactions are created for you by the stack. */ transaction = (SIPServerTransaction) sipStack.findTransaction( (SIPRequest) request, true); if (transaction != null) throw new TransactionAlreadyExistsException( "Transaction exists! "); transaction = (SIPServerTransaction) ((SIPRequest) request) .getTransaction(); if (transaction == null) throw new TransactionUnavailableException( "Transaction not available!"); if (transaction.getOriginalRequest() == null) transaction.setOriginalRequest(sipRequest); // Map the transaction. try { sipStack.addTransaction(transaction); } catch (IOException ex) { throw new TransactionUnavailableException( "Could not send back provisional response!"); } // If there is a dialog already assigned then just update the // dialog state. String dialogId = sipRequest.getDialogId(true); SIPDialog dialog = sipStack.getDialog(dialogId); if (dialog != null) { dialog.addTransaction(transaction); dialog.addRoute(sipRequest); transaction.setDialog(dialog, sipRequest.getDialogId(true)); } } else { transaction = (SIPServerTransaction) sipStack.findTransaction( (SIPRequest) request, true); if (transaction != null) throw new TransactionAlreadyExistsException( "Transaction exists! "); transaction = (SIPServerTransaction) ((SIPRequest) request) .getTransaction(); if (transaction != null) { if (transaction.getOriginalRequest() == null) transaction.setOriginalRequest(sipRequest); // Map the transaction. sipStack.mapTransaction(transaction); // If there is a dialog already assigned then just // assign the dialog to the transaction. String dialogId = sipRequest.getDialogId(true); SIPDialog dialog = sipStack.getDialog(dialogId); if (dialog != null) { dialog.addTransaction(transaction); dialog.addRoute(sipRequest); transaction.setDialog(dialog, sipRequest .getDialogId(true)); } return transaction; } else { // tx does not exist so create the tx. MessageChannel mc = (MessageChannel) sipRequest .getMessageChannel(); transaction = sipStack.createServerTransaction(mc); if (transaction == null) throw new TransactionUnavailableException( "Transaction unavailable -- too many servrer transactions"); transaction.setOriginalRequest(sipRequest); sipStack.mapTransaction(transaction); // If there is a dialog already assigned then just // assign the dialog to the transaction. String dialogId = sipRequest.getDialogId(true); SIPDialog dialog = sipStack.getDialog(dialogId); if (dialog != null) { dialog.addTransaction(transaction); dialog.addRoute(sipRequest); transaction.setDialog(dialog, sipRequest .getDialogId(true)); } return transaction; } } } return transaction; } finally { sipStack.releaseSem(); } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getSipStack() */ public SipStack getSipStack() { return (SipStack) this.sipStack; } /* * (non-Javadoc) * * @see javax.sip.SipProvider#removeSipListener(javax.sip.SipListener) */ public void removeSipListener(SipListener sipListener) { if (sipListener == this.getSipListener()) { this.sipListener = null; } boolean found = false; for (Iterator<SipProviderImpl> it = sipStack.getSipProviders(); it.hasNext();) { SipProviderImpl nextProvider = (SipProviderImpl) it.next(); if (nextProvider.getSipListener() != null) found = true; } if (!found) { sipStack.sipListener = null; } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#sendRequest(javax.sip.message.Request) */ public void sendRequest(Request request) throws SipException { if (!sipStack.isAlive()) throw new SipException("Stack is stopped."); // mranga: added check to ensure we are not sending empty (keepalive) // message. if (((SIPRequest) request).getRequestLine() != null && request.getMethod().equals(Request.ACK)) { Dialog dialog = sipStack.getDialog(((SIPRequest) request) .getDialogId(false)); if (dialog != null && dialog.getState() != null) { if (logger.isLoggingEnabled()) logger.logWarning( "Dialog exists -- you may want to use Dialog.sendAck() " + dialog.getState()); } } Hop hop = sipStack.getRouter((SIPRequest) request).getNextHop(request); if (hop == null) throw new SipException("could not determine next hop!"); SIPRequest sipRequest = (SIPRequest) request; // Check if we have a valid via. // Null request is used to send default proxy keepalive messages. if ((!sipRequest.isNullRequest()) && sipRequest.getTopmostVia() == null) throw new SipException("Invalid SipRequest -- no via header!"); try { /* * JvB: Via branch should already be OK, dont touch it here? Some * apps forward statelessly, and then it's not set. So set only when * not set already, dont overwrite CANCEL branch here.. */ if (!sipRequest.isNullRequest()) { Via via = sipRequest.getTopmostVia(); String branch = via.getBranch(); if (branch == null || branch.length() == 0) { via.setBranch(sipRequest.getTransactionId()); } } MessageChannel messageChannel = null; if (this.listeningPoints.containsKey(hop.getTransport() .toUpperCase())) { messageChannel = sipStack.createRawMessageChannel( this.getListeningPoint(hop.getTransport()).getIPAddress(), this.getListeningPoint(hop.getTransport()).getPort(), hop); } if (messageChannel != null) { messageChannel.sendMessage((SIPMessage) sipRequest,hop); } else { if ( logger.isLoggingEnabled(LogLevels.TRACE_DEBUG) ) { logger.logDebug("Could not create a message channel for " + hop.toString() + " listeningPoints = " + this.listeningPoints); } throw new SipException( "Could not create a message channel for " + hop.toString()); } } catch (IOException ex) { // not needed as we throw the exception below and it pollutes the logs // if (logger.isLoggingEnabled()) { // logger.logException(ex); // } if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG) ) { logger.logDebug("Could not create a message channel for " + hop.toString() + " listeningPoints = " + this.listeningPoints + " because of an IO issue " + ex.getMessage()); } throw new SipException( "IO Exception occured while Sending Request", ex); } catch (ParseException ex1) { InternalErrorHandler.handleException(ex1); } finally { if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug( "done sending " + request.getMethod() + " to hop " + hop); } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#sendResponse(javax.sip.message.Response) */ public void sendResponse(Response response) throws SipException { if (!sipStack.isAlive()) throw new SipException("Stack is stopped"); SIPResponse sipResponse = (SIPResponse) response; Via via = sipResponse.getTopmostVia(); if (via == null) throw new SipException("No via header in response!"); SIPServerTransaction st = (SIPServerTransaction) sipStack.findTransaction((SIPMessage)response, true); if ( st != null && st.getInternalState() != TransactionState._TERMINATED && this.isAutomaticDialogSupportEnabled()) { throw new SipException("Transaction exists -- cannot send response statelessly"); } String transport = via.getTransport(); // check to see if Via has "received paramaeter". If so // set the host to the via parameter. Else set it to the // Via host. String host = via.getReceived(); if (host == null) host = via.getHost(); // Symmetric nat support int port = via.getRPort(); if (port == -1) { port = via.getPort(); if (port == -1) { if (transport.equalsIgnoreCase("TLS")||transport.equalsIgnoreCase("SCTP-TLS")) port = 5061; else port = 5060; } } // for correct management of IPv6 addresses. if (host.indexOf(":") > 0) if (host.indexOf("[") < 0) host = "[" + host + "]"; Hop hop = sipStack.getAddressResolver().resolveAddress( new HopImpl(host, port, transport)); try { ListeningPointImpl listeningPoint = (ListeningPointImpl) this .getListeningPoint(transport); if (listeningPoint == null) throw new SipException( "whoopsa daisy! no listening point found for transport " + transport); MessageChannel messageChannel = sipStack.createRawMessageChannel( this.getListeningPoint(hop.getTransport()).getIPAddress(), listeningPoint.port, hop); // Fix for https://github.com/RestComm/jain-sip/issues/133 if (messageChannel != null) { messageChannel.sendMessage(sipResponse); } else { if ( logger.isLoggingEnabled(LogLevels.TRACE_DEBUG) ) { logger.logDebug("Could not create a message channel for " + hop.toString() + " listeningPoints = " + this.listeningPoints); } throw new SipException( "Could not create a message channel for " + hop.toString()); } } catch (IOException ex) { throw new SipException(ex.getMessage()); } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#setListeningPoint(javax.sip.ListeningPoint) */ public synchronized void setListeningPoint(ListeningPoint listeningPoint) { if (listeningPoint == null) throw new NullPointerException("Null listening point"); ListeningPointImpl lp = (ListeningPointImpl) listeningPoint; lp.sipProvider = this; String transport = lp.getTransport().toUpperCase(); // this.address = listeningPoint.getIPAddress(); // this.port = listeningPoint.getPort(); // This is the first listening point. this.listeningPoints.clear(); this.listeningPoints.put(transport, listeningPoint); } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getNewDialog(javax.sip.Transaction) */ public Dialog getNewDialog(Transaction transaction) throws SipException { if (transaction == null) throw new NullPointerException("Null transaction!"); if (!sipStack.isAlive()) throw new SipException("Stack is stopped."); if (isAutomaticDialogSupportEnabled()) throw new SipException(" Error - AUTOMATIC_DIALOG_SUPPORT is on"); if (!SIPTransactionStack.isDialogCreated(transaction.getRequest().getMethod())) throw new SipException("Dialog cannot be created for this method " + transaction.getRequest().getMethod()); SIPDialog dialog = null; SIPTransaction sipTransaction = (SIPTransaction) transaction; if (transaction instanceof ServerTransaction) { SIPServerTransaction st = (SIPServerTransaction) transaction; Response response = st.getLastResponse(); if (response != null) { if (response.getStatusCode() != 100) throw new SipException( "Cannot set dialog after response has been sent"); } SIPRequest sipRequest = (SIPRequest) transaction.getRequest(); String dialogId = sipRequest.getDialogId(true); dialog = sipStack.getDialog(dialogId); if (dialog == null) { dialog = sipStack.createDialog((SIPTransaction) transaction); // create and register the dialog and add the inital route set. dialog.addTransaction(sipTransaction); dialog.addRoute(sipRequest); sipTransaction.setDialog(dialog, null); } else { sipTransaction.setDialog(dialog, sipRequest.getDialogId(true)); } if (sipRequest.getMethod().equals(Request.INVITE) && this.isDialogErrorsAutomaticallyHandled()) { sipStack.putInMergeTable(st, sipRequest); } } else { SIPClientTransaction sipClientTx = (SIPClientTransaction) transaction; SIPResponse response = sipClientTx.getLastResponse(); if (response == null) { // A response has not yet been received, then set this up as the // default dialog. SIPRequest request = (SIPRequest) sipClientTx.getRequest(); String dialogId = request.getDialogId(false); dialog = sipStack.getDialog(dialogId); if (dialog != null) { throw new SipException("Dialog already exists!"); } else { dialog = sipStack.createDialog(sipTransaction); } sipClientTx.setDialog(dialog, null); } else { throw new SipException( "Cannot call this method after response is received!"); } } dialog.addEventListener(this); return dialog; } /** * Invoked when an error has ocurred with a transaction. Propagate up to the * listeners. * * @param transactionErrorEvent * Error event. */ public void transactionErrorEvent( SIPTransactionErrorEvent transactionErrorEvent) { SIPTransaction transaction = (SIPTransaction) transactionErrorEvent .getSource(); if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TRANSPORT_ERROR) { // There must be a way to inform the TU here!! if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { logger.logDebug( "TransportError occured on " + transaction); } // Treat this like a timeout event. (Suggestion from Christophe). Object errorObject = transactionErrorEvent.getSource(); Timeout timeout = Timeout.TRANSACTION; TimeoutEvent ev = null; if (errorObject instanceof SIPServerTransaction) { ev = new TimeoutEvent(this, (ServerTransaction) errorObject, timeout); } else { SIPClientTransaction clientTx = (SIPClientTransaction) errorObject; Hop hop = clientTx.getNextHop(); if ( sipStack.getRouter() instanceof RouterExt ) { ((RouterExt) sipStack.getRouter()).transactionTimeout(hop); } ev = new TimeoutEvent(this, (ClientTransaction) errorObject, timeout); } // Handling transport error like timeout this.handleEvent(ev, (SIPTransaction) errorObject); } else if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TIMEOUT_ERROR) { // This is a timeout event. Object errorObject = transactionErrorEvent.getSource(); Timeout timeout = Timeout.TRANSACTION; TimeoutEvent ev = null; if (errorObject instanceof SIPServerTransaction) { ev = new TimeoutEvent(this, (ServerTransaction) errorObject, timeout); } else { SIPClientTransaction clientTx = (SIPClientTransaction) errorObject; Hop hop = clientTx.getNextHop(); if ( sipStack.getRouter() instanceof RouterExt ) { ((RouterExt) sipStack.getRouter()).transactionTimeout(hop); } ev = new TimeoutEvent(this, (ClientTransaction) errorObject, timeout); } this.handleEvent(ev, (SIPTransaction) errorObject); } else if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) { // This is a timeout retransmit event. // We should never get this if retransmit filter is // enabled (ie. in that case the stack should handle. // all retransmits. Object errorObject = transactionErrorEvent.getSource(); Transaction tx = (Transaction) errorObject; if (tx.getDialog() != null) InternalErrorHandler.handleException("Unexpected event !", this.logger); Timeout timeout = Timeout.RETRANSMIT; TimeoutEvent ev = null; if (errorObject instanceof SIPServerTransaction) { ev = new TimeoutEvent(this, (ServerTransaction) errorObject, timeout); } else { ev = new TimeoutEvent(this, (ClientTransaction) errorObject, timeout); } this.handleEvent(ev, (SIPTransaction) errorObject); } } /* * (non-Javadoc) * @see gov.nist.javax.sip.stack.SIPDialogEventListener#dialogErrorEvent(gov.nist.javax.sip.stack.SIPDialogErrorEvent) */ public void dialogErrorEvent(SIPDialogErrorEvent dialogErrorEvent) { SIPDialog sipDialog = (SIPDialog) dialogErrorEvent.getSource(); Reason reason = Reason.AckNotReceived; if (dialogErrorEvent.getErrorID() == SIPDialogErrorEvent.DIALOG_ACK_NOT_SENT_TIMEOUT) { reason= Reason.AckNotSent; } else if (dialogErrorEvent.getErrorID() == SIPDialogErrorEvent.DIALOG_REINVITE_TIMEOUT) { reason = Reason.ReInviteTimeout; } else if (dialogErrorEvent.getErrorID() == SIPDialogErrorEvent.EARLY_STATE_TIMEOUT) { reason = Reason.EarlyStateTimeout; } if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { logger.logDebug( "Dialog TimeoutError occured on " + sipDialog); } DialogTimeoutEvent ev = new DialogTimeoutEvent(this, sipDialog, reason); ev.setClientTransaction(dialogErrorEvent.getClientTransaction()); // Handling transport error like timeout this.handleEvent(ev, null); } /* * (non-Javadoc) * * @see javax.sip.SipProvider#getListeningPoints() */ public synchronized ListeningPoint[] getListeningPoints() { ListeningPoint[] retval = new ListeningPointImpl[this.listeningPoints .size()]; this.listeningPoints.values().toArray(retval); return retval; } /* * (non-Javadoc) * * @see javax.sip.SipProvider#addListeningPoint(javax.sip.ListeningPoint) */ public synchronized void addListeningPoint(ListeningPoint listeningPoint) throws ObjectInUseException { ListeningPointImpl lp = (ListeningPointImpl) listeningPoint; if (lp.sipProvider != null && lp.sipProvider != this) throw new ObjectInUseException( "Listening point assigned to another provider"); String transport = lp.getTransport().toUpperCase(); if (this.listeningPoints.containsKey(transport) && this.listeningPoints.get(transport) != listeningPoint) throw new ObjectInUseException( "Listening point already assigned for transport!"); // This is for backwards compatibility. lp.sipProvider = this; this.listeningPoints.put(transport, lp); } /* * (non-Javadoc) * * @see javax.sip.SipProvider#removeListeningPoint(javax.sip.ListeningPoint) */ public synchronized void removeListeningPoint(ListeningPoint listeningPoint) throws ObjectInUseException { ListeningPointImpl lp = (ListeningPointImpl) listeningPoint; if (lp.messageProcessor.inUse()) throw new ObjectInUseException("Object is in use"); this.listeningPoints.remove(lp.getTransport().toUpperCase()); } /** * Remove all the listening points for this sip provider. This is called * when the stack removes the Provider */ public synchronized void removeListeningPoints() { for (Iterator it = this.listeningPoints.values().iterator(); it .hasNext();) { ListeningPointImpl lp = (ListeningPointImpl) it.next(); lp.messageProcessor.stop(); it.remove(); } } /* * (non-Javadoc) * * @see javax.sip.SipProvider#setAutomaticDialogSupportEnabled(boolean) */ public void setAutomaticDialogSupportEnabled( boolean automaticDialogSupportEnabled) { this.automaticDialogSupportEnabled = automaticDialogSupportEnabled; if ( this.automaticDialogSupportEnabled ) { this.dialogErrorsAutomaticallyHandled = true; } } /** * @return Returns the automaticDialogSupportEnabled. */ public boolean isAutomaticDialogSupportEnabled() { return automaticDialogSupportEnabled; } /* * (non-Javadoc) * @see gov.nist.javax.sip.SipProviderExt#setDialogErrorsAutomaticallyHandled() */ public void setDialogErrorsAutomaticallyHandled() { this.dialogErrorsAutomaticallyHandled = true; } public boolean isDialogErrorsAutomaticallyHandled() { return this.dialogErrorsAutomaticallyHandled; } /** * @return the sipListener */ public SipListener getSipListener() { return sipListener; } }