/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip.server;
import com.sun.voip.CallState;
import com.sun.voip.Logger;
import com.sun.voip.MediaInfo;
import com.sun.voip.SdpInfo;
import com.sun.voip.RtpPacket;
import javax.sip.*;
import javax.sip.header.*;
import javax.sip.message.*;
import javax.sip.address.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.text.ParseException;
import java.util.Vector;
/**
* SipIncomingCallAgent handles calls from outside directed to us.
* We are acting as a user agent server and handle INVITE requests.
*
* We reply to the INVITE with an OK and the port number of a new conference
* with just this call. The purpose of starting a new conference is to
* watch for dtmf keys identifying the person and the real conference to join.
*/
public class SipIncomingCallAgent extends CallSetupAgent implements SipListener {
private ServerTransaction st;
private SipServer.SipServerCallback sipServerCallback;
private SipUtil sipUtil;
/*
* Maintain a list of all sipCallId's for incoming calls
* so we can detect duplicate INVITES.
*/
private static Vector sipCallIds = new Vector();
private String sipCallId; // The sipCallId for this agent
private boolean receivedBye;
/**
* Constructor
*/
public SipIncomingCallAgent(CallHandler callHandler, Object o) {
super(callHandler);
sipServerCallback = SipServer.getSipServerCallback();
MediaInfo mixerMediaPreference = callHandler.getConferenceManager().getMediaInfo();
sipUtil = new SipUtil(mixerMediaPreference);
handleInvite((RequestEvent)o);
}
/**
* Processes SIP requests. The only request being handled is INVITE.
* @param requestEvent the event containing the SIP request
*/
public synchronized void processRequest(RequestEvent requestEvent) {
Request request = requestEvent.getRequest();
if (request.getMethod().equals(Request.ACK)) {
handleAck(request);
} else if (request.getMethod().equals(Request.BYE)) {
handleBye(requestEvent);
} else if (request.getMethod().equals(Request.CANCEL)) {
handleBye(requestEvent);
} else {
// no other requests should come in other than ACK or BYE or CANCEL
Logger.error("SipIncomingCallAgent: ignoring request "
+ request.getMethod());
//terminateCall(); // just ignore for now : BAO
}
}
/**
* handles an INVITE request
* @param request the request
* @param transId the transaction Id
* @throws TransactionDoesNotExistException when the transaction
* record does not exist.
*
* Here's what we need to do:
* Create a temporary conference for this call. Add this member
* to the conference. Send an OK reply with the socket address
* of the conference receiver.
*/
private void handleInvite(RequestEvent requestEvent) {
setState(CallState.INVITED);
Request request = requestEvent.getRequest();
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
ToHeader toHeader = (ToHeader) request.getHeader(ToHeader.NAME);
String from = fromHeader.getAddress().toString();
String to = toHeader.getAddress().toString();
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
String sipCallId = callIdHeader.getCallId();
this.sipCallId = sipCallId;
Logger.println("SipIncomingCallAgent: Got an INVITE from " + from + " to " + to);
Logger.writeFile(request.toString());
if (Logger.logLevel >= Logger.LOG_SIP) {
Logger.println("SipIncomingCallAgent: Adding listener for call id " + sipCallId);
}
try {
sipServerCallback.addSipListener(sipCallId, this);
answerCall(requestEvent);
} catch (Exception e) {
Logger.println("SipIncomingCallAgent: " + request);
e.printStackTrace();
terminateCall();
}
}
private void handleAck(Request request) {
Logger.writeFile("SipIncomingCallAgent: Got ack...");
Logger.writeFile(request.toString());
if (getState() != CallState.ESTABLISHED) {
try {
processSdp(request);
} catch (ParseException e) {
Logger.error("SipIncomingCallAgent: " + e.getMessage());
terminateCall();
}
ToHeader toHeader = (ToHeader) request.getHeader(ToHeader.NAME);
String s = "ToAddress='" + toHeader.getAddress().toString() + "'";
s += " IncomingCall='true'";
setState(CallState.ESTABLISHED, s);
}
}
private void handleBye(RequestEvent requestEvent) {
Request request = requestEvent.getRequest();
Logger.writeFile("SipIncomingCallAgent got BYE or CANCEL");
Logger.writeFile(request.toString());
try {
CallIdHeader callIdHeader = (CallIdHeader)
request.getHeader("Call-Id");
String sipCallId = callIdHeader.getCallId();
if (sipCallId.equals(this.sipCallId)) {
receivedBye = true;
removeSipCallId(sipCallId);
try {
Logger.println("Call " + cp + " has hung up.");
sipUtil.sendOK(request,
requestEvent.getServerTransaction());
} catch (Exception e) {
/*
* We sometimes get a null ServerTransaction
*/
}
cancelRequest("hung up");
sipServerCallback.removeSipListener(sipCallId);
} else {
/*
* this should not happen since the message has been
* delegated to this sip agent.
*/
throw new TransactionDoesNotExistException(
cp + "BYE request received did not "
+ "match our callId: " + request);
}
} catch (TransactionDoesNotExistException e) {
Logger.error("Call " + cp
+ " Transaction not found " + e.getMessage());
} catch (SipException e) {
Logger.exception("Call " + cp + " SIP Stack error", e);
cancelRequest("handleBye: SIP Stack error " + e.getMessage());
} catch (Exception e) {
Logger.exception("Call " + cp + " Unknown error", e);
cancelRequest("handleBye: SIP Stack error " + e.getMessage());
}
}
private SdpInfo processSdp(Request request) throws ParseException {
byte[] rawSdp = request.getRawContent();
if (rawSdp == null) {
return null;
}
String sdpBody = new String(rawSdp);
SdpInfo sdpInfo = sipUtil.getSdpInfo(sdpBody);
String remoteHost = sdpInfo.getRemoteHost();
int remotePort = sdpInfo.getRemotePort();
Logger.println(
"SipIncomingCallAgent: remote socket " + remoteHost + " "
+ remotePort);
try {
InetSocketAddress isa =
new InetSocketAddress(remoteHost, remotePort);
setEndpointAddress(isa, sdpInfo.getMediaInfo().getPayload(),
sdpInfo.getTransmitMediaInfo().getPayload(),
sdpInfo.getTelephoneEventPayload());
} catch (Exception e) {
Logger.println("SipIncomingCallAgent: can't create isa");
throw new ParseException("SipIncomingCallAgent: can't create isa", 0);
}
return sdpInfo;
}
/**
* Processes SIP responses.
* @param responseEvent the event containing the SIP response
*/
public synchronized void processResponse(
ResponseEvent responseEvent) {
try {
Response response = (Response)responseEvent.getResponse();
int statusCode = response.getStatusCode();
FromHeader fromHeader = (FromHeader)
response.getHeader(FromHeader.NAME);
String displayName = fromHeader.getAddress().getDisplayName();
/*
* We don't expect any responses from the other side.
*/
Logger.println("SipIncomingCallAgent: Response ignored: statusCode "
+ statusCode + " fromHeader " + displayName
+ " " + response);
} catch (Exception e) {
Logger.exception("SipIncomingCallAgent: processResponse: ", e);
}
}
/**
* Processes a retransmit or expiration Timeout of an underlying
* {@link Transaction} handled by this SipListener. This Event notifies the
* application that a retransmission or transaction Timer expired in the
* SipProvider's transaction state machine. The TimeoutEvent encapsulates
* the specific timeout type and the transaction identifier either client
* or server upon which the timeout occured. The type of Timeout can by
* determined by:
* <code>timeoutType = timeoutEvent.getTimeout().getValue();</code>
*
* @param timeoutEvent - the timeoutEvent received indicating either the
* message retransmit or transaction timed out.
*/
public void processTimeout(TimeoutEvent timeoutEvent) {
}
public void answerCall(RequestEvent requestEvent) {
setState(CallState.INVITED);
Request request = requestEvent.getRequest();
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
ToHeader toHeader = (ToHeader) request.getHeader(ToHeader.NAME);
String from = fromHeader.getAddress().toString();
String to = toHeader.getAddress().toString();
Logger.println("SipIncomingCallAgent: Accept call " + from + " to " + to);
Logger.writeFile(request.toString());
try {
SdpInfo sdpInfo = processSdp(request);
st = requestEvent.getServerTransaction();
if (st == null) {
st = SipServer.getSipProvider().getNewServerTransaction(request);
}
InetSocketAddress isa = callHandler.getReceiveAddress();
if (isa == null) {
Logger.println("SipIncomingCallAgent: can't get receiver socket!");
terminateCall();
return;
}
setState(CallState.ANSWERED);
if (Logger.logLevel >= Logger.LOG_SIP) {
Logger.println("SipIncomingCallAgent: sending ok");
}
sipUtil.sendOkWithSdp(request, st, isa, sdpInfo);
} catch (Exception e) {
Logger.println("SipIncomingCallAgent: " + request);
e.printStackTrace();
terminateCall();
}
}
public void terminateCall() {
if (receivedBye) {
return;
}
if (sipCallId != null) {
sipServerCallback.removeSipListener(sipCallId);
removeSipCallId(sipCallId);
}
if (getState() == CallState.INVITED) {
try {
Logger.writeFile("SipIncomingCallAgent: sendCancel: " + cp);
sipUtil.sendCancel(st);
} catch (Exception e) {
}
} else {
try {
Logger.writeFile("sendBye: " + cp);
sipUtil.sendBye(st);
} catch (Exception ex) {
}
}
}
/*
* Check for duplicate sipCallid. If it's a new call,
* add it to the list
*/
public static boolean addSipCallId(String sipCallId) {
synchronized(sipCallIds) {
for (int i = 0; i < sipCallIds.size(); i++) {
String id = (String) sipCallIds.elementAt(i);
if (id.equals(sipCallId)) {
return false;
}
}
sipCallIds.add(sipCallId); // add to list of all coming calls
}
return true;
}
private void removeSipCallId(String sipCallId) {
synchronized(sipCallIds) {
for (int i = 0; i < sipCallIds.size(); i++) {
String id = (String)sipCallIds.elementAt(i);
if (sipCallId.equals(id)) {
sipCallIds.remove(i);
}
}
}
}
public void processDialogTerminated(DialogTerminatedEvent dte) {
if (Logger.logLevel >= Logger.LOG_SIP) {
Logger.println("processDialogTerminated called");
}
}
public void processTransactionTerminated(TransactionTerminatedEvent tte) {
if (Logger.logLevel >= Logger.LOG_SIP) {
Logger.println("processTransactionTerminated called");
}
}
public void processIOException(IOExceptionEvent ioee) {
if (Logger.logLevel >= Logger.LOG_SIP) {
Logger.println("processTransactionTerminated called");
}
}
}