/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * Large portions of this software are based upon public domain software * https://sip-communicator.dev.java.net/ * */ package net.sourceforge.gjtapi.raw.sipprovider.sip; import java.text.ParseException; import java.util.ArrayList; import java.util.Properties; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.InvalidArgumentException; import javax.sip.ServerTransaction; import javax.sip.SipException; import javax.sip.Transaction; import javax.sip.TransactionUnavailableException; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.address.URI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.ContentLengthHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.ToHeader; import javax.sip.message.Request; import javax.sip.message.Response; import net.sourceforge.gjtapi.raw.sipprovider.common.Console; import net.sourceforge.gjtapi.raw.sipprovider.sip.security.SipSecurityException; /** * <p>Title: SIP COMMUNICATOR-1.1</p> * <p>Description: JAIN-SIP-1.1 Audio/Video Phone Application</p> * <p>Copyright: Copyright (c) 2003</p> * <p>Company: Organisation: LSIIT Laboratory (http://lsiit.u-strasbg.fr)<p> * </p>Network Research Team (http://www-r2.u-strasbg.fr))<p> * </p>Louis Pasteur University - Strasbourg - France</p> * @author Emil Ivov * @version 1.1 */ public class CallProcessing { protected static final Console console = Console.getConsole(CallProcessing.class); protected SipManager sipManCallback = null; protected CallDispatcher callDispatcher = new CallDispatcher(); private final Properties sipProp; public CallProcessing(SipManager sipManCallback, Properties props) { this.sipManCallback = sipManCallback; sipProp = props; } void setSipManagerCallBack(SipManager sipManCallback) { this.sipManCallback = sipManCallback; } //============================= Remotely Initiated Processing =================================== //----------------------------- Responses public void processTrying(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(response); return; } //change status if (!call.getState().equals(Call.MOVING_LOCALLY)) call.setState(Call.DIALING); } finally { console.logExit(); } } public void processRinging(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call == null) { //?maybe we should just ignore it? sipManCallback.fireUnknownMessageReceived(response); return; } //change status call.setState(Call.RINGING); } finally { console.logExit(); } } /** * According to the RFC a * UAC canceling a request cannot rely on receiving a 487 (Request * Terminated) response for the original request, as an RFC 2543- * compliant UAS will not generate such a response. So we are closing the * call when sending the cancel request and here we don't do anything. * @param clientTransaction * @param response */ public void processRequestTerminated(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); //add any additional code here } finally { console.logExit(); } } public void processByeOK(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); //add any additional code here } finally { console.logExit(); } } public void processCancelOK(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); //add any additional code here } finally { console.logExit(); } } public void processInviteOK(ClientTransaction clientTransaction, Response ok) { try { console.logEntry(); //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(ok); return; } //Send ACK try { //Need to use dialog generated ACKs so that the remote UA core //sees them - Fixed by M.Ranganathan Request ack = clientTransaction.getDialog(). createRequest(Request.ACK); clientTransaction.getDialog().sendAck(ack); } catch (SipException ex) { console.error("Failed to acknowledge call!", ex); call.setState(Call.DISCONNECTED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to acknowledge call!" , ex) ); return; } // !!! set sdp content before setting call state as that is where //listeners get alerted and they need the sdp call.setRemoteSdpDescription(new String(ok.getRawContent())); //change status if (!call.getState().equals(Call.CONNECTED)) { call.setState(Call.CONNECTED); } } finally { console.logExit(); } } public void processBusyHere(ClientTransaction clientTransaction, Response busyHere) { try { console.logEntry(); //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(busyHere); return; } //change status call.setState(Call.BUSY); //it is the stack that should be sending the ACK so don't do it here } finally { console.logExit(); } } public void processCallError(ClientTransaction clientTransaction, Response notAcceptable) { try { console.logEntry(); if(console.isDebugEnabled()) { console.debug("Processing CALL ERROR response:\n" + notAcceptable); } //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(notAcceptable); return; } //change status call.setState(Call.FAILED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Remote party returned error response: " + notAcceptable.getStatusCode() +" - " + notAcceptable.getReasonPhrase() ) ); return; //it is the stack that should be sending the ACK so don't do it here } finally { console.logExit(); } } /** * Attempts to re-ogenerate the corresponding request with the proper * credentials and terminates the call if it fails. * * @param clientTransaction the corresponding transaction * @param response the challenge */ public void processAuthenticationChallenge(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); Request challengedRequest = clientTransaction.getRequest(); // Request reoriginatedRequest = null; ClientTransaction retryTran = sipManCallback.sipSecurityManager. handleChallenge(response, clientTransaction.getBranchId(), challengedRequest); // Dialog dialog = clientTransaction.getDialog(); //dialog.sendRequest(retryTran); retryTran.sendRequest(); } catch (SipSecurityException exc) { sipManCallback.fireCommunicationsError( new CommunicationsException("Authorization failed!", exc)); } // catch(){} catch (Exception exc) { sipManCallback.fireCommunicationsError( new CommunicationsException("Failed to resend a request " + "after a security challenge!", exc) ); } finally { callDispatcher.findCall(clientTransaction.getDialog()). setState(Call.FAILED); console.logExit(); } } //-------------------------- Requests --------------------------------- public void processInvite(ServerTransaction serverTransaction, Request invite) { try { console.logEntry(); Dialog dialog = serverTransaction.getDialog(); Call call = callDispatcher.createCall(dialog, invite); sipManCallback.fireCallReceived(call); //change status call.setState(Call.ALERTING); //sdp description may be in acks - bug report Laurent Michel ContentLengthHeader cl = invite.getContentLength(); if (cl != null && cl.getContentLength() > 0) { call.setRemoteSdpDescription(new String(invite.getRawContent())); } //Are we the one they are looking for? URI calleeURI = ( (ToHeader) invite.getHeader(ToHeader.NAME)). getAddress().getURI(); if (calleeURI.isSipURI()) { String calleeUser = ( (SipURI) calleeURI).getUser(); String localUser = sipManCallback.getLocalUser(); //user info is case sensitive according to rfc3261 if (!calleeUser.equals(localUser)) { sipManCallback.fireCallRejectedLocally( "The user specified by the caller did not match the local user!", invite ); call.setState(Call.DISCONNECTED); Response notFound = null; try { notFound = sipManCallback.messageFactory.createResponse( Response.NOT_FOUND, invite ); sipManCallback.attachToTag(notFound, dialog); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to create a NOT_FOUND response to an INVITE request!" , ex) ); return; } try { serverTransaction.sendResponse(notFound); if( console.isDebugEnabled() ) console.debug("sent a not found response: " + notFound); } catch (SipException ex) { call.setState(Call.DISCONNECTED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to send a NOT_FOUND response to an INVITE request!" , ex) ); return; } return; } } //Send RINGING Response ringing = null; try { ringing = sipManCallback.messageFactory.createResponse( Response.RINGING, invite ); sipManCallback.attachToTag(ringing, dialog); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to create a RINGING response to an INVITE request!" , ex) ); return; } try { serverTransaction.sendResponse(ringing); if( console.isDebugEnabled() ) console.debug("sent a ringing response: " + ringing); } catch (SipException ex) { call.setState(Call.DISCONNECTED); sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to send a RINGING response to an INVITE request!" , ex) ); return; } } finally { console.logExit(); } } public void processTimeout(Transaction transaction, Request request) { try { console.logEntry(); Call call = callDispatcher.findCall(transaction.getDialog()); if (call == null) { return; } sipManCallback.fireCommunicationsError( new CommunicationsException("The remote party has not replied!" + "The call will be disconnected") ); //change status call.setState(Call.DISCONNECTED); } finally { console.logExit(); } } public void processBye(ServerTransaction serverTransaction, Request byeRequest) { try { console.logEntry(); //find the call Call call = callDispatcher.findCall(serverTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(byeRequest); return; } //change status call.setState(Call.DISCONNECTED); //Send OK Response ok = null; try { ok = sipManCallback.messageFactory. createResponse(Response.OK, byeRequest); sipManCallback.attachToTag(ok, call.getDialog()); } catch (ParseException ex) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to construct an OK response to a BYE request!", ex) ); return; } try { serverTransaction.sendResponse(ok); if( console.isDebugEnabled() ) console.debug("sent response " + ok); } catch (SipException ex) { //This is not really a problem according to the RFC //so just dump to stdout should someone be interested console.error("Failed to send an OK response to BYE request," + "exception was:\n", ex); } } finally { console.logExit(); } } public void processAck(ServerTransaction serverTransaction, Request ackRequest) { try { console.logEntry(); if (!serverTransaction.getDialog().getFirstTransaction().getRequest(). getMethod().equals(Request.INVITE)) { console.debug("ignored ack"); return; } //find the call Call call = callDispatcher.findCall(serverTransaction. getDialog()); if (call == null) { //this is most probably the ack for a killed call - don't signal it //sipManCallback.fireUnknownMessageReceived(ackRequest); console.debug("didn't find an ack's call, returning"); return; } ContentLengthHeader cl = ackRequest.getContentLength(); if (cl != null && cl.getContentLength() > 0) { call.setRemoteSdpDescription(new String(ackRequest.getRawContent())); } //change status call.setState(Call.CONNECTED); } finally { console.logExit(); } } public void processCancel(ServerTransaction serverTransaction, Request cancelRequest) { try { console.logEntry(); if (!serverTransaction.getDialog().getFirstTransaction().getRequest(). getMethod().equals(Request.INVITE)) { //For someone else console.debug("ignoring request"); return; } //find the call Call call = callDispatcher.findCall(serverTransaction. getDialog()); if (call == null) { sipManCallback.fireUnknownMessageReceived(cancelRequest); return; } //change status call.setState(Call.DISCONNECTED); // Cancels should be OK-ed and the initial transaction - terminated // (report and fix by Ranga) try { Response ok = sipManCallback.messageFactory.createResponse(Response. OK, cancelRequest); sipManCallback.attachToTag(ok, call.getDialog()); serverTransaction.sendResponse(ok); if( console.isDebugEnabled() ) console.debug("sent ok response: " + ok); } catch (ParseException ex) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to create an OK Response to an CANCEL request.", ex)); } catch (SipException ex) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to send an OK Response to an CANCEL request.", ex)); } try { //stop the invite transaction as well Transaction tran = call.getDialog().getFirstTransaction(); //should be server transaction and misplaced cancels should be //filtered by the stack but it doesn't hurt checking anyway if (! (tran instanceof ServerTransaction)) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Received a misplaced CANCEL request!")); return; } ServerTransaction inviteTran = (ServerTransaction) tran; Request invite = call.getDialog().getFirstTransaction().getRequest(); Response requestTerminated = sipManCallback. messageFactory. createResponse(Response.REQUEST_TERMINATED, invite); sipManCallback.attachToTag(requestTerminated, call.getDialog()); inviteTran.sendResponse(requestTerminated); if( console.isDebugEnabled() ) console.debug("sent request terminated response: " + requestTerminated); } catch (ParseException ex) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to create a REQUEST_TERMINATED Response to an INVITE request.", ex)); } catch (SipException ex) { sipManCallback.fireCommunicationsError( new CommunicationsException( "Failed to send an REQUEST_TERMINATED Response to an INVITE request.", ex)); } } finally { console.logExit(); } } // ----------------- Responses -------------------------- //NOT FOUND public void processNotFound(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); if (!clientTransaction.getDialog().getFirstTransaction().getRequest(). getMethod().equals(Request.INVITE)) { //Not for us console.debug("ignoring not found response"); return; } //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); if (call != null) { call.setState(Call.DISCONNECTED); } sipManCallback.fireCallRejectedRemotely( "Server returned a NOT FOUND Response", response ); } finally { console.logExit(); } } public void processNotImplemented(ClientTransaction clientTransaction, Response response) { try { console.logEntry(); if (!clientTransaction.getDialog().getFirstTransaction().getRequest(). getMethod().equals(Request.INVITE)) { //Not for us console.debug("ignoring not implemented response"); return; } //find the call Call call = callDispatcher.findCall(clientTransaction. getDialog()); call.setState(Call.DISCONNECTED); sipManCallback.fireCallRejectedRemotely( "Server returned a NOT IMPLEMENTED Response", response ); } finally { console.logExit(); } } //-------------------------------- User Initiated processing --------------------------------- public Call invite(String callee, String sdpContent) throws CommunicationsException { try { console.logEntry(); callee = callee.trim(); //Handle default domain name (i.e. transform 1234 -> 1234@sip.com String defaultDomainName = sipProp.getProperty("net.java.sip.communicator.sip.DEFAULT_DOMAIN_NAME"); if (defaultDomainName != null //no sip scheme && callee.indexOf('@') == -1 //most probably a sip uri ) { callee = callee + "@" + defaultDomainName; } //Let's be uri fault tolerant if (callee.toLowerCase().indexOf("sip:") == -1 //no sip scheme && callee.indexOf('@') != -1 //most probably a sip uri ) { callee = "sip:" + callee; } //Request URI URI requestURI; try { requestURI = sipManCallback.addressFactory.createURI(callee); } catch (ParseException ex) { console.error(callee + " is not a legal SIP uri!", ex); throw new CommunicationsException(callee + " is not a legal SIP uri!", ex); } //Call ID CallIdHeader callIdHeader = sipManCallback.sipProvider.getNewCallId(); //CSeq CSeqHeader cSeqHeader; try { cSeqHeader = sipManCallback.headerFactory.createCSeqHeader(1, Request.INVITE); } catch (ParseException ex) { //Shouldn't happen console.error(ex, ex); throw new CommunicationsException( "An unexpected erro occurred while" + "constructing the CSeqHeadder", ex); } catch (InvalidArgumentException ex) { //Shouldn't happen console.error( "An unexpected erro occurred while" + "constructing the CSeqHeadder", ex); throw new CommunicationsException( "An unexpected erro occurred while" + "constructing the CSeqHeadder", ex); } //FromHeader FromHeader fromHeader = sipManCallback.getFromHeader(); //ToHeader Address toAddress = sipManCallback.addressFactory.createAddress( requestURI); ToHeader toHeader; try { toHeader = sipManCallback.headerFactory.createToHeader( toAddress, null); } catch (ParseException ex) { //Shouldn't happen console.error( "Null is not an allowed tag for the to header!", ex); throw new CommunicationsException( "Null is not an allowed tag for the to header!", ex); } //ViaHeaders ArrayList viaHeaders = sipManCallback.getLocalViaHeaders(); //MaxForwards MaxForwardsHeader maxForwards = sipManCallback.getMaxForwardsHeader(); //Contact ContactHeader contactHeader = sipManCallback.getContactHeader(); Request invite = null; try { invite = sipManCallback.messageFactory.createRequest(requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); } catch (ParseException ex) { console.error( "Failed to create invite Request!", ex); throw new CommunicationsException( "Failed to create invite Request!", ex); } // invite.addHeader(contactHeader); //Content ContentTypeHeader contentTypeHeader = null; try { //content type should be application/sdp (not applications) //reported by Oleg Shevchenko (Miratech) contentTypeHeader = sipManCallback.headerFactory.createContentTypeHeader( "application", "sdp"); } catch (ParseException ex) { //Shouldn't happen console.error( "Failed to create a content type header for the INVITE request", ex); throw new CommunicationsException( "Failed to create a content type header for the INVITE request", ex); } try { invite.setContent(sdpContent, contentTypeHeader); } catch (ParseException ex) { console.error( "Failed to parse sdp data while creating invite request!", ex); throw new CommunicationsException( "Failed to parse sdp data while creating invite request!", ex); } //Transaction ClientTransaction inviteTransaction; try { inviteTransaction = sipManCallback.sipProvider. getNewClientTransaction(invite); } catch (TransactionUnavailableException ex) { console.error( "Failed to create inviteTransaction.\n" + "This is most probably a network connection error.", ex); throw new CommunicationsException( "Failed to create inviteTransaction.\n" + "This is most probably a network connection error.", ex); } try { inviteTransaction.sendRequest(); if( console.isDebugEnabled() ) console.debug("sent request: " + invite); } catch (SipException ex) { console.error( "An error occurred while sending invite request", ex); throw new CommunicationsException( "An error occurred while sending invite request", ex); } Call call = callDispatcher.createCall(inviteTransaction. getDialog(), invite); call.setState(Call.DIALING); return call; } finally { console.logExit(); } } //end call public void endCall(int callID) throws CommunicationsException { try { console.logEntry(); Call call = callDispatcher.getCall(callID); if (call == null) { console.error( "Could not find call with id=" + callID); throw new CommunicationsException( "Could not find call with id=" + callID); } Dialog dialog = call.getDialog(); if (call.getState().equals(Call.CONNECTED) || call.getState().equals(Call.RECONNECTED)) { call.setState(Call.DISCONNECTED); sayBye(dialog); } else if (call.getState().equals(Call.DIALING) || call.getState().equals(Call.RINGING)) { if (dialog.getFirstTransaction() != null) { try { //Someone knows about us. Let's be polite and say we are leaving sayCancel(dialog); } catch (CommunicationsException ex) { //something went wrong let's just tell the others console.error( "Could not send the CANCEL request! " + "Remote party won't know we're leaving!", ex); sipManCallback.fireCommunicationsError( new CommunicationsException( "Could not send the CANCEL request! " + "Remote party won't know we're leaving!", ex)); } } call.setState(Call.DISCONNECTED); } else if (call.getState().equals(Call.ALERTING)) { call.setState(Call.DISCONNECTED); sayBusyHere(dialog); } //For FAILE and BUSY we only need to update CALL_STATUS else if (call.getState().equals(Call.BUSY)) { call.setState(Call.DISCONNECTED); } else if (call.getState().equals(Call.FAILED)) { call.setState(Call.DISCONNECTED); } else { call.setState(Call.DISCONNECTED); console.error( "Could not determine call state!"); throw new CommunicationsException ("Could not determine call state!"); } } finally { console.logExit(); } } //end call //Bye private void sayBye(Dialog dialog) throws CommunicationsException { try { console.logEntry(); // Request request = dialog.getFirstTransaction().getRequest(); Request bye = null; try { bye = dialog.createRequest(Request.BYE); } catch (SipException ex) { console.error( "Failed to create bye request!", ex); throw new CommunicationsException( "Failed to create bye request!", ex); } ClientTransaction clientTransaction = null; try { clientTransaction = sipManCallback.sipProvider.getNewClientTransaction(bye); } catch (TransactionUnavailableException ex) { console.error( "Failed to construct a client transaction from the BYE request", ex); throw new CommunicationsException( "Failed to construct a client transaction from the BYE request", ex); } try { dialog.sendRequest(clientTransaction); if( console.isDebugEnabled() ) console.debug("sent request: " + bye); } catch (SipException ex1) { throw new CommunicationsException("Failed to send the BYE request"); } } finally { console.logExit(); } } //bye //cancel private void sayCancel(Dialog dialog) throws CommunicationsException { try { console.logEntry(); // Request request = dialog.getFirstTransaction().getRequest(); if (dialog.isServer()) { console.error("Cannot cancel a server transaction"); throw new CommunicationsException( "Cannot cancel a server transaction"); } ClientTransaction clientTransaction = (ClientTransaction) dialog.getFirstTransaction(); try { Request cancel = clientTransaction.createCancel(); ClientTransaction cancelTransaction = sipManCallback.sipProvider.getNewClientTransaction(cancel); cancelTransaction.sendRequest(); if( console.isDebugEnabled() ) console.debug("sent request: " + cancel); } catch (SipException ex) { console.error("Failed to send the CANCEL request", ex); throw new CommunicationsException( "Failed to send the CANCEL request", ex); } } finally { console.logExit(); } } //cancel //busy here private void sayBusyHere(Dialog dialog) throws CommunicationsException { try { console.logEntry(); Request request = dialog.getFirstTransaction().getRequest(); Response busyHere = null; try { busyHere = sipManCallback. messageFactory.createResponse(Response.BUSY_HERE, request); sipManCallback.attachToTag(busyHere, dialog); } catch (ParseException ex) { console.error("Failed to create the BUSY_HERE response!", ex); throw new CommunicationsException( "Failed to create the BUSY_HERE response!", ex); } if (!dialog.isServer()) { console.error("Cannot send BUSY_HERE in a client transaction"); throw new CommunicationsException( "Cannot send BUSY_HERE in a client transaction"); } ServerTransaction serverTransaction = (ServerTransaction) dialog.getFirstTransaction(); try { serverTransaction.sendResponse(busyHere); if( console.isDebugEnabled() ) console.debug("sent response: " + busyHere); } catch (SipException ex) { console.error("Failed to send the BUSY_HERE response", ex); throw new CommunicationsException( "Failed to send the BUSY_HERE response", ex); } } finally { console.logExit(); } } //busy here //------------------ say ok public void sayOK(int callID, String sdpContent) throws CommunicationsException { try { console.logEntry(); Call call = callDispatcher.getCall(callID); if (call == null) { console.error("Failed to find call with id=" + callID); throw new CommunicationsException( "Failed to find call with id=" + callID); } Dialog dialog = call.getDialog(); if (dialog == null) { call.setState(Call.DISCONNECTED); console.error( "Failed to extract call's associated dialog! Ending Call!"); throw new CommunicationsException( "Failed to extract call's associated dialog! Ending Call!"); } Transaction transaction = dialog.getFirstTransaction(); if (transaction == null || !dialog.isServer()) { call.setState(Call.DISCONNECTED); throw new CommunicationsException( "Failed to extract a ServerTransaction " +"from the call's associated dialog!"); } ServerTransaction serverTransaction = (ServerTransaction) transaction; Response ok = null; try { ok = sipManCallback.messageFactory.createResponse( Response.OK, dialog.getFirstTransaction().getRequest()); sipManCallback.attachToTag(ok, dialog); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); console.error( "Failed to construct an OK response to an INVITE request", ex); throw new CommunicationsException( "Failed to construct an OK response to an INVITE request", ex); } //Content ContentTypeHeader contentTypeHeader = null; try { //content type should be application/sdp (not applications) //reported by Oleg Shevchenko (Miratech) contentTypeHeader = sipManCallback.headerFactory.createContentTypeHeader( "application", "sdp"); } catch (ParseException ex) { //Shouldn't happen call.setState(Call.DISCONNECTED); console.error( "Failed to create a content type header for the OK request", ex); throw new CommunicationsException( "Failed to create a content type header for the OK request", ex); } try { ok.setContent(sdpContent, contentTypeHeader); } catch (NullPointerException ex) { call.setState(Call.DISCONNECTED); console.error( "No sdp data was provided for the ok response to an INVITE request!", ex); throw new CommunicationsException( "No sdp data was provided for the ok response to an INVITE request!", ex); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); console.error( "Failed to parse sdp data while creating invite request!", ex); throw new CommunicationsException( "Failed to parse sdp data while creating invite request!", ex); } //TODO This is here provisionally as my remote user agent that I am using for //testing is not doing it. It is not correct from the protocol point of view //and should probably be removed if ( ( (ToHeader) ok.getHeader(ToHeader.NAME)).getTag() == null) { try { ( (ToHeader) ok.getHeader(ToHeader.NAME)).setTag(Integer. toString(dialog.hashCode())); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); throw new CommunicationsException( "Unable to set to tag", ex ); } } ContactHeader contactHeader = sipManCallback.getContactHeader(); ok.addHeader(contactHeader); try { serverTransaction.sendResponse(ok); if( console.isDebugEnabled() ) console.debug("sent response " + ok); } catch (SipException ex) { call.setState(Call.DISCONNECTED); console.error( "Failed to send an OK response to an INVITE request", ex ); throw new CommunicationsException( "Failed to send an OK response to an INVITE request", ex ); } } finally { console.logExit(); } } //answer call //------------------ Internal Error public void sayInternalError(int callID) throws CommunicationsException { try { console.logEntry(); Call call = callDispatcher.getCall(callID); if (call == null) { console.error("Failed to find call with id=" + callID); throw new CommunicationsException( "Failed to find call with id=" + callID ); } Dialog dialog = call.getDialog(); if (dialog == null) { call.setState(Call.DISCONNECTED); console.error( "Failed to extract call's associated dialog! Ending Call!" ); throw new CommunicationsException( "Failed to extract call's associated dialog! Ending Call!" ); } Transaction transaction = dialog.getFirstTransaction(); if (transaction == null || !dialog.isServer()) { call.setState(Call.DISCONNECTED); console.error( "Failed to extract a transaction" +" from the call's associated dialog!"); throw new CommunicationsException( "Failed to extract a transaction from the call's associated dialog!" ); } ServerTransaction serverTransaction = (ServerTransaction) transaction; Response internalError = null; try { internalError = sipManCallback.messageFactory.createResponse( Response.SERVER_INTERNAL_ERROR, dialog.getFirstTransaction().getRequest()); sipManCallback.attachToTag(internalError, dialog); } catch (ParseException ex) { call.setState(Call.DISCONNECTED); console.error( "Failed to construct an OK response to an INVITE request", ex); throw new CommunicationsException( "Failed to construct an OK response to an INVITE request", ex); } ContactHeader contactHeader = sipManCallback.getContactHeader(); internalError.addHeader(contactHeader); try { serverTransaction.sendResponse(internalError); if( console.isDebugEnabled() ) console.debug("sent response: " + internalError); } catch (SipException ex) { call.setState(Call.DISCONNECTED); console.error( "Failed to send an OK response to an INVITE request", ex); throw new CommunicationsException( "Failed to send an OK response to an INVITE request", ex ); } } finally { console.logExit(); } } //internal error public CallDispatcher getCallDispatcher() { return callDispatcher; } //The following method is currently being implemented and tested public void processReInvite(ServerTransaction serverTransaction, Request request) { try { console.logEntry(); console.error("processReInvite is not yet implemented"); }finally { console.logExit(); } } }