Package org.cafesip.jiplet.sip

Source Code of org.cafesip.jiplet.sip.ResponseForwarding

/*
* ResponseForwarding.java
*
* Created on April 16, 2003, 11:09 AM
*/

package org.cafesip.jiplet.sip;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.TransactionState;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.RecordRouteHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.cafesip.jiplet.Jiplet;
import org.cafesip.jiplet.JipletDialog;
import org.cafesip.jiplet.JipletLogger;

/**
*
* @author deruelle
*/
public class ResponseForwarding
{
    private SipProvider responseProvider;

    private Jiplet jiplet;

    private boolean presenceServer;

    /** Creates a new instance of ResponseForwarding */
    public ResponseForwarding(Jiplet jiplet, SipProvider sipProvider,
            boolean presenceServer)
    {
        this.jiplet = jiplet;
        this.responseProvider = sipProvider;
        this.presenceServer = presenceServer;
    }

    public void forwardResponse(Response response,
            ClientTransaction clientTransaction) throws SipException,
            ParseException
    {
        TransactionsMapping transactionsMapping = null;

        try
        {
            /*
             * RFC 3261: 16.7. Response Processing: When a response is received
             * by an element, it first tries to locate a client transaction
             * (Section 17.1.3) matching the response. If none is found, the
             * element MUST process the response (even if it is an informational
             * response) as a stateless jiplet (described below). If a match is
             * found, the response is handed to the client transaction. 1. Find
             * the appropriate response context 2. Update timer C for
             * provisional responses 3. Remove the topmost Via 4. Add the
             * response to the response context 5. Check to see if this response
             * should be forwarded immediately 6. When necessary, choose the
             * best final response from the response context 7. Aggregate
             * authorization header field values if necessary 8. Optionally
             * rewrite Record-Route header field values 9. Forward the response
             * 10. Generate any necessary CANCEL requests
             *
             * 16.11: Response processing as described in Section 16.7 does not
             * apply to a jiplet behaving statelessly. When a response arrives
             * at a stateless jiplet, the jiplet MUST inspect the sent-by value
             * in the first (topmost) Via header field value. If that address
             * matches the jiplet, (it equals a value this jiplet has inserted
             * into previous requests) the jiplet MUST remove that header field
             * value from the response and forward the result to the location
             * indicated in the next Via header field value. The jiplet MUST NOT
             * add to, modify, or remove the message body. Unless specified
             * otherwise, the jiplet MUST NOT remove any other header field
             * values. If the address does not match the jiplet, the message
             * MUST be silently discarded.
             */

            if (clientTransaction == null
                    || clientTransaction.getDialog() == null)
            {
                if (ProxyUtilities.hasTopViaHeaderProxy(jiplet, response))
                {
                    ListIterator viaList = response.getHeaders(ViaHeader.NAME);
                    response.removeHeader(ViaHeader.NAME);
                    ArrayList v = new ArrayList();
                    viaList.next();
                    while (viaList.hasNext())
                    {
                        ViaHeader viaHeader = (ViaHeader) viaList.next();
                        v.add(viaHeader);
                    }

                    for (int j = 0; j < v.size(); j++)
                    {
                        ViaHeader viaHeader = (ViaHeader) v.get(j);
                        response.addHeader(viaHeader);
                    }

                    viaList = response.getHeaders(ViaHeader.NAME);
                    if (viaList == null || !viaList.hasNext())
                    {
                        return;
                    }
                    else
                    {
                        ListeningPoint defaultLP = jiplet
                                .getListeningPointDefault();
                        jiplet.getSipProvider(defaultLP).sendResponse(response);

                        return;
                    }
                }
                else
                {
                    return;
                }
            }

            if (clientTransaction.getDialog() == null)
                return;
            else
            {
                JipletDialog jd = jiplet.getDialog(clientTransaction
                        .getDialog(), true);
                transactionsMapping = jd.getTransactionsMapping();
            }

            // 1. Find the appropriate response context

            /*
             * The jiplet locates the "response context" it created before
             * forwarding the original request using the key described in
             * Section 16.6. The remaining processing steps take place in this
             * context.
             */

            // I guess it is done by the stack....
            // 2. Update timer C for provisional responses
            // I guess it is done by the stack....
            // 3. Via
            /*
             * The jiplet removes the topmost Via header field value from the
             * response. If no Via header field values remain in the response,
             * the response was meant for this element and MUST NOT be
             * forwarded. The remainder of the processing described in this
             * section is not performed on this message, the UAC processing
             * rules described in Section 8.1.3 are followed instead (transport
             * layer processing has already occurred).
             */

            ListIterator viaList = response.getHeaders(ViaHeader.NAME);
            viaList.next();
            viaList.remove();
            if (!viaList.hasNext())
            {
                return;
            }

            // 4. Add response to context

            // The stack takes care of that...

            // 5. Check response for forwarding

            /*
             * Until a final response has been sent on the server transaction,
             * the following responses MUST be forwarded immediately: - Any
             * provisional response other than 100 (Trying) - Any 2xx response
             */

            if (response.getStatusCode() == Response.TRYING)
            {
                return;
            }

            if (response.getStatusCode() == Response.REQUEST_TERMINATED)
            {
                return;
            }

            CSeqHeader cseqHeader = (CSeqHeader) response
                    .getHeader(CSeqHeader.NAME);

            // No special processing for OK related to SUBSCIRBE and NOTIFY
            if (presenceServer && response.getStatusCode() == Response.OK
                    && cseqHeader.getMethod().equals(Request.SUBSCRIBE))
            {
                return;
            }

            if (presenceServer && response.getStatusCode() == Response.OK
                    && cseqHeader.getMethod().equals(Request.NOTIFY))
            {
                return;
            }

            // 6. Choosing the best response

            /*
             * A jiplet MUST NOT insert a tag into the To header field of a 1xx
             * or 2xx response if the request did not contain one. A jiplet MUST
             * NOT modify the tag in the To header field of a 1xx or 2xx
             * response.
             *
             * An element SHOULD preserve the To tag when simply forwarding a
             * 3-6xx response to a request that did not contain a To tag.
             *
             * A jiplet MUST NOT modify the To tag in any forwarded response to
             * a request that contains a To tag.
             */

            // 7. Aggregate Authorization Header Field Values
            // 8. Record-Route (we may modify it before sending, see further
            // below)
            // 9. Forward response
            /*
             * 16.7. 9. Forward response: The jiplet MUST pass the response to
             * the server transaction associated with the response context. This
             * will result in the response being sent to the location now
             * indicated in the topmost Via header field value. If the server
             * transaction is no longer available to handle the transmission,
             * the element MUST forward the response statelessly by sending it
             * to the server transport. The server transaction might indicate
             * failure to send the response or signal a timeout in its state
             * machine. These errors would be logged for diagnostic purposes as
             * appropriate, but the protocol requires no remedial action from
             * the jiplet.
             */
            ServerTransaction serverTransaction = transactionsMapping
                    .getServerTransaction(clientTransaction);

            // For forking:
            if ((response.getStatusCode() == Response.UNAUTHORIZED || response
                    .getStatusCode() == Response.DECLINE)
                    && serverTransaction != null)
            {
                // check the busy or decline
                Vector clientsTransactionList = transactionsMapping
                        .getClientTransactions(serverTransaction);
                if (clientsTransactionList != null
                        && clientsTransactionList.size() > 1)
                {
                    transactionsMapping.removeMapping(clientTransaction);
                }
            }

            // For forking:
            if (response.getStatusCode() == Response.OK
                    && serverTransaction != null)
            {
                Dialog peerDialog = serverTransaction.getDialog();
                Vector clientsTransactionList = transactionsMapping
                        .getClientTransactions(serverTransaction);

                if (peerDialog != null && peerDialog.getState() != null
                        && peerDialog.getState().equals(DialogState.CONFIRMED)
                        && clientsTransactionList != null
                        && clientsTransactionList.size() > 1)
                {
                    Dialog dialog = clientTransaction.getDialog();
                    Request byeRequest = dialog.createRequest(Request.BYE);

                    ClientTransaction ct = responseProvider
                            .getNewClientTransaction(byeRequest);
                    dialog.sendRequest(ct);

                    // we have to remove the transaction from the table:
                    transactionsMapping.removeMapping(clientTransaction);
                    return;
                }
                else
                {
                    if (serverTransaction != null)
                        transactionsMapping.addMapping(serverTransaction,
                                clientTransaction);
                }
            }

            if (serverTransaction == null)
            {
                ListeningPoint defaultLP = jiplet.getListeningPointDefault();
                jiplet.getSipProvider(defaultLP).sendResponse(response);
                return;
            }
            else
            {
                // we can try to modify the tags:
                Dialog dialog = serverTransaction.getDialog();
                if (dialog != null)
                {
                    String localTag = dialog.getLocalTag();
                    String remoteTag = dialog.getRemoteTag();
                    ToHeader toHeader = (ToHeader) response
                            .getHeader(ToHeader.NAME);
                    FromHeader fromHeader = (FromHeader) response
                            .getHeader(FromHeader.NAME);

                    if (localTag != null && remoteTag != null)
                    {
                        if (dialog.isServer())
                        {
                            toHeader.setTag(localTag);
                        }
                        else
                        {
                            fromHeader.setTag(remoteTag);
                        }
                    }
                }

                // 8. Record-Route - modify record route header if needed
                /*
                 * If the selected response contains a Record-Route header field
                 * value originally provided by this proxy, the proxy MAY choose
                 * to rewrite the value before forwarding the response. This
                 * allows the proxy to provide different URIs for itself to the
                 * next upstream and downstream elements. A proxy may choose to
                 * use this mechanism for any reason. For instance, it is useful
                 * for multi-homed hosts.
                 */

                ListIterator rrHeaders = response
                        .getHeaders(RecordRouteHeader.NAME);
                while (rrHeaders.hasNext())
                {
                    // look for the 1st one to replace, replace it & get out
                    RecordRouteHeader rr = (RecordRouteHeader) rrHeaders.next();
                    URI uri = rr.getAddress().getURI();
                    if (uri instanceof SipURI)
                    {
                        SipURI sipURI = (SipURI) uri;
                        // is this a record route header we added?
                        if (jiplet.hasAddress(sipURI.getHost(), sipURI
                                .getPort()))
                        {
                            // does this one need replacing?
                            String user = sipURI.getUser();
                            if (user != null)
                            {
                                StringTokenizer tok = new StringTokenizer(user,
                                        "-");
                                if (tok.countTokens() == 2)
                                {
                                    SipURI sourceURI = jiplet
                                            .getAddressFactory().createSipURI(
                                                    null, tok.nextToken());

                                    sourceURI.setPort(Integer.valueOf(
                                            tok.nextToken()).intValue());
                                    sourceURI.setLrParam();

                                    // rewrite it back into the RR header
                                    rr.getAddress().setURI(sourceURI);
                                    rrHeaders.set(rr);
                                    break;
                                }
                            }
                        }
                    }
                }

                try
                {
                    serverTransaction.sendResponse(response);
                }
                catch (InvalidArgumentException e)
                {
                    // this exception only happens if the Response was created
                    // by Dialog.createReliableProvisionalResponse(int) and the
                    // application calls ServerTransaction.sendResponse() to
                    // send it
                    JipletLogger
                            .error("Response forwarding failed - invalid send method for reliable provisional response - need to add a check for this and call dialog method sendReliableProvisionalResponse() instead. Response = \n"
                                    + response.toString());
                }
            }

            /** ************************************************************************ */
            /** ************ 10. Generate CANCELs ******* */
            /** ************************************************************************* */
            /*
             * If the forwarded response was a final response, the jiplet MUST
             * generate a CANCEL request for all pending client transactions
             * associated with this response context. A jiplet SHOULD also
             * generate a CANCEL request for all pending client transactions
             * associated with this response context when it receives a 6xx
             * response. A pending client transaction is one that has received a
             * provisional response, but no final response (it is in the
             * proceeding state) and has not had an associated CANCEL generated
             * for it. Generating CANCEL requests is described in Section 9.1.
             */

            if (response.getStatusCode() == Response.OK
                    || (response.getStatusCode() >= Response.BUSY_EVERYWHERE && response
                            .getStatusCode() <= Response.SESSION_NOT_ACCEPTABLE))
            {
                Vector clientsTransactionList = transactionsMapping
                        .getClientTransactions(serverTransaction);

                for (Enumeration e = clientsTransactionList.elements(); e
                        .hasMoreElements();)
                {
                    ClientTransaction ctr = (ClientTransaction) e.nextElement();
                    if (ctr != clientTransaction)
                    {
                        TransactionState transactionState = ctr.getState();
                        if (transactionState == null
                                || transactionState.getValue() == TransactionState.PROCEEDING
                                        .getValue())
                        {

                            /*
                             * 9.1: The following procedures are used to
                             * construct a CANCEL request. The Request-URI,
                             * Call-ID, To, the numeric part of CSeq, and From
                             * header fields in the CANCEL request MUST be
                             * identical to those in the request being
                             * cancelled, including tags. A CANCEL constructed
                             * by a client MUST have only a single Via header
                             * field value matching the top Via value in the
                             * request being cancelled. Using the same values
                             * for these header fields allows the CANCEL to be
                             * matched with the request it cancels (Section 9.2
                             * indicates how such matching occurs). However, the
                             * method part of the CSeq header field MUST have a
                             * value of CANCEL. This allows it to be identified
                             * and processed as a transaction in its own right
                             * (See Section 17).
                             *
                             * If the request being cancelled contains a Route
                             * header field, the CANCEL request MUST include
                             * that Route header field's values.
                             */
                            Request cancelRequest = ctr.createCancel();

                            // Let's keep only the top most via header:
                            ListIterator cancelViaList = cancelRequest
                                    .getHeaders(ViaHeader.NAME);
                            cancelRequest.removeHeader(ViaHeader.NAME);
                            cancelRequest.addHeader((ViaHeader) cancelViaList
                                    .next());

                            SipURI localAddr = (SipURI) ctr.getDialog()
                                    .getLocalParty().getURI();
                            SipProvider p = jiplet.getSipProvider(localAddr
                                    .getHost(), localAddr.getPort());

                            if (p == null)
                            {
                                ListeningPoint defaultLP = jiplet
                                        .getListeningPointDefault();
                                p = jiplet.getSipProvider(defaultLP);
                            }

                            ClientTransaction ct = p
                                    .getNewClientTransaction(cancelRequest);
                            ct.sendRequest();
                        }
                    }
                }
            }
        }
        finally
        {
            if (clientTransaction != null
                    && clientTransaction.getState().equals(
                            TransactionState.COMPLETED))
            {
                if (clientTransaction.getDialog() != null)
                {
                    JipletDialog jd = jiplet.getDialog(clientTransaction
                            .getDialog(), true);
                    transactionsMapping = jd.getTransactionsMapping();

                    if (transactionsMapping != null)
                        transactionsMapping.removeMapping(clientTransaction);
                }
            }
        }
    }
}
TOP

Related Classes of org.cafesip.jiplet.sip.ResponseForwarding

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.