/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sipmack.sip;
import gov.nist.javax.sip.header.CSeq;
import java.text.ParseException;
import java.util.ArrayList;
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.AllowHeader;
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.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import net.java.sipmack.common.Log;
import net.java.sipmack.sip.security.SipSecurityException;
import org.jivesoftware.spark.phone.PhoneManager;
/**
* <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 SipManager sipManCallback = null;
protected CallDispatcher callDispatcher = new CallDispatcher();
CallProcessing() {
}
CallProcessing(SipManager sipManCallback) {
this.sipManCallback = sipManCallback;
}
void setSipManagerCallBack(SipManager sipManCallback) {
this.sipManCallback = sipManCallback;
}
// ============================= Remotely Initiated Processing
// ===================================
// ----------------------------- Responses
void processTrying(ClientTransaction clientTransaction, Response response) {
// 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);
}
void processRinging(ClientTransaction clientTransaction, Response response) {
// find the call
Call call = callDispatcher.findCall(clientTransaction.getDialog());
String sdp = response.getRawContent() != null ? response
.getRawContent().toString() : "";
call.setRemoteSdpDescription(sdp);
// change status
call.setState(Call.RINGING);
}
void processRingingBack(ClientTransaction clientTransaction,
Response response) {
// find the call
Call call = callDispatcher.findCall(clientTransaction.getDialog());
call.setRemoteSdpDescription(new String(response.getRawContent()));
// change status
call.setState(Call.RINGING);
}
/**
* 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
*/
void processRequestTerminated(ClientTransaction clientTransaction,
Response response) {
try {
// add any additional code here
}
finally {
}
}
void processByeOK(ClientTransaction clientTransaction, Response response) {
try {
// add any additional code here
}
finally {
}
}
void processCancelOK(ClientTransaction clientTransaction, Response response) {
try {
// add any additional code here
}
finally {
}
}
void processInviteOK(ClientTransaction clientTransaction, Response ok) {
// 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 ackRequest = clientTransaction.getDialog()
.createAck(((CSeqHeader)ok.getHeader(CSeqHeader.NAME)).getSeqNumber());
clientTransaction.getDialog().sendAck(ackRequest);
}
catch (SipException ex) {
ex.printStackTrace();
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to acknowledge call!", ex));
return;
} catch (InvalidArgumentException e) {
e.printStackTrace();
call.setState(Call.DISCONNECTED);
sipManCallback.fireCommunicationsError(new CommunicationsException(
"Failed to create ack!", e));
return;
}
call.setRemoteSdpDescription(new String(ok.getRawContent()));
// change status
if (!call.getState().equals(Call.CONNECTED)) {
call.setState(Call.CONNECTED);
}
}
void processBusyHere(ClientTransaction clientTransaction, Response busyHere) {
// 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
}
void processCallError(ClientTransaction clientTransaction,
Response 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
}
/**
* 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
*/
void processAuthenticationChallenge(ClientTransaction clientTransaction,
Response response) {
try {
ClientTransaction retryTran = sipManCallback.sipSecurityManager
.handleChallenge(response, clientTransaction);
// There is a new dialog that will be started with this request. Get
// that dialog and record it into the Call objet for later use (by
// Bye-s for example).
Call call = callDispatcher.findCall(clientTransaction.getDialog());
call.setDialog(retryTran.getDialog());
call.setInitialRequest(retryTran.getRequest());
retryTran.sendRequest();
}
catch (SipSecurityException exc) {
callDispatcher.findCall(clientTransaction.getDialog()).setState(
Call.FAILED);
sipManCallback.fireCommunicationsError(new CommunicationsException(
"Authorization failed!", exc));
}
catch (Exception exc) {
callDispatcher.findCall(clientTransaction.getDialog()).setState(
Call.FAILED);
sipManCallback.fireCommunicationsError(new CommunicationsException(
"Failed to resend a request "
+ "after a security challenge!", exc));
}
}
// -------------------------- Requests ---------------------------------
void processInvite(ServerTransaction serverTransaction, Request invite) {
Dialog dialog = serverTransaction.getDialog();
if (!sipManCallback.isBusy() && !(PhoneManager.isUseStaticLocator()&&PhoneManager.isUsingMediaLocator())) {
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();
/**
* @todo We shoud rather ask the user what to do here as some
* would add prefixes or change user URIs
*/
if (calleeURI.isSipURI()) {
boolean assertUserMatch = SIPConfig
.isFailCallInUserMismatch();
// user info is case sensitive according to rfc3261
if (assertUserMatch) {
String calleeUser = ((SipURI) calleeURI).getUser();
String localUser = sipManCallback.getLocalUser();
if (calleeUser != null && !calleeUser.equals(localUser)) {
sipManCallback
.fireCallRejectedLocally(
"The user specified by the caller did not match the local user!",
invite, call);
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);
}
catch (SipException ex) {
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
ex));
return;
} catch (InvalidArgumentException e) {
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
e));
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);
}
catch (SipException ex) {
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a RINGING response to an INVITE request!",
ex));
return;
} catch (InvalidArgumentException e) {
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
e));
return;
}
} else {
// Send BUSY_HERE
Response busy = null;
try {
busy = sipManCallback.messageFactory.createResponse(
Response.BUSY_HERE, invite);
sipManCallback.attachToTag(busy, dialog);
}
catch (ParseException ex) {
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to create a RINGING response to an INVITE request!",
ex));
return;
}
try {
serverTransaction.sendResponse(busy);
}
catch (SipException ex) {
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a RINGING response to an INVITE request!",
ex));
return;
} catch (InvalidArgumentException e) {
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a RINGING response to an INVITE request!",
e));
return;
}
}
}
void processTimeout(Transaction transaction, Request request) {
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);
}
void processBye(ServerTransaction serverTransaction, Request byeRequest) {
try {
// find the call
Call call = callDispatcher.findCall(serverTransaction.getDialog());
if (call == null) {
Log.debug("No call find");
sipManCallback.fireUnknownMessageReceived(byeRequest);
return;
}
// 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);
}
catch (SipException ex) {
// This is not really a problem according to the RFC
// so just dump to stdout should someone be interested
}
endCall(call.getID());
}
catch (Exception e) {
Log.error("processBye", e);
}
}
void processAck(ServerTransaction serverTransaction, Request ackRequest) {
if (!serverTransaction.getDialog().getFirstTransaction()
.getRequest().getMethod().equals(Request.INVITE)) {
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);
return;
}
ContentLengthHeader cl = ackRequest.getContentLength();
if (cl != null && cl.getContentLength() > 0) {
call.setRemoteSdpDescription(new String(ackRequest
.getRawContent()));
}
// change status
call.setState(Call.CONNECTED);
}
void processCancel(ServerTransaction serverTransaction,
Request cancelRequest) {
if (!serverTransaction.getDialog().getFirstTransaction()
.getRequest().getMethod().equals(Request.INVITE)) {
// For someone else
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);
}
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));
} catch (InvalidArgumentException e) {
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
e));
}
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);
}
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));
} catch (InvalidArgumentException e) {
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
e));
}
}
// ----------------- Responses --------------------------
// NOT FOUND
void processNotFound(ClientTransaction clientTransaction, Response response) {
if (!clientTransaction.getDialog().getFirstTransaction()
.getRequest().getMethod().equals(Request.INVITE)) {
// Not for us
return;
}
// find the call
Call call = callDispatcher.findCall(clientTransaction.getDialog());
if (call != null)
call.setState(Call.DISCONNECTED);
sipManCallback.fireCallRejectedRemotely(
"Number NOT found at the server.", response, call);
}
void processNotImplemented(ClientTransaction clientTransaction,
Response response) {
if (!clientTransaction.getDialog().getFirstTransaction()
.getRequest().getMethod().equals(Request.INVITE)) {
// Not for us
return;
}
// find the call
Call call = callDispatcher.findCall(clientTransaction.getDialog());
call.setState(Call.DISCONNECTED);
sipManCallback.fireCallRejectedRemotely(
"Server cannot dial this number.", response, call);
}
// -------------------------------- User Initiated processing
// ---------------------------------
Call invite(String callee, String sdpContent)
throws CommunicationsException {
callee = callee.trim();
// Remove excessive characters from phone numbers such as '
// ','(',')','-'
String excessiveChars = SIPConfig.getExcessiveURIChar();
if (excessiveChars != null) {
StringBuffer calleeBuff = new StringBuffer(callee);
for (int i = 0; i < excessiveChars.length(); i++) {
String charToDeleteStr = excessiveChars.substring(i, i + 1);
int charIndex = -1;
while ((charIndex = calleeBuff.indexOf(charToDeleteStr)) != -1)
calleeBuff.delete(charIndex, charIndex + 1);
}
callee = calleeBuff.toString();
}
// Handle default domain name (i.e. transform 1234 -> 1234@sip.com
String defaultDomainName = SIPConfig.getDefaultDomain();
if (defaultDomainName != null // no sip scheme
&& !callee.trim().startsWith("tel:")
&& 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) {
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(1L,
Request.INVITE);
}
catch (ParseException ex) {
// Shouldn't happen
throw new CommunicationsException(
"An unexpected erro occurred while"
+ "constructing the CSeqHeadder", ex);
}
catch (InvalidArgumentException ex) {
// Shouldn't happen
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
throw new CommunicationsException(
"Null is not an allowed tag for the to header!", ex);
}
// ViaHeaders
ArrayList<ViaHeader> 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) {
throw new CommunicationsException(
"Failed to create invite Request!", ex);
}
//
invite.addHeader(contactHeader);
AllowHeader allow = null;
try {
allow = sipManCallback.headerFactory.createAllowHeader("INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO");
invite.addHeader(allow);
} catch (ParseException e) {
Log.error(e);
}
// 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
throw new CommunicationsException(
"Failed to create a content type header for the INVITE request",
ex);
}
try {
invite.setContent(sdpContent, contentTypeHeader);
}
catch (ParseException 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) {
throw new CommunicationsException(
"Failed to create inviteTransaction.\n"
+ "This is most probably a network connection error.",
ex);
}
try {
inviteTransaction.sendRequest();
}
catch (SipException 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;
}
// Hold
public void hold(int callID, String sdpContent)
throws CommunicationsException {
Call call = callDispatcher.getCall(callID);
Request invite = null;
invite = call.getDialog().getFirstTransaction().getRequest();
try {
invite = call.getDialog().createRequest(Request.INVITE);
} catch (SipException e) {
Log.error("hold", e);
}
long cseq = ((CSeq) (invite.getHeader(CSeq.NAME)))
.getSequenceNumber() + 1;
invite.removeHeader(CSeq.NAME);
try {
invite.addHeader(sipManCallback.headerFactory.createCSeqHeader(cseq, Request.INVITE));
}
catch (Exception e) {
Log.error("hold", e);
}
invite.removeHeader(ViaHeader.NAME);
for (ViaHeader via : sipManCallback.getLocalViaHeaders())
invite.addHeader(via);
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
throw new CommunicationsException(
"Failed to create a content type header for the INVITE request",
ex);
}
try {
invite.setContent(sdpContent, contentTypeHeader);
}
catch (ParseException ex) {
throw new CommunicationsException(
"Failed to parse sdp data while creating invite request!",
ex);
}
// Transaction
ClientTransaction inviteTransaction;
try {
inviteTransaction = sipManCallback.sipProvider.getNewClientTransaction(invite);
call.getDialog().sendRequest(inviteTransaction);
call.setLastRequest(invite);
}
catch (SipException ee) {
Log.error("hold", ee);
}
return;
}
// send DTMF
void sendDTMF(int callID, String digit) {
try {
Call call = callDispatcher.getCall(callID);
Dialog dialog = call.getDialog();
sendNumDTMF(dialog, digit);
//
}
catch (CommunicationsException e) {
}
} // send DTMF
// end call
void endCall(int callID) throws CommunicationsException {
Call call = callDispatcher.getCall(callID);
if (call == null) {
throw new CommunicationsException(
"Could not find call with id=" + callID);
} else {
endCall(call);
}
} // end call
void endCall(Call call) throws CommunicationsException {
if (call == null) {
throw new CommunicationsException(
"Could not find call");
}
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
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 FAILED 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);
throw new CommunicationsException(
"Could not determine call state!");
}
} // end call
// send dtmf
private void sendNumDTMF(Dialog dialog, String digit)
throws CommunicationsException {
Request info = null;
String body = "Signal=" + digit + "\nDuration=160";
String contentType = "application/dtmf-relay";
String[] contentTypeTab = contentType.split("/");
ContentTypeHeader contentTypeHeader = null;
try {
info = dialog.createRequest(Request.INFO);
try {
contentTypeHeader = sipManCallback.headerFactory
.createContentTypeHeader(contentTypeTab[0],
contentTypeTab[1]);
info.setContent(body, contentTypeHeader);
}
catch (ParseException ex) {
throw new CommunicationsException(
"ContentType Header must look like type/subtype!",
ex);
}
}
catch (SipException ex) {
throw new CommunicationsException(
"Failed to create bye request!", ex);
}
ClientTransaction clientTransaction = null;
try {
clientTransaction = sipManCallback.sipProvider
.getNewClientTransaction(info);
}
catch (TransactionUnavailableException ex) {
throw new CommunicationsException(
"Failed to construct a client transaction from the INFO request",
ex);
}
try {
dialog.sendRequest(clientTransaction);
}
catch (SipException ex1) {
throw new CommunicationsException(
"Failed to send the INFO request");
}
}
// send message
// private void sendInfoMessage(Dialog dialog, String body)
// throws CommunicationsException {
// Request request = dialog.getFirstTransaction().getRequest();
// Request info = null;
// String contentType = "application/dtmd-relay";
// String[] contentTypeTab = contentType.split("/");
// ContentTypeHeader contentTypeHeader = null;
// try {
// info = dialog.createRequest(Request.INFO);
// try {
// contentTypeHeader = sipManCallback.headerFactory
// .createContentTypeHeader(contentTypeTab[0],
// contentTypeTab[1]);
// info.setContent(body, contentTypeHeader);
// }
// catch (ParseException ex) {
//
// throw new CommunicationsException(
// "ContentType Header must look like type/subtype!",
// ex);
// }
// }
// catch (SipException ex) {
//
// throw new CommunicationsException(
// "Failed to create bye request!", ex);
// }
// ClientTransaction clientTransaction = null;
// try {
// clientTransaction = sipManCallback.sipProvider
// .getNewClientTransaction(info);
// }
// catch (TransactionUnavailableException ex) {
//
// throw new CommunicationsException(
// "Failed to construct a client transaction from the INFO request",
// ex);
// }
// try {
// dialog.sendRequest(clientTransaction);
// }
// catch (SipException ex1) {
// throw new CommunicationsException(
// "Failed to send the INFO request");
// }
//
// } // send message
// Bye
private void sayBye(Dialog dialog) throws CommunicationsException {
Request bye = null;
bye = dialog.getFirstTransaction().getRequest();
try {
bye = dialog.createRequest(Request.BYE);
} catch (SipException e) {
Log.error("bye", e);
}
long cseq = ((CSeq) (bye.getHeader(CSeq.NAME)))
.getSequenceNumber() + 1;
bye.removeHeader(CSeq.NAME);
try {
bye.addHeader(sipManCallback.headerFactory.createCSeqHeader(cseq, Request.BYE));
}
catch (Exception e) {
Log.error("bye", e);
}
bye.removeHeader(ViaHeader.NAME);
for (ViaHeader via : sipManCallback.getLocalViaHeaders())
bye.addHeader(via);
ContactHeader contactHeader = sipManCallback.getContactHeader();
bye.addHeader(contactHeader);
AllowHeader allow = null;
try {
allow = sipManCallback.headerFactory.createAllowHeader("INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO");
bye.addHeader(allow);
} catch (ParseException e) {
Log.error(e);
}
// Transaction
ClientTransaction inviteTransaction;
try {
inviteTransaction = sipManCallback.sipProvider.getNewClientTransaction(bye);
dialog.sendRequest(inviteTransaction);
}
catch (SipException ee) {
Log.error("bye", ee);
}
return;
} // bye
// cancel
private void sayCancel(Dialog dialog) throws CommunicationsException {
if (dialog.isServer()) {
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();
}
catch (SipException ex) {
throw new CommunicationsException(
"Failed to send the CANCEL request", ex);
}
} // cancel
// busy here
private void sayBusyHere(Dialog dialog) throws CommunicationsException {
Request request = dialog.getFirstTransaction().getRequest();
Response busyHere = null;
try {
busyHere = sipManCallback.messageFactory.createResponse(
Response.BUSY_HERE, request);
sipManCallback.attachToTag(busyHere, dialog);
}
catch (ParseException ex) {
throw new CommunicationsException(
"Failed to create the BUSY_HERE response!", ex);
}
if (!dialog.isServer()) {
throw new CommunicationsException(
"Cannot send BUSY_HERE in a client transaction");
}
ServerTransaction serverTransaction = (ServerTransaction) dialog
.getFirstTransaction();
try {
serverTransaction.sendResponse(busyHere);
}
catch (SipException ex) {
throw new CommunicationsException(
"Failed to send the BUSY_HERE response", ex);
} catch (InvalidArgumentException e) {
throw new CommunicationsException(
"Failed to send the BUSY_HERE response", e);
}
} // busy here
// ------------------ say ok
public void sayOK(int callID, String sdpContent)
throws CommunicationsException {
Call call = callDispatcher.getCall(callID);
if (call == null) {
throw new CommunicationsException(
"Failed to find call with id=" + callID);
}
if (!call.isIncoming()) return;
Dialog dialog = call.getDialog();
if (dialog == null) {
call.setState(Call.DISCONNECTED);
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);
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);
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);
throw new CommunicationsException(
"No sdp data was provided for the ok response to an INVITE request!",
ex);
}
catch (ParseException ex) {
call.setState(Call.DISCONNECTED);
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);
}
catch (SipException ex) {
call.setState(Call.DISCONNECTED);
throw new CommunicationsException(
"Failed to send an OK response to an INVITE request",
ex);
} catch (InvalidArgumentException e) {
call.setState(Call.DISCONNECTED);
sipManCallback
.fireCommunicationsError(new CommunicationsException(
"Failed to send a NOT_FOUND response to an INVITE request!",
e));
}
} // answer call
// ------------------ Internal Error
void sayInternalError(int callID) throws CommunicationsException {
Call call = callDispatcher.getCall(callID);
if (call == null) {
throw new CommunicationsException(
"Failed to find call with id=" + callID);
}
Dialog dialog = call.getDialog();
if (dialog == null) {
call.setState(Call.DISCONNECTED);
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 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);
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);
}
catch (SipException ex) {
call.setState(Call.DISCONNECTED);
throw new CommunicationsException(
"Failed to send an OK response to an INVITE request",
ex);
} catch (InvalidArgumentException e) {
call.setState(Call.DISCONNECTED);
throw new CommunicationsException(
"Failed to send an OK response to an INVITE request",
e);
}
} // internal error
/**
* @return Returns the callDispatcher.
* @uml.property name="callDispatcher"
*/
CallDispatcher getCallDispatcher() {
return callDispatcher;
}
// The following method is currently being implemented and tested
protected void processReInvite(ServerTransaction serverTransaction,
Request invite) {
sipManCallback.setBusy(false);
Log.debug("REINVITE DETECTED");
Call call = callDispatcher.findCall(serverTransaction.getDialog());
if (call == null) {
call = callDispatcher.createCall(serverTransaction.getDialog(),
invite);
}
call.setRemoteSdpDescription(new String(invite.getRawContent()));
Log.debug("CALL CONNECT EVENT");
try {
sayOK(call.getID(), call.getLocalSdpDescription().toString());
} catch (CommunicationsException e) {
Log.error("Re-Invite", e);
}
call.setState(Call.MOVING_REMOTELY);
call.setState(Call.CONNECTED);
}
}