Package com.ericsson.ssa.sip

Source Code of com.ericsson.ssa.sip.INVITESession

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

Related Classes of com.ericsson.ssa.sip.INVITESession

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.