/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip;
import com.ericsson.ssa.sip.persistence.IncompleteDialogException;
import com.ericsson.ssa.sip.persistence.ReplicationUnitOfWork;
import com.ericsson.ssa.sip.timer.GeneralTimer;
import com.ericsson.ssa.sip.timer.GeneralTimerBase;
import com.ericsson.ssa.sip.timer.GeneralTimerListener;
import com.ericsson.ssa.sip.timer.TimerServiceImpl;
import com.ericsson.ssa.sip.transaction.TransactionManager;
import org.jvnet.glassfish.comms.util.LogUtil;
import org.jvnet.glassfish.comms.deployment.backend.SipApplicationListeners;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.Servlet;
import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipErrorEvent;
import javax.servlet.sip.SipErrorListener;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.UAMode;
/**
* This finite state machine listens to messages related to INVITE sessions. It
* will act on: ACK, BYE, CANCEL, INVITE, PRACK (RFC 3262), UPDATE (RFC 3311)
* and timeouts generated by its own state machine (e.g. Expires Header value)
* and related transaction state machines. This implemementation will atomically
* handle all the above methods according to RFC 3261. Either the method will be
* handled completely or not at all before another method is taken care of. Note
* however that other methods sharing this dialog could occur simultaniously.
* According to RFC 3261, 14.1 UAC Behavior the following should be considered:
* Note that a UAC MUST NOT initiate a new INVITE transaction within a dialog
* while another INVITE transaction is in progress in either direction. 1. If
* there is an ongoing INVITE client transaction, the TU MUST wait until the
* transaction reaches the completed or terminated state before initiating the
* new INVITE. 2. If there is an ongoing INVITE server transaction, the TU MUST
* wait until the transaction reaches the confirmed or terminated state before
* initiating the new INVITE. However, a UA MAY initiate a regular transaction
* while an INVITE transaction is in progress. A UA MAY also initiate an INVITE
* transaction while a regular transaction is in progress. First test release
* will support: ACK, BYE, CANCEL, INVITE, re-INVITE, UPDATE. TODO First test
* release will not support: Expires Header, INVITE (Join Header RFC 3911),
* PRACK, check of header fields like Allow (SHOULD requirement), Supported
* (SHOULD requirement), Accept (MAY requirement, etc., Validating CSeq number
*
* @author ehsroha
*/
public class INVITESession extends FSM implements GeneralTimerListener,
Serializable {
private static final long serialVersionUID = 1L;
private static final Logger m_Log = LogUtil.SIP_LOGGER.getLogger();
private static boolean disableByeOnNoAckReceived =
Boolean.getBoolean("org.glassfish.sip.disableByeOnNoAckReceived");
// enum states
private static List<String> m_StateStrings = new ArrayList<String>(19);
private static final int RUNNING = 0;
private static final int INITIAL_UAC = 1;
private static final int TRYING_UAC = 2;
private static final int EARLY_UAC = 3;
private static final int CONFIRMED_UAC = 4;
private static final int CLOSING_UAC = 5;
private static final int TERMINATED_UAC = 6;
private static final int INITIAL_UAS = 7;
private static final int TRYING_UAS = 8;
private static final int EARLY_UAS = 9;
private static final int CONFIRMED_UAS = 10;
private static final int TIMEOUT_UAS = 11;
private static final int CLOSING_UAS = 12;
private static final int TERMINATED_UAS = 13;
private static final int RE_INVITE_INITIAL_UAC = 14;
private static final int RE_INVITE_TRYING_UAC = 15;
private static final int RE_INVITE_EARLY_UAC = 16;
private static final int RE_INVITE_UAS = 17;
private static final int PRACKED_UAS = 18;
static {
m_StateStrings.add(RUNNING, "RUNNING");
m_StateStrings.add(INITIAL_UAC, "INITIAL_UAC");
m_StateStrings.add(TRYING_UAC, "TRYING_UAC");
m_StateStrings.add(EARLY_UAC, "EARLY_UAC");
m_StateStrings.add(CONFIRMED_UAC, "CONFIRMED_UAC");
m_StateStrings.add(CLOSING_UAC, "CLOSING_UAC");
m_StateStrings.add(TERMINATED_UAC, "TERMINATED_UAC");
m_StateStrings.add(INITIAL_UAS, "INITIAL_UAS");
m_StateStrings.add(TRYING_UAS, "TRYING_UAS");
m_StateStrings.add(EARLY_UAS, "EARLY_UAS");
m_StateStrings.add(CONFIRMED_UAS, "CONFIRMED_UAS");
m_StateStrings.add(TIMEOUT_UAS, "TIMEOUT_UAS");
m_StateStrings.add(CLOSING_UAS, "CLOSING_UAS");
m_StateStrings.add(TERMINATED_UAS, "TERMINATED_UAS");
m_StateStrings.add(RE_INVITE_INITIAL_UAC, "RE_INVITE_INITIAL_UAC");
m_StateStrings.add(RE_INVITE_TRYING_UAC, "RE_INVITE_TRYING_UAC");
m_StateStrings.add(RE_INVITE_EARLY_UAC, "RE_INVITE_EARLY_UAC");
m_StateStrings.add(RE_INVITE_UAS, "RE_INVITE_UAS");
m_StateStrings.add(PRACKED_UAS, "PRACKED_UAS");
}
private int m_State = INITIAL_UAS;
private SipServletRequestImpl m_INVITE = null;
private SipServletRequestImpl m_PendingCANCEL = null;
// T1 -> T1*2 ->
private GeneralTimer m_TimerShort;
private GeneralTimer m_TimerShortProvRsp;
// 64*T1
private GeneralTimer m_TimerLong;
private GeneralTimer m_TimerLongProvRsp;
private String m_Rack = null;
private boolean m_PrackReceived = false;
private SipServletResponseImpl m_RetransmitResponse = null;
private SipServletResponseImpl m_RetransmitReliableResponse = null;
private SipServletRequestImpl m_RetransmitACK = null;
private Header m_CancelVia = null;
private INVITESession() {
}
/**
* Used by clone.
*
* @param state
* @param originalINVITE
* @param pendingCANCEL
*/
private INVITESession(int state, SipServletRequestImpl originalINVITE,
SipServletRequestImpl pendingCANCEL) {
m_State = state;
m_INVITE = originalINVITE;
m_PendingCANCEL = pendingCANCEL;
}
/**
* Will return a new INVITESession if the message matches this FSM.
*
* @param m
* the incoming message is the key to decide which FSM to create
* @return a INVITESession or null if no match occured
*/
public static FSM createFSM(SipServletMessage m) {
// TODO isInitial() ?????
if (m.getMethod().equals("INVITE")) {
return new INVITESession();
}
return null;
}
public Object clone() {
return new INVITESession(EARLY_UAC, m_INVITE, m_PendingCANCEL);
}
/**
* This FSM is responsible if the message is of method type: ACK, BYE,
* CANCEL, INVITE, PRACK or UPDATE.
*
* @param m
* the incoming message is the key to decide whether this FSM will
* match or not.
*/
private static boolean isStaticResponsible(SipServletMessage m) {
String method = m.getMethod();
return (method.equals("ACK") || method.equals("BYE") ||
method.equals("CANCEL") || method.equals("INVITE") ||
method.equals("PRACK") || method.equals("UPDATE"));
}
public boolean isResponsible(SipServletMessage m) {
return isStaticResponsible(m);
}
/**
* Returns the original INVITE that created this INVITESession.
* Implemented for B2buaHelper.createCancel handling.
*/
public SipServletRequestImpl getOrigRequestImpl() {
return this.m_INVITE;
}
private void storeRAck(SipServletResponseImpl resp, UA uac)
throws IllegalStateException {
String rseq = resp.getHeader(Header.RSEQ);
if (rseq != null) {
String CSeq = resp.getHeader(Header.CSEQ);
if (CSeq != null) {
m_Rack = rseq + " " + CSeq;
} else {
throw new IllegalStateException("CSeq header is missing.");
}
}
}
private void retreiveRAck(SipServletRequestImpl req, UA uac)
throws IllegalStateException {
if (m_Rack != null) {
Header rack = new SingleLineHeader(Header.RACK, true);
rack.setValue(m_Rack, true);
req.setHeader(rack);
} else {
throw new IllegalStateException("RAck is undefined.");
}
}
/**
* A synchronized call to atomically update the state machine with the
* request from the local UAC. If an error occurs (invalid state change, etc)
* IllegalStateException is returned to the caller.
*
* @param req
* the incoming request from the local UAC
* @return whether to dispatch or not after call
*/
private synchronized boolean doRequestUAC(SipServletRequestImpl req, UA uac)
throws IllegalStateException {
boolean isDispatchEnabled = true;
String method = req.getMethod();
if (req.isInitial() && method.equals("INVITE")) {
// must ensure that not more than
// one INVITE is ongoing at one time
if (!uac.addDialogSession(method, null)) {
throw new IllegalStateException(
"Not allowed with two INVITE at one time.");
} else {
m_State = INITIAL_UAC;
m_INVITE = req;
}
} else {
switch (m_State) {
case INITIAL_UAC: {
if ((m_PendingCANCEL == null) && method.equals("CANCEL")) {
m_PendingCANCEL = req;
// must ensure that the pending CANCEL is not sent,
// must first wait for provisional response
isDispatchEnabled = false;
} else {
throw new IllegalStateException();
}
break;
}
case TRYING_UAC: {
if ((m_PendingCANCEL == null) && method.equals("CANCEL")) {
// lets send the CANCEL request
// add top via from response...
if (m_CancelVia != null) {
req.setHeader(m_CancelVia);
}
// don't want to dispatch twice...
m_INVITE = null;
} else {
throw new IllegalStateException();
}
break;
}
case EARLY_UAC: {
if ((m_PendingCANCEL == null) && method.equals("CANCEL")) {
// lets send the CANCEL request
// add top via from response...
if (m_CancelVia != null) {
req.setHeader(m_CancelVia);
}
// don't want to dispatch twice...
m_INVITE = null;
} else if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (method.equals("PRACK")) {
retreiveRAck(req, uac);
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
case CONFIRMED_UAC: {
if (method.equals("ACK")) {
m_RetransmitACK = req;
m_State = RUNNING;
} else if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (method.equals("PRACK")) {
retreiveRAck(req, uac);
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
case TERMINATED_UAC: {
if (method.equals("CANCEL")) {
throw new IllegalStateException();
}
break;
}
case RUNNING: {
if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (method.equals("INVITE")) {
m_State = RE_INVITE_INITIAL_UAC;
m_INVITE = req;
} else if (method.equals("PRACK")) {
retreiveRAck(req, uac);
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
case RE_INVITE_INITIAL_UAC: {
if (method.equals("CANCEL")) {
m_PendingCANCEL = req;
isDispatchEnabled = false;
} else if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
case RE_INVITE_TRYING_UAC:
case RE_INVITE_EARLY_UAC: {
if (method.equals("CANCEL")) {
if (m_INVITE != null) {
// lets send the CANCEL request
// add top via from response...
if (m_CancelVia != null) {
req.setHeader(m_CancelVia);
}
// don't want to dispatch twice...
m_INVITE = null;
} else {
m_PendingCANCEL = req;
isDispatchEnabled = false;
}
} else if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
case TIMEOUT_UAS: {
if (method.equals("BYE")) {
m_State = CLOSING_UAC;
} else if (!method.equals("UPDATE")) {
throw new IllegalStateException();
}
break;
}
// this is not valid states, lets respond with an appropriate
// error
case TRYING_UAS:
case CONFIRMED_UAS:
case RE_INVITE_UAS:
case EARLY_UAS:
if (method.equals("UPDATE")) {
// Sending UPDATE is allowed
break;
}
case CLOSING_UAS:
case INITIAL_UAS:
case TERMINATED_UAS:
/* case CLOSING_UAC:
* allowing to create BYE when the response is 401 or 407 */
throw new IllegalStateException();
}
}
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST,
req.toDebugString() + ", state = " + stateToString() +
", reference = " + this);
}
return isDispatchEnabled;
}
public void send(SipServletRequestImpl req, UA uac)
throws IllegalStateException {
if (doRequestUAC(req, uac)) {
// the request need to be cloned before sending
SipServletRequestImpl clone = (SipServletRequestImpl) req.clone();
clone.setTransactionRequest(req);
// lets run in new thread...
super.send(clone, uac);
}
}
private void setDerivedOrOriginalSession(SipServletResponseImpl resp, UA ua) {
// if the ua has a session with same to tag as the dialog, use it...
if ((ua.getSipSession() != null) && !ua.getSipSession().hasNoToTag() &&
(resp.getDialog().getToTag() != null) &&
resp.getDialog().getToTag().equals(ua.getSipSession().getToTag())) {
resp.setSession(ua.getSipSession());
} else {
// ...otherwise fetch or create one
DialogFragment df = resp.getDialog();
// lets update to-tag of dialog...
SipSessionBase s = resp.getSessionImpl()
.getOriginalOrDerivedSessionAndRegisterDialog(resp,
df);
if (s.isDerived()) {
// lets set the session cuz it is a derived session
resp.setSession(s);
}
ua.setSipSession(s);
}
}
/**
* A synchronized call to atomically update the state machine with the
* response from the remote UAS. Some request will be generated immediately
* and should be sent back to the remote UAS (e.g. periodic ACK to incoming
* 2xx responses). If successfull the response might have been updated and
* should be forwarded. From SSA spec. 7.1.7 Sending CANCEL "...Note that
* responses to CANCEL requests are not passed to the application......SIP
* Servlet applications may send CANCEL before a provisional response. It is
* the containers responsibility to delay sending of the CANCEL until a
* provisional response has been received."
*
* @param resp
* the incoming response from the remote UAS
* @return whether the SipServlet should be invoked or not
*/
private synchronized boolean doResponseUAC(SipServletResponseImpl resp,
UA uac) {
boolean isInvokeServlet = true;
int status = resp.getStatus();
String method = resp.getMethod();
switch (m_State) {
case INITIAL_UAC: {
if (method.equals("INVITE")) {
if (status == 100) {
// as soon as at least a 100 INVITE
// has arrived its possible to cancel
m_State = TRYING_UAC;
// since the dialog is not established
// lets us the temp one
resp.setSession(uac.getSipSession());
isInvokeServlet = false;
// lets dispatch any pending cancel
if ((m_INVITE != null) && (m_PendingCANCEL != null)) {
// add top via from response...
m_PendingCANCEL.setHeader(resp.getCancelVia());
// lets dispatch it...
m_PendingCANCEL.popDispatcher().dispatch(m_PendingCANCEL);
// don't want to dispatch twice...
m_INVITE = null;
} else {
// save the via if CANCEL arrives later
m_CancelVia = resp.getCancelVia();
}
}
// 101-199 INVITE response with to-tag
else if ((status >= 101) && (status < 200) && resp.hasToTag()) {
setDerivedOrOriginalSession(resp, uac);
if (resp.getRequest().isInitial()) {
try {
uac.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Parse problem of Contact or Record-Route for response: " +
resp);
}
isInvokeServlet = false;
}
}
storeRAck(resp, uac);
m_State = EARLY_UAC;
// lets dispatch any pending cancel
if ((m_INVITE != null) && (m_PendingCANCEL != null)) {
// add top via from response...
m_PendingCANCEL.setHeader(resp.getCancelVia());
// lets dispatch it...
m_PendingCANCEL.popDispatcher().dispatch(m_PendingCANCEL);
// don't want to dispatch twice...
m_INVITE = null;
} else {
// save the via if CANCEL arrives later
m_CancelVia = resp.getCancelVia();
}
}
// 200-299 INVITE response with to-tag
else if ((status >= 200) && (status < 300) && resp.hasToTag()) {
setDerivedOrOriginalSession(resp, uac);
if (resp.getRequest().isInitial()) {
try {
uac.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Parse problem of Contact or Record-Route for response: " +
resp);
}
isInvokeServlet = false;
}
}
m_State = CONFIRMED_UAC;
// a confirmed dialog is not possible to
// cancel, any pending CANCEL is removed
m_PendingCANCEL = null;
m_CancelVia = null;
} else if ((status >= 300) && (status < 700)) {
// lets inform that this session is ending
uac.removeDialogSession(method, null);
m_PendingCANCEL = null;
m_CancelVia = null;
m_INVITE = null;
m_State = INITIAL_UAC;
// TODO, is it wrong to set a session with valid dialog for
// 300-700?
}
}
break;
}
case TRYING_UAC:
case EARLY_UAC: {
if (method.equals("INVITE")) {
// 101-199 INVITE response with to-tag
if ((status >= 101) && (status < 200) && resp.hasToTag()) {
setDerivedOrOriginalSession(resp, uac);
if (resp.getRequest().isInitial()) {
try {
uac.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Parse problem of Contact or Record-Route for response: " +
resp);
}
isInvokeServlet = false;
}
}
storeRAck(resp, uac);
m_State = EARLY_UAC;
// Might have a pending CANCEL to send
if ((m_INVITE != null) && (m_PendingCANCEL != null)) {
// add top via from response...
m_PendingCANCEL.setHeader(resp.getCancelVia());
// lets dispatch it...
m_PendingCANCEL.popDispatcher().dispatch(m_PendingCANCEL);
// don't want to dispatch twice...
m_INVITE = null;
} else if (m_CancelVia != null) {
// save the via if CANCEL arrives later
m_CancelVia = resp.getCancelVia();
}
}
// 200-299 INVITE response
else if ((status >= 200) && (status < 300) && resp.hasToTag()) {
setDerivedOrOriginalSession(resp, uac);
if (resp.getRequest().isInitial()) {
try {
uac.saveRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Parse problem of Contact or Record-Route for response: " +
resp);
}
isInvokeServlet = false;
}
}
// a confirmed dialog is not possible to
// cancel, any pending CANCEL is removed
m_PendingCANCEL = null;
m_CancelVia = null;
m_INVITE = null;
m_State = CONFIRMED_UAC;
}
// 300-699 INVITE response
else if ((status >= 300) && (status < 700) && resp.hasToTag()) {
// lets inform that this session is ending
uac.removeDialogSession(method, null);
m_PendingCANCEL = null;
m_CancelVia = null;
m_INVITE = null;
m_State = INITIAL_UAC;
}
}
// 200 CANCEL
else if (method.equals("CANCEL") && (status >= 200) &&
(status < 300)) {
// according to SSA 7.1.7 should not invoke UAC application...
// inform caller not to deliver 2xx to application
isInvokeServlet = false;
}
break;
}
case CLOSING_UAC: {
// 200-299 BYE response
if (method.equals("BYE") && (status >= 200) && (status < 300)) {
// lets inform that this session is ending
uac.removeDialogSession(method, null);
m_State = TERMINATED_UAC;
}
break;
}
case CONFIRMED_UAC:
case RUNNING: {
if (method.equals("INVITE")) {
// 200-299 INVITE response
if ((status >= 200) && (status < 300)) {
// must retransmit an ACK:
// From SSA spec. 7.16 Sending ACK "It is the containers
// responsibility to retransmit application generated ACKs for
// 2xx's when a 2xx retransmission is received and the
// container must not deliver the 2xx retransmission to the
// UAC application."
if (m_RetransmitACK != null &&
m_RetransmitACK.getCSeqNumber() == resp.getCSeqNumber()) {
SipServletRequestImpl ack = (SipServletRequestImpl) m_RetransmitACK.clone();
ack.popDispatcher().dispatch(ack);
}
// inform caller not to deliver 2xx to application
isInvokeServlet = false;
}
// 491 - Request Pending
else if (status == SipServletResponse.SC_REQUEST_PENDING) {
// TODO start timer according to RFC 3261, 14.1
// if a UAC receives a 491 response to a re-INVITE
}
}
break;
}
case RE_INVITE_INITIAL_UAC:
case RE_INVITE_TRYING_UAC:
case RE_INVITE_EARLY_UAC:{
if (method.equals("INVITE")) {
if (status == 100) {
isInvokeServlet = false;
// lets dispatch any pending cancel
if ((m_INVITE != null) && (m_PendingCANCEL != null)) {
// add top via from response...
m_PendingCANCEL.setHeader(resp.getCancelVia());
// lets dispatch it...
m_PendingCANCEL.popDispatcher().dispatch(m_PendingCANCEL);
// don't want to dispatch twice...
m_INVITE = null;
} else {
// save the via if CANCEL arrives later
m_CancelVia = resp.getCancelVia();
}
m_State = RE_INVITE_TRYING_UAC;
}
// 101-199 INVITE response with to-tag
if ((status >= 101) && (status < 200) && resp.hasToTag()) {
storeRAck(resp, uac);
// lets dispatch any pending cancel
if ((m_INVITE != null) && (m_PendingCANCEL != null)) {
// add top via from response...
m_PendingCANCEL.setHeader(resp.getCancelVia());
// lets dispatch it...
m_PendingCANCEL.popDispatcher().dispatch(m_PendingCANCEL);
// don't want to dispatch twice...
m_INVITE = null;
} else {
// save the via if CANCEL arrives later
m_CancelVia = resp.getCancelVia();
}
m_State = RE_INVITE_EARLY_UAC;
}
// 200-299 re-INVITE response
else if ((status >= 200) && (status < 300)) {
try {
uac.saveTargetRefreshContact(resp);
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Parse problem of Contact or Record-Route for response: " + resp);
}
isInvokeServlet = false;
}
m_State = CONFIRMED_UAC;
}
// 300-699 re-INVITE response, do not change
// anything after an unsuccessful re-INVITE
else if ((status >= 300) && (status < 700)) {
m_State = RUNNING;
}
}
break;
}
case TERMINATED_UAC: {
if (!method.equals("BYE")) {
// a re-sending of e.g. a 200OK to an INVITE should
// not be forwarded to the servlet again...
isInvokeServlet = false;
}
}
}
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST,
resp.toDebugString() + ", state = " + stateToString() +
", reference = " + this);
}
if (status == 100)
isInvokeServlet = false;
return isInvokeServlet;
}
public void dispatch(SipServletResponseImpl resp, UA uac) {
// Check if this is a reliable provisional response with same RSeq and
// CSeq as already
// stored. If so no further processing will be done
int status = resp.getStatus();
if ((status > 100) && (status < 200) &&
resp.getMethod().equals("INVITE")) {
if (isResentProvisionalResp(resp)) {
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO,
"Response dropped. Same RSeq/CSeq as handled before ");
}
return;
}
}
if (resp.getMethod().equals("CANCEL")) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Response for CANCEL received. Drop. ");
}
return;
}
if (doResponseUAC(resp, uac)) {
try {
Servlet s = uac.getServlet(resp.getSessionImpl().getHandler());
if (s != null) {
resp.getSessionImpl()
.updateSipSessionState(resp, uac.getType());
resp.setIncoming();
// first message will create the pending
// message lists for UAC and UAS..
if (resp.getRequestImpl().isB2buaHelper()) {
resp.getSessionImpl().createPendingMessageHelper();
}
// ...if pending lists exist, add pending uncommitted message
if (!resp.isCommitted() && resp.getSessionImpl().isB2buaHelper()) {
resp.getSessionImpl().addPendingMessage(resp, UAMode.UAC);
}
s.service(null, resp);
} else {
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO,
"Could not find servlet name: " +
resp.getSessionImpl().getHandler() +
" in application: " +
resp.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
// problem in servlet, lets drop response
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO, "Problem in servlet.", e);
}
}
}
}
/**
* Check if this is a resent provisional response with same RSeq and CSeq
* that has already been handled by this INVITE session
*
* @param resp
* @return true if the response has same RSeq and CSeq as already stored
*/
private boolean isResentProvisionalResp(SipServletResponseImpl resp) {
boolean result = false;
String newRack = "";
String rseq = resp.getHeader(Header.RSEQ);
if (rseq != null) {
String CSeq = resp.getHeader(Header.CSEQ);
if (CSeq != null) {
newRack = rseq + " " + CSeq;
if (newRack.equals(m_Rack)) {
result = true;
}
}
}
return result;
}
/**
* A synchronized call to atomically update the state machine with the
* request from the remote UAC. If an error occurs (invalid state change,
* etc) the appropriate response is returned. If successfull the request
* might have been updated and should be forwarded.
*
* @param req
* the incoming request from the remote UAC
* @return whether the SipServlet should be invoked or not
*/
// Must support the following scenario:
//
// UAC A........................UAS B Core.....................UAS B
// .............................(SipServlet) (we are here)....
//
// ----INVITE-------------------INITIAL------------------------>
// <---100(INVITE)--------------INITIAL
// ----CANCEL ----------------> TERMINATED
// <----487 (INVITE) -----------TERMINATED
// <----200 (CANCEL) -----------TERMINATED
// .............................TERMINATED --CANCEL------------>
//
private synchronized boolean doRequestUAS(SipServletRequestImpl req, UA uas) {
SipServletResponseImpl resp = null;
boolean isInvokeServlet = true;
String method = req.getMethod();
if (req.isInitial() && method.equals("INVITE")) // TODO will never happen because first INVITE to UAS will go directly to
// servlet
{
// must ensure that not more than
// one INVITE is ongoing at one time
if (!uas.addDialogSession(method, null)) {
// 500
resp = req.createTerminatingResponse(500);
} else {
m_State = INITIAL_UAS;
m_INVITE = req;
}
} else if (method.equals("PRACK") && m_PrackReceived) {
// already received PRACK for a transmitted response.
resp = req.createTerminatingResponse(481);
} else {
switch (m_State) {
case EARLY_UAS: {
if (method.equals("BYE")) {
m_State = CLOSING_UAS;
} else if (method.equals("CANCEL")) {
m_State = TERMINATED_UAS;
// lets inform that this session is ending
uas.removeDialogSession(method, null);
// 200 OK
SipServletResponseImpl resp200 = req.createTerminatingResponse(200);
// Request Terminated
// Fix for issue 1182
SipServletRequestImpl reqINV = m_INVITE.getTransactionRequest() != null ? m_INVITE.getTransactionRequest() : m_INVITE;
SipServletResponseImpl resp487 = reqINV.createTerminatingResponse(487);
// lets respond 200 CANCEL and 487 INVITE
if (!resp487.getSessionImpl().hasNoToTag()) {
Header to = resp487.getRawHeader(Header.TO);
Header toC = resp200.getRawHeader(Header.TO);
try {
Address adr = to.getAddressValue();
((AddressImpl) adr).setReadOnly(false);
adr.setParameter(AddressImpl.TAG_PARAM,
resp487.getSessionImpl().getToTag());
((AddressImpl) adr).setReadOnly(true);
Address adrC = toC.getAddressValue();
((AddressImpl) adrC).setReadOnly(false);
adrC.setParameter(AddressImpl.TAG_PARAM,
resp487.getSessionImpl().getToTag());
((AddressImpl) adrC).setReadOnly(true);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of To Header");
}
}
/**
* Since the resp487.popDispatcher().dispatch(resp487) does
* not go though the FSM, instead goes directly to DialogManager,
* the updateSipSessionState was never called.
*
* Hence, we need to call updateSipSessionState from here
* so that the session is marked for readyToInvalidate.
*/
resp487.getSessionImpl().updateSipSessionState(resp487, uas.getType());
resp487.popDispatcher().dispatch(resp487);
m_INVITE.setSentResponse(resp487.getStatus());
resp200.popDispatcher().dispatch(resp200);
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("PRACK") && !method.equals("ACK")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
break;
}
case TRYING_UAS: {
if (method.equals("CANCEL")) {
m_State = TERMINATED_UAS;
// lets inform that this session is ending
uas.removeDialogSession(method, null);
// 200 OK
SipServletResponseImpl resp200 = req.createTerminatingResponse(200);
// Request Terminated
SipServletResponseImpl resp487 = m_INVITE.createTerminatingResponse(487);
// lets respond 200 CANCEL and 487 INVITE
if (!resp487.getSessionImpl().hasNoToTag()) {
Header to = resp487.getRawHeader(Header.TO);
Header toC = resp200.getRawHeader(Header.TO);
try {
Address adr = to.getAddressValue();
((AddressImpl) adr).setReadOnly(false);
adr.setParameter(AddressImpl.TAG_PARAM,
resp487.getSessionImpl().getToTag());
((AddressImpl) adr).setReadOnly(true);
Address adrC = toC.getAddressValue();
((AddressImpl) adrC).setReadOnly(false);
adrC.setParameter(AddressImpl.TAG_PARAM,
resp487.getSessionImpl().getToTag());
((AddressImpl) adrC).setReadOnly(true);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of To Header");
}
}
resp487.popDispatcher().dispatch(resp487);
m_INVITE.setSentResponse(resp487.getStatus());
resp200.popDispatcher().dispatch(resp200);
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("ACK")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
break;
}
case CONFIRMED_UAS: {
if (method.equals("ACK")) {
// Lets stop the timer which triggers re-sending of 200
// (RFC 3261 13.3.1)
if (m_TimerShort != null) {
m_TimerShort.cancel();
m_TimerShort = null;
}
if (m_TimerLong != null) {
m_TimerLong.cancel();
m_TimerLong = null;
}
m_State = RUNNING;
//dereference to release memory
m_RetransmitResponse = null;
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
} else if (method.equals("BYE")) {
// HH20098
// Lets stop the timer which triggers re-sending of 200
// (RFC 3261 13.3.1)
if (m_TimerShort != null) {
m_TimerShort.cancel();
m_TimerShort = null;
}
if (m_TimerLong != null) {
m_TimerLong.cancel();
m_TimerLong = null;
}
m_State = CLOSING_UAS;
} else if (method.equals("CANCEL")) {
resp = req.createTerminatingResponse(481);
} else if (method.equals("INVITE")) {
resp = req.createTerminatingResponse(491);
} else if (!method.equals("PRACK")) {
resp = req.createTerminatingResponse(403);
}
break;
}
case TERMINATED_UAS: {
if (method.equals("CANCEL")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("ACK")) {
// Request Terminated
resp = req.createTerminatingResponse(487);
}
break;
}
case RUNNING: {
if (method.equals("BYE")) {
m_State = CLOSING_UAS;
} else if (method.equals("INVITE")) {
m_State = RE_INVITE_UAS;
m_INVITE = req;
} else if (method.equals("PRACK")) {
// A 1XX is pending and a 200OK(INVITE) have been sent before
// receiving
// the PRACK request.
String rackPrack = req.getHeader(Header.RACK);
// The Rack Header in the PRACK must match the RSeq + CSeq of the
// prov response
if (!((rackPrack != null) && (m_Rack != null) &&
m_Rack.equalsIgnoreCase(rackPrack))) {
resp = req.createTerminatingResponse(481);
}
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else {
isInvokeServlet = false;
if (!method.equals("ACK")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
}
break;
}
case RE_INVITE_INITIAL_UAC:
case RE_INVITE_TRYING_UAC:
case RE_INVITE_EARLY_UAC: {
if (method.equals("BYE")) {
m_State = CLOSING_UAS;
} else if (method.equals("INVITE")) {
// OK, a remote INVITE arrived while processing
// a locally generated INVITE already
m_State = RUNNING;
// need to immediately respond with a pending response
resp = req.createTerminatingResponse(491);
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("PRACK") && !method.equals("UPDATE") &&
!method.equals("ACK")) {
resp = req.createTerminatingResponse(403);
}
break;
}
case RE_INVITE_UAS: {
if (method.equals("BYE")) {
m_State = CLOSING_UAS;
} else if (method.equals("INVITE")) {
// OK, the second remote INVITE
// respond with an 500 error
// TODO add Retry-After Header
// random value between 0-10s
resp = req.createTerminatingResponse(500);
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
} else if (method.equals("CANCEL")) {
m_State = RUNNING;
// lets inform that this session is ending
uas.removeDialogSession(method, null);
// 200 OK
SipServletResponseImpl resp200 = req.createTerminatingResponse(200);
// Request Terminated
SipServletResponseImpl resp487 = m_INVITE.createTerminatingResponse(487);
// lets respond 200 CANCEL and 487 INVITE
if (!resp487.getSessionImpl().hasNoToTag()) {
Header to = resp487.getRawHeader(Header.TO);
try {
Address adr = to.getAddressValue();
((AddressImpl) adr).setReadOnly(false);
adr.setParameter(AddressImpl.TAG_PARAM,
resp487.getSessionImpl().getToTag());
((AddressImpl) adr).setReadOnly(true);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of To Header");
}
}
resp487.popDispatcher().dispatch(resp487);
m_INVITE.setSentResponse(487);
resp200.popDispatcher().dispatch(resp200);
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("PRACK") && !method.equals("ACK")) {
resp = req.createTerminatingResponse(403);
}
break;
}
case PRACKED_UAS: {
if (method.equals("PRACK")) {
String rackPrack = req.getHeader(Header.RACK);
// The Rack Header in the PRACK match the RSeq + CSeq of the prov
// response
if ((rackPrack != null) && (m_Rack != null) &&
m_Rack.equalsIgnoreCase(rackPrack)) {
// Lets stop the timer which triggers re-sending of 200
// (RFC 3261 13.3.1)
if (m_TimerShortProvRsp != null) {
m_TimerShortProvRsp.cancel();
m_TimerShortProvRsp = null;
}
if (m_TimerLongProvRsp != null) {
m_TimerLongProvRsp.cancel();
m_TimerLongProvRsp = null;
}
m_PrackReceived = true;
} else {
resp = req.createTerminatingResponse(481);
}
} else if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
break;
}
case CONFIRMED_UAC: {
if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
else if (method.equals("BYE")) {
m_State = CLOSING_UAS;
}
/* Will a CANCEL reach this far on CONFIRMED_UAC? ***/
else if (method.equals("CANCEL")) {
resp = req.createTerminatingResponse(200);
}
else if (!method.equals("ACK")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
break;
}
// this is not valid states, lets respond with an appropriate
// error
case INITIAL_UAC:
case TRYING_UAC:
case EARLY_UAC:
case CLOSING_UAC:
case TERMINATED_UAC:
case INITIAL_UAS:
case TIMEOUT_UAS:
case CLOSING_UAS: {
if (method.equals("UPDATE")) {
if (!req.getSessionImpl().setUpdateOngoing()) {
resp = req.createTerminatingResponse(500);
resp.setHeader("Retry-After", "5");
}
}
// should always repond with 200OK to CANCEL when dialog was found
else if (method.equals("CANCEL")) {
resp = req.createTerminatingResponse(200);
}
// Can't sent response on ACK, cf
// SipServletResponseImpl.populateResponse()
else if (!method.equals("ACK")) {
// Forbidden
resp = req.createTerminatingResponse(403);
}
break;
}
}
}
if (resp != null) {
// lets respond
resp.popDispatcher().dispatch(resp);
isInvokeServlet = false;
}
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST,
req.toDebugString() + ", state = " + stateToString() +
", reference = " + this);
}
return isInvokeServlet;
}
public void dispatch(SipServletRequestImpl req, UA uas) {
// Workaround for issue 1457
ReplicationUnitOfWork oldUOW = ReplicationUnitOfWork.getThreadLocalUnitOfWork();
boolean servletMustBeInvoked = doRequestUAS(req, uas);
// if a response was sent on the original invite request in this same thread
// then the UOW got lost in the replication manager when handling that response.
// Therefore we have to restore the UOW here if we do not want to get into
// problems later in this thread.
ReplicationUnitOfWork newUOW = ReplicationUnitOfWork.getThreadLocalUnitOfWork();
if ((oldUOW != null && newUOW == null)) {
if (m_INVITE != null && m_INVITE.getDialog() != null) {
oldUOW.lockDialog(m_INVITE.getDialog());
}
oldUOW.setThreadLocal();
}
if (servletMustBeInvoked) {
if (uas.getSipSession() != null) {
req.setSession(uas.getSipSession());
}
try {
Servlet s = uas.getServlet(req.getSessionImpl().getHandler());
if (s != null) {
req.getSessionImpl()
.updateSipSessionState(req, uas.getType());
if (!req.isCommitted() && req.getSessionImpl().isB2buaHelper()) {
req.getSessionImpl().addPendingMessage(req, UAMode.UAS);
}
s.service(req, null);
} else {
if (m_Log.isLoggable(Level.INFO)) {
m_Log.log(Level.INFO,
"Could not find servlet name: " +
req.getSessionImpl().getHandler() +
" in application: " +
req.getSessionImpl().getApplicationSessionImpl()
.getName());
}
}
} catch (Exception e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "Problem in servlet ", e);
}
// problem in servlet...
SipServletResponseImpl resp = req.createTerminatingResponse(500);
// TR HH52078
if (resp == null) {
return;
}
resp.popDispatcher().dispatch(resp);
}
}
}
private void updateToTag(SipServletResponseImpl resp, UA uas) {
if (!resp.hasToTag()) {
SipSessionDialogImpl sipSessionDialogImpl = (SipSessionDialogImpl) uas.getSipSession();
// FIXME replaced by uas: resp.getSessionImpl();
String toTag = (sipSessionDialogImpl != null)
? sipSessionDialogImpl.getToTag() : null;
if (toTag == null) {
resp.createTag(Header.TO);
setDerivedOrOriginalSession(resp, uas);
} else {
Header to = resp.getRawHeader(Header.TO);
try {
Address adr = to.getAddressValue();
((AddressImpl) adr).setReadOnly(false);
adr.setParameter(AddressImpl.TAG_PARAM, toTag);
((AddressImpl) adr).setReadOnly(true);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of To Header");
}
}
}
}
/**
* A synchronized call to atomically update the state machine with the
* response from the local UAS. If successfull the response might have been
* updated and should be forwarded.
*
* @param resp
* the incoming response from the local UAS
*/
private synchronized void doResponseUAS(SipServletResponseImpl resp, UA uas) {
int status = resp.getStatus();
String method = resp.getMethod();
// 8.2.6.2 Headers and Tags
// the UAS MUST add a tag to the To header field in
// the response (with the exception of the 100 (Trying) response, in
// which a tag MAY be present).
if (status != 100) {
updateToTag(resp, uas);
}
switch (m_State) {
case INITIAL_UAS:
case TRYING_UAS: {
if (method.equals("INVITE")) {
if (status == 100) {
m_INVITE = resp.getRequestImpl();
m_State = TRYING_UAS;
}
// 200-299 INVITE response
else if ((status >= 200) && (status < 300)) {
// make sure that getLocalParty and getRemoteParty
// change behaviour
SipSessionDialogImpl s = (SipSessionDialogImpl) resp.getSessionImpl();
s.swapLocalRemote();
saveContactRouteSetRemoteTarget(resp, uas);
// lets start timer for retransmission of 200 until ACK is
// received
startTimers(resp);
m_State = CONFIRMED_UAS;
m_INVITE = null;
}
// 101-199 INVITE response
else if ((status > 100) && (status < 200)) {
saveContactRouteSetRemoteTarget(resp, uas);
if (m_INVITE == null) {
m_INVITE = resp.getRequestImpl();
}
if (resp.isReliableProvisionalResponse()) {
// lets start timer for retransmission of 1xx until PRACK is
// received
startTimers(resp);
storeRAck(resp, uas);
m_State = PRACKED_UAS;
m_PrackReceived = false;
} else {
m_State = EARLY_UAS;
}
}
} else if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
case EARLY_UAS: {
if (method.equals("INVITE")) {
// 101-199 INVITE Reliable response (PRACK scenario)
if ((status > 100) && (status < 200) &&
resp.isReliableProvisionalResponse()) {
// lets start timer for retransmission of 1xx until PRACK is
// received
startTimers(resp);
storeRAck(resp, uas);
m_State = PRACKED_UAS;
m_PrackReceived = false;
}
// 200-299 INVITE response
else if ((status >= 200) && (status < 300)) {
// make sure that getLocalParty and getRemoteParty
// change behaviour
SipSessionDialogImpl s = (SipSessionDialogImpl) resp.getSessionImpl();
s.swapLocalRemote();
saveContactRouteSetRemoteTarget(resp, uas);
// lets start timer for retransmission of 200 until ACK is
// received
startTimers(resp);
m_State = CONFIRMED_UAS;
m_INVITE = null;
}
// 300-699 INVITE response
else if ((status >= 300) && (status < 700)) {
// lets inform that this session is ending
uas.removeDialogSession(method, null);
m_State = TERMINATED_UAS;
m_INVITE = null;
}
} else if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
case CLOSING_UAS: {
// 200-299 BYE response
if (method.equals("BYE") && (status >= 200) && (status < 300)) {
// lets inform that this session is ending
uas.removeDialogSession(method, null);
m_State = TERMINATED_UAS;
}
break;
}
case CONFIRMED_UAS: {
if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
case RUNNING: {
if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
case RE_INVITE_UAS: {
if (method.equals("INVITE")) {
// 101-199 re-INVITE Reliable response (PRACK scenario)
if ((status > 100) && (status < 200) &&
resp.isReliableProvisionalResponse()) {
// lets start timer for retransmission of 1xx until PRACK is
// received
startTimers(resp);
storeRAck(resp, uas);
m_State = PRACKED_UAS;
m_PrackReceived = false;
}
// 200-299 re-INVITE response
if ((status >= 200) && (status < 300)) {
m_RetransmitResponse = null;
// lets start timer for retransmission of 200 until ACK is
// received
saveContactRemoteTarget(resp, uas);
startTimers(resp); //HI18979
m_State = CONFIRMED_UAS;
m_INVITE = null;
}
// 300-699 re-INVITE response, don't change
// anything, keep current setup
else if ((status >= 300) && (status < 700)) {
m_State = RUNNING;
}
} else if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
case PRACKED_UAS: {
if (method.equals("PRACK") && m_PrackReceived) {
// 200 OK PRACK response
if (status == 200) {
resp.getSessionImpl().reset1xxReliable();
// Initial INVITE sequence
if ((m_INVITE != null) && (m_INVITE.isInitial())) {
m_State = EARLY_UAS;
}
// RE-INVITE case sequence
else {
m_State = RE_INVITE_UAS;
}
}
} else if (method.equals("INVITE")) {
// 101-199 INVITE reliable response
if ((status > 100) && (status < 200) &&
resp.isReliableProvisionalResponse()) {
// SSA Layer must avoid to get here
// (SipServletResponse.sendReliable())
// Wrong state for a 1xx rel response because we allow only one
// 1XX rel at a time
// PRACKED state means a 1xx Rel is pending or the 200 OK (PRACK)
// is not send
m_Log.log(Level.WARNING,
resp.toDebugString() + ", state = " + stateToString() +
", only one 1XX reliable at a time is permit");
// 200-299 INVITE response
} else if ((status >= 200) && (status < 300)) {
// Only 1xx rel resp without SDP pending can be ongoing
// SSA layer check that
// Initial INVITE sequence
if ((m_INVITE != null) && (m_INVITE.isInitial())) {
saveContactRouteSetRemoteTarget(resp, uas);
m_INVITE = null;
}
resp.getSessionImpl().reset1xxReliable();
// HI47267
// lets start timer for retransmission of 2xx until ACK received
startTimers(resp);
m_State = CONFIRMED_UAS;
}
// 300-699 INVITE response
else if ((status >= 300) && (status < 700)) {
// lets inform that this session is ending
uas.removeDialogSession(method, null);
m_State = TERMINATED_UAS;
m_INVITE = null;
}
} else if (method.equals("UPDATE")) {
resp.getSessionImpl().resetUpdateOngoing();
}
break;
}
}
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST,
resp.toDebugString() + ", state = " + stateToString() +
", reference = " + this);
}
}
private void saveContactRemoteTarget(SipServletResponseImpl resp, UA uas) {
try {
// need to save system headers
uas.saveContactRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException("Parse problem of Contact");
}
}
private void saveContactRouteSetRemoteTarget(SipServletResponseImpl resp,
UA uas) {
try {
// need to save system headers
uas.saveContactRouteSetRemoteTarget(resp);
} catch (ServletParseException e) {
throw new IllegalStateException(
"Parse problem of Contact or Record-Route");
}
}
public void send(SipServletResponseImpl resp, UA uas)
throws IllegalStateException {
doResponseUAS(resp, uas);
resp.getSessionImpl().updateSipSessionState(resp, uas.getType());
if (!resp.isCommitted() && resp.getSessionImpl().isB2buaHelper()) {
resp.getSessionImpl().addPendingMessage(resp, UAMode.UAS);
}
// the response need to be cloned before sending
final SipServletResponseImpl clone = (SipServletResponseImpl) resp.clone();
// if there is a transaction request, added it to the response...
SipServletRequestImpl req = resp.getRequestImpl().getTransactionRequest();
if (req != null) {
clone.setRequest(req);
clone.setSession(req.getSessionImpl());
}
IWRHandler.getInstance().handle(null, resp);
// lets run in new thread...
super.send(clone, uas);
}
private void sendRetransmission(SipServletResponseImpl r) {
SipServletResponseImpl resp = (SipServletResponseImpl) r.clone();
Dispatcher d = resp.popDispatcher();
if (d != null) {
d.dispatch(resp);
}
}
private void startTimers(SipServletResponseImpl resp) {
if (resp.isReliableProvisionalResponse()) {
// CR_38
if (m_RetransmitReliableResponse == null) {
// lets start a timer to send provisionnal resp until PRACK is
// received
// (RFC3262)
m_RetransmitReliableResponse = resp;
m_TimerLongProvRsp = TimerServiceImpl.getInstance()
.createTimer(this,
64 * TransactionManager.getInstance().getTimerT1(), DialogTimer.TimerLongProvResp);
// Start retransmit timer
m_TimerShortProvRsp = TimerServiceImpl.getInstance()
.createTimer(this, TransactionManager.getInstance().getTimerT1(),
DialogTimer.TimerShortProvRsp);
}
} else {
if (m_RetransmitResponse == null) {
// lets start a timer to send 200 until ACK is received
// (RFC326, 13.3.1)
m_RetransmitResponse = resp;
long t1 = TransactionManager.getInstance().getTimerT1();
m_TimerLong = TimerServiceImpl.getInstance()
.createTimer(this, 64 * t1,
DialogTimer.TimerLong);
// Start retransmit timer
m_TimerShort = TimerServiceImpl.getInstance()
.createTimer(this, t1,
DialogTimer.TimerShort);
}
}
}
public void timeout(GeneralTimer timer) {
DialogTimer dt = (DialogTimer) timer.getInfo();
switch (dt) {
case TimerShort:
synchronized (this) {
if (m_State == CONFIRMED_UAS) {
// Have to dirty cast in order not to reimplement hole structure
long delay = ((GeneralTimerBase) timer).getDelay();
long t2 = TransactionManager.getInstance().getTimerT2();
// calculate next timer*2 but less then T2 (4sec)
delay = ((delay * 2) <= t2) ? (delay * 2) : t2;
// schedulle new timer
m_TimerShort = TimerServiceImpl.getInstance()
.createTimer(this, delay,
DialogTimer.TimerShort);
// resend the response
sendRetransmission(m_RetransmitResponse);
}
}
break;
case TimerLong:
SipServletRequest byeRequest = null;
ReplicationUnitOfWork uow = new ReplicationUnitOfWork();
try {
synchronized (this) {
if (m_State == CONFIRMED_UAS) {
m_State = TIMEOUT_UAS;
if (m_TimerShort != null) {
m_TimerShort.cancel();
m_TimerShort = null;
}
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Timer fired after 64*T1s - end dialog");
}
// Lock the DS for the entire duration.
SipSession session = m_RetransmitResponse.getSession();
DialogFragment dialog = ((SipSessionImplBase) session).getDF();
if (dialog != null) { // to be on the safe side
uow.lockDialog(dialog);
}
// issue 1085 :: create byeRequest before invoking the listeners.
// issue 1350 :: check that session is still valid. If app is undeployed it will not be
if(session.isValid()){
byeRequest = session.createRequest("BYE");
}
else {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Skipping to send BYE since SipSession is no longer valid");
}
}
// invoke the listeners first, then they will still be done if anything
// goes wrong later with the actual creating or sending of the bye
// XXX ? should we not check if the SAS is still valid at this point?
SipApplicationSessionImpl sas = m_RetransmitResponse.getSessionImpl().getApplicationSessionImpl();
if(sas!=null){
SipApplicationListeners sipAppListeners = sas.getSipApplicationListeners();
if (sipAppListeners != null) {
ArrayList<SipErrorListener> sipErrorList =
sipAppListeners.getSipErrorListeners();
for (SipErrorListener listener : sipErrorList) {
try {
listener.noAckReceived(new SipErrorEvent(
m_RetransmitResponse.getRequest(),
m_RetransmitResponse));
} catch (Throwable t) {
m_Log.log(Level.WARNING, t.getMessage(), t);
}
}
}
}
}
}
// send after synchronization...
if (byeRequest != null) {
if (disableByeOnNoAckReceived) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "BYE is not automatically sent by the container for no ACK recieved.");
}
} else {
byeRequest.send();
}
}
} catch (IncompleteDialogException e) { // uow.lockdialog can cause this.
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "The dialog was incomplete while handling TimerLong. Skipping send", e);
}
} catch (RemoteLockRuntimeException e) { // uow.lockdialog or getSASImpl() can cause this.
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "The dialog was remotely locked while handling TimerLong. Skipping send", e);
}
} catch (IOException e) { /// bye.send can cause this.
m_Log.log(Level.WARNING, "Problem sending BYE", e);
} finally {
uow.saveAndUnlock();
}
break;
case TimerShortProvRsp:
synchronized (this) {
if (m_State == PRACKED_UAS) {
// Have to dirty cast in order not to re-implement the whole structure
long delay = ((GeneralTimerBase) timer).getDelay();
// calculate next timer*2 Exponential
delay = delay * 2;
// schedule new timer
m_TimerShortProvRsp = TimerServiceImpl.getInstance()
.createTimer(this,
delay, DialogTimer.TimerShortProvRsp);
// re-send the response
sendRetransmission(m_RetransmitReliableResponse);
}
}
break;
case TimerLongProvResp:
synchronized (this) {
if (m_State == PRACKED_UAS) {
if (m_TimerShortProvRsp != null) {
m_TimerShortProvRsp.cancel();
m_TimerShortProvRsp = null;
}
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Timer fired after 64*T1s - end dialog");
}
// Initial INVITE sequence
if ((m_INVITE != null) && (m_INVITE.isInitial())) {
m_State = EARLY_UAS;
}
// RE-INVITE case sequence
else {
m_State = RE_INVITE_UAS;
}
m_RetransmitReliableResponse.getSessionImpl()
.reset1xxReliable();
ArrayList<SipErrorListener> sipErrorList;
sipErrorList = m_RetransmitReliableResponse.getSessionImpl()
.getApplicationSessionImpl()
.getSipApplicationListeners()
.getSipErrorListeners();
for (SipErrorListener listener : sipErrorList) {
listener.noPrackReceived(new SipErrorEvent(m_INVITE,
m_RetransmitReliableResponse));
}
}
}
break;
default:
m_Log.log(Level.FINE, "IllegalTimer in dialog = " + dt);
}
}
public synchronized boolean isDeletable() {
return (m_State == TERMINATED_UAC) || (m_State == TERMINATED_UAS);
}
private String stateToString() {
if ((m_State >= 0) && (m_State < m_StateStrings.size())) {
return m_StateStrings.get(m_State);
}
return "OUT_OF_RANGE_STATE";
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(m_State);
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
m_State = in.readInt();
}
enum DialogTimer {TimerShort,
TimerLong,
TimerShortProvRsp,
TimerLongProvResp;
}
}