/* * 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"); } } }