FileDocCategorySizeDatePackage
ClientTransaction.javaAPI DocphoneME MR2 API (J2ME)34930Wed May 02 18:00:42 BST 2007gov.nist.siplite.stack

ClientTransaction.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package gov.nist.siplite.stack;

import gov.nist.siplite.message.*;
import gov.nist.siplite.header.*;
import gov.nist.siplite.*;
import gov.nist.siplite.address.*;
import gov.nist.core.*;
import java.util.*;

import java.io.IOException;
import javax.microedition.sip.SipException;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * Represents a client transaction.
 *
 * @version JAIN-SIP-1.1
 *
 * <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
 *
 * <pre>
 *
 * Implements the following state machines. (FromHeader RFC 3261)
 *
 * |INVITE from TU
 * Timer A fires |INVITE sent
 * Reset A, V Timer B fires
 * INVITE sent +-----------+ or Transport Err.
 * +---------| |---------------+inform TU
 * | | Calling | |
 * +-------->| |-------------->|
 * +-----------+ 2xx |
 * | | 2xx to TU |
 * | |1xx |
 * 300-699 +---------------+ |1xx to TU |
 * ACK sent | | |
 * resp. to TU | 1xx V |
 * | 1xx to TU -----------+ |
 * | +---------| | |
 * | | |Proceeding |-------------->|
 * | +-------->| | 2xx |
 * | +-----------+ 2xx to TU |
 * | 300-699 | |
 * | ACK sent, | |
 * | resp. to TU| |
 * | | | NOTE:
 * | 300-699 V |
 * | ACK sent +-----------+Transport Err. | transitions
 * | +---------| |Inform TU | labeled with
 * | | | Completed |-------------->| the event
 * | +-------->| | | over the action
 * | +-----------+ | to take
 * | ^ | |
 * | | | Timer D fires |
 * +--------------+ | - |
 * | |
 * V |
 * +-----------+ |
 * | | |
 * | Terminated|<--------------+
 * | |
 * +-----------+
 *
 * Figure 5: INVITE client transaction
 *
 *
 * |Request from TU
 * |send request
 * Timer E V
 * send request +-----------+
 * +---------| |-------------------+
 * | | Trying | Timer F |
 * +-------->| | or Transport Err.|
 * +-----------+ inform TU |
 * 200-699 | | |
 * resp. to TU | |1xx |
 * +---------------+ |resp. to TU |
 * | | |
 * | Timer E V Timer F |
 * | send req +-----------+ or Transport Err. |
 * | +---------| | inform TU |
 * | | |Proceeding |------------------>|
 * | +-------->| |-----+ |
 * | +-----------+ |1xx |
 * | | ^ |resp to TU |
 * | 200-699 | +--------+ |
 * | resp. to TU | |
 * | | |
 * | V |
 * | +-----------+ |
 * | | | |
 * | | Completed | |
 * | | | |
 * | +-----------+ |
 * | ^ | |
 * | | | Timer K |
 * +--------------+ | - |
 * | |
 * V |
 * NOTE: +-----------+ |
 * | | |
 * transitions | Terminated|<------------------+
 * labeled with | |
 * the event +-----------+
 * over the action
 * to take
 *
 * Figure 6: non-INVITE client transaction
 * </pre>
 */
public class ClientTransaction
        extends Transaction
        implements SIPServerResponseInterface {
    /** Last request. */
    private Request lastRequest;
    /** Flag indicating events are pending. */
    private boolean eventPending;
    /** Via port number. */
    private int viaPort;
    /** Via host. */
    private String viaHost;
    /** Real ResponseInterface to pass messages to. */
    private SIPServerResponseInterface respondTo;

    /**
     * Creates a new client transaction.
     *
     * @param newSIPMessageStack Transaction stack this transaction
     * belongs to.
     * @param newChannelToHeaderUse Channel to encapsulate.
     */
    protected ClientTransaction(
            SIPTransactionStack newSIPMessageStack,
            MessageChannel newChannelToHeaderUse) {
        super(newSIPMessageStack, newChannelToHeaderUse);
        setBranch(SIPUtils.generateBranchId());

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "Creating clientTransaction " + this);
            // new Exception().printStackTrace();
        }
    }

    /**
     * Sets the real ResponseInterface this transaction encapsulates.
     *
     * @param newRespondToHeader ResponseInterface to send messages to.
     */
    public void setResponseInterface(SIPServerResponseInterface
            newRespondToHeader) {
        respondTo = newRespondToHeader;
    }

    /**
     * Gets the processing information.
     * @return processing information
     */
    public String getProcessingInfo() {
        return respondTo.getProcessingInfo();
    }

    /**
     * Returns this transaction.
     * @return request channel transaction
     */
    public MessageChannel getRequestChannel() {
        return this;
    }

    /**
     * Deterines if the message is a part of this transaction.
     *
     * @param messageToHeaderTest Message to check if it is part of this
     * transaction.
     *
     * @return True if the message is part of this transaction,
     * false if not.
     */
    public boolean isMessagePartOfTransaction(Message messageToHeaderTest) {
        return isMessageTransOrMult(messageToHeaderTest, false);
    }

    /**
     * Deterines if the message is a part of this transaction or it is
     * multiple 2xx response.
     *
     * @param messageToHeaderTest Message to check if it is part of this
     * transaction.
     *
     * @return True if the message is part of this transaction,
     * false if not.
     */
    public boolean isMessageTransOrMult(Message messageToHeaderTest) {
        return isMessageTransOrMult(messageToHeaderTest, true);
    }

    /**
     * Deterines if the message is a part of this transaction or it is
     * multiple 2xx response.
     *
     * @param messageToHeaderTest Message to check if it is part of this
     * transaction.
     * @param checkMultResponse flag of checking multiple 2xx response
     *
     * @return True if the message is part of this transaction,
     * false if not.
     */
    private boolean isMessageTransOrMult(Message messageToHeaderTest,
        boolean checkMultResponse) {

        // List of Via headers in the message to test
        ViaList viaHeaders = messageToHeaderTest.getViaHeaders();
        // Flags whether the select message is part of this transaction
        boolean transactionMatches;
        String messageBranch = ((ViaHeader)viaHeaders.getFirst()).getBranch();
        boolean rfc3261Compliant =
            (getBranch() != null) &&
            (messageBranch != null) &&
            getBranch().startsWith(SIPConstants.GENERAL_BRANCH_MAGIC_COOKIE) &&
            messageBranch.startsWith(SIPConstants.GENERAL_BRANCH_MAGIC_COOKIE);

        /**
         * if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         "--------- TEST ------------");
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         " testing " + this.getOriginalRequest());
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         "Against " + messageToHeaderTest);
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         "isTerminated = " + isTerminated());
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         "messageBranch = " + messageBranch);
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *         "viaList = " + messageToHeaderTest.getViaHeaders());
         *     Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
         *        "myBranch = " + getBranch());
         * }
         */

        transactionMatches = false;
        // Response 2xx should be processed even in TERMINATED state
        // RFC 3261, 13.2.2.4:
        // Multiple 2xx responses may arrive at the UAC for a single INVITE
        // request due to a forking proxy.
        boolean isResponse = messageToHeaderTest instanceof Response;
        if (!isTerminated() ||
            (checkMultResponse && isTerminated()
                && isResponse && isInviteTransaction() &&
                (((Response)messageToHeaderTest).getStatusCode()/100 == 2))) {
            if (rfc3261Compliant) {
                if (viaHeaders != null) {
                    // If the branch parameter is the
                    // same as this transaction and the method is the same,
                    if (getBranch().equals
                            (((ViaHeader)viaHeaders.getFirst()).
                            getBranch())) {
                        transactionMatches =
                                getOriginalRequest().getCSeqHeader().
                                getMethod().equals
                                (messageToHeaderTest.getCSeqHeader().
                                getMethod());

                    }
                }
            } else {
                transactionMatches =
                        getOriginalRequest().getTransactionId().equals
                        (messageToHeaderTest.getTransactionId());

            }

        }
        return transactionMatches;

    }

    /**
     * Deterines if the response is multiple (RFC 3261, 13.2.2.4).
     *
     * @param response response for checking
     * transaction.
     *
     * @return True if the input response has 2xx status, the current
     * transaction has TERMINATED state and the To tag is not same as
     * To tag of current transaction. This method doesn't compare other
     * members, it should be use with method isMessageTransOrMult
     * together .
     */
    public boolean isMultipleResponse(Response response) {
        boolean returnValue = false;
        if ((response.getStatusCode()/100 == 2) && isTerminated()) {
            Response lastResponse = getLastResponse();
            if (lastResponse != null) {
                String newTag = response.getToTag();
                returnValue = !newTag.equals(lastResponse.getToTag());
            }
        }
        return returnValue;
    }

    /**
     * Sends a request message through this transaction and
     * onto the client.
     *
     * @param messageToHeaderSend Request to process and send.
     */
    public void sendMessage(Message messageToHeaderSend)
    throws IOException {

        // Message typecast as a request
        Request transactionRequest;


        transactionRequest = (Request)messageToHeaderSend;


        // Set the branch id for the top via header.
        ViaHeader topVia =
                (ViaHeader) transactionRequest.getViaHeaders().getFirst();
        // Tack on a branch identifier to match responses.

        topVia.setBranch(getBranch());

        // If this is the first request for this transaction,
        if (getState() == INITIAL_STATE) {
            // Save this request as the one this transaction
            // is handling
            setOriginalRequest(transactionRequest);

            // Change to trying/calling state
            if (transactionRequest.getMethod().equals(Request.INVITE)) {
                setState(CALLING_STATE);
            } else if (transactionRequest.getMethod().equals(Request.ACK)) {
                // Acks are never retransmitted.
                setState(TERMINATED_STATE);
            } else {
                setState(TRYING_STATE);
            }

            if (!isReliable()) {
                enableRetransmissionTimer();
            }

            if (isInviteTransaction()) {
                enableTimeoutTimer(TIMER_B);
            } else {
                enableTimeoutTimer(TIMER_F);
            }

        } else if (getState() == PROCEEDING_STATE ||
                getState() == CALLING_STATE) {

            // If this is a TU-generated ACK request,
            if (transactionRequest.getMethod().equals(Request.ACK)) {
                // Send directly to the underlying
                // transport and close this transaction
                setState(TERMINATED_STATE);
                getMessageChannel().sendMessage(transactionRequest);
                return;

            }

        }

        try {
            // Send the message to the server
            lastRequest = transactionRequest;
            getMessageChannel().sendMessage(transactionRequest);
        } catch (IOException e) {
            setState(TERMINATED_STATE);
            throw e;
        }
    }


    /**
     * Processes a new response message through this transaction.
     * If necessary, this message will also be passed onto the TU.
     *
     * @param transactionResponse Response to process.
     * @param sourceChannel Channel that received this message.
     */
    public synchronized void processResponse(
            Response transactionResponse,
            MessageChannel sourceChannel)
            throws SIPServerException {

        // Log the incoming response in our log file.
        if (ServerLog.needsLogging(ServerLog.TRACE_MESSAGES))
            this.logResponse(transactionResponse,
                    System.currentTimeMillis(), "normal processing");

        int statusGroup = transactionResponse.getStatusCode()/100;

        // Ignore 1xx
        if (getState() == COMPLETED_STATE && statusGroup == 1) {
            return;
        // This block is against RFC 3261 17.1.1.2, figure 5 and
        // 17.1.4, figure 6
        /*
        } else if (PROCEEDING_STATE == this.getState()
        && transactionResponse.getStatusCode() == 100) {
            // Ignore 100 if received after 180
            return;
        */
        } else {
            // IMPL_NOTE: investigate if this flag may be completely removed.
            while (eventPending) {
                try {
                    // Wait for clearEventPending() call.
                    wait();
                } catch (InterruptedException e) {
                    // intentionally ignored
                    // wait for clearEventPending() call
                }
            }
        }

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "processing " +
                transactionResponse.getFirstLine()
                + "current state = "
                + getState());
        }

        this.lastResponse = transactionResponse;

        if ((dialog != null) && (statusGroup < 3)) {
            // add the route before you process the response.
            dialog.addRoute(transactionResponse);
        }

        String method = transactionResponse.getCSeqHeader().getMethod();
        
        if (dialog != null) {
            boolean added = false;
            SIPTransactionStack sipStackImpl
                    = (SIPTransactionStack) getSIPStack();

            // A tag just got assigned or changed.
            if (dialog.getRemoteTag() == null &&
                    transactionResponse.getTo().getTag() != null) {

                // Dont assign tag on provisional response
                if (transactionResponse.getStatusCode() != 100) {
                    dialog.setRemoteTag(transactionResponse.getToTag());
                }

                String dialogId = transactionResponse.getDialogId(false);
                dialog.setDialogId(dialogId);

                if (sipStackImpl.isDialogCreated(method) &&
                        transactionResponse.getStatusCode() != 100) {
                    sipStackImpl.putDialog(dialog);
                    if (statusGroup == 1) {
                        dialog.setState(Dialog.EARLY_STATE);
                    } else if (statusGroup == 2) {
                        dialog.setState(Dialog.CONFIRMED_STATE);
                    }
                    added = true;
                }

            } else if (dialog.getRemoteTag() != null &&
                    transactionResponse.getToTag() != null &&
                    ! dialog.getRemoteTag().equals
                    (transactionResponse.getToTag())) {
                dialog.setRemoteTag(transactionResponse.getToTag());
                String dialogId = transactionResponse.getDialogId(false);
                dialog.setDialogId(dialogId);

                if (sipStackImpl.isDialogCreated(method)) {
                    sipStackImpl.putDialog(dialog);
                    added = true;
                }
            }

            if (sipStackImpl.isDialogCreated(method)) {
                // Make a final tag assignment.
                if (transactionResponse.getToTag() != null &&
                        statusGroup == 2) {
                    // This is a dialog creating method (such as INVITE).
                    // 2xx response -- set the state to the confirmed
                    // state.
                    dialog.setRemoteTag(transactionResponse.getToTag());
                    dialog.setState(Dialog.CONFIRMED_STATE);
                } else if ((
                        transactionResponse.getStatusCode() == 487 ||
                        statusGroup == 5 ||
                        statusGroup == 6) &&
                        (dialog.getState() == -1 ||
                        dialog.getState() ==
                        Dialog.EARLY_STATE)) {
                    // Invite transaction generated an error.
                    dialog.setState(Dialog.TERMINATED_STATE);
                }
            }
            
            // 200 OK for a bye so terminate the dialog.
            if (transactionResponse.getCSeqHeader().
                    getMethod().equals(Request.BYE) &&
                        transactionResponse.getStatusCode() == 200) {
                dialog.setState(Dialog.TERMINATED_STATE);
            }
        }
        
        try {
            if (isInviteTransaction()) {
                inviteClientTransaction(transactionResponse, sourceChannel);
            } else {
                nonInviteClientTransaction(transactionResponse, sourceChannel);
            }
        } catch (IOException ex) {
            setState(TERMINATED_STATE);
            raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR);
        }
    }

    /**
     * Implements the state machine for invite client transactions.
     * @param transactionResponse -- transaction response received.
     * @param sourceChannel - source channel on which the response was received.
     * <pre>
     *
     * |Request from TU
     * |send request
     * Timer E V
     * send request +-----------+
     * +---------| |-------------------+
     * | | Trying | Timer F |
     * +-------->| | or Transport Err.|
     * +-----------+ inform TU |
     * 200-699 | | |
     * resp. to TU | |1xx |
     * +---------------+ |resp. to TU |
     * | | |
     * | Timer E V Timer F |
     * | send req +-----------+ or Transport Err. |
     * | +---------| | inform TU |
     * | | |Proceeding |------------------>|
     * | +-------->| |-----+ |
     * | +-----------+ |1xx |
     * | | ^ |resp to TU |
     * | 200-699 | +--------+ |
     * | resp. to TU | |
     * | | |
     * | V |
     * | +-----------+ |
     * | | | |
     * | | Completed | |
     * | | | |
     * | +-----------+ |
     * | ^ | |
     * | | | Timer K |
     * +--------------+ | - |
     * | |
     * V |
     * NOTE: +-----------+ |
     * | | |
     * transitions | Terminated|<------------------+
     * labeled with | |
     * the event +-----------+
     * over the action
     * to take
     *
     * Figure 6: non-INVITE client transaction
     */
    private void nonInviteClientTransaction(
            Response transactionResponse,
            MessageChannel sourceChannel)
            throws IOException, SIPServerException {
        int currentState = getState();

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "nonInviteClientTransaction " +
                transactionResponse.getFirstLine());
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "currentState = " + currentState);
        }

        int statusCode = transactionResponse.getStatusCode();
        if (currentState == TRYING_STATE) {
            if (statusCode / 100 == 1) {
                // Response to TU, RFC 3261, 17.1.4, figure 6
                respondTo.processResponse(transactionResponse, this);
                setState(PROCEEDING_STATE);
                enableRetransmissionTimer
                        (MAXIMUM_RETRANSMISSION_TICK_COUNT);
                enableTimeoutTimer(TIMER_F);
            } else if (200 <= statusCode && statusCode <= 699) {
                // Send the response up to the TU.
                respondTo.processResponse(transactionResponse, this);
                if (! isReliable()) {
                    setState(COMPLETED_STATE);
                    enableTimeoutTimer(TIMER_K);
                } else {
                    setState(TERMINATED_STATE);
                }
            }
        } else if (currentState == PROCEEDING_STATE &&
                200 <= statusCode && statusCode <= 699) {

            respondTo.processResponse(transactionResponse, this);

            disableRetransmissionTimer();
            disableTimeoutTimer();
            if (! isReliable()) {
                setState(COMPLETED_STATE);
                enableTimeoutTimer(TIMER_K);
            } else {
                setState(TERMINATED_STATE);
	        }
        } else if (currentState == PROCEEDING_STATE &&
                statusCode / 100 == 1) {
		    // Response to TU, RFC 3261, 17.1.4, figure 6
            respondTo.processResponse(transactionResponse, this);
        }
    }

    /**
     * Implements the state machine for invite client transactions.
     * @param transactionResponse -- transaction response received.
     * @param sourceChannel - source channel on which the response was received.
     * <pre>
     *
     * |INVITE from TU
     * Timer A fires |INVITE sent
     * Reset A, V Timer B fires
     * INVITE sent +-----------+ or Transport Err.
     * +---------| |---------------+inform TU
     * | | Calling | |
     * +-------->| |-------------->|
     * +-----------+ 2xx |
     * | | 2xx to TU |
     * | |1xx |
     * 300-699 +---------------+ |1xx to TU |
     * ACK sent | | |
     * resp. to TU | 1xx V |
     * | 1xx to TU -----------+ |
     * | +---------| | |
     * | | |Proceeding |-------------->|
     * | +-------->| | 2xx |
     * | +-----------+ 2xx to TU |
     * | 300-699 | |
     * | ACK sent, | |
     * | resp. to TU| |
     * | | | NOTE:
     * | 300-699 V |
     * | ACK sent +-----------+Transport Err. | transitions
     * | +---------| |Inform TU | labeled with
     * | | | Completed |-------------->| the event
     * | +-------->| | | over the action
     * | +-----------+ | to take
     * | ^ | |
     * | | | Timer D fires |
     * +--------------+ | - |
     * | |
     * V |
     * +-----------+ |
     * | | |
     * | Terminated|<--------------+
     * | |
     * +-----------+
     * </pre>
     */
    private void inviteClientTransaction(
            Response transactionResponse,
            MessageChannel sourceChannel)
            throws IOException, SIPServerException {
        int statusCode = transactionResponse.getStatusCode();
        int currentState = getState();

        if (currentState == TERMINATED_STATE) {
            // Do nothing in the terminated state.
            return;
        } else if (currentState == CALLING_STATE) {
            if (statusCode/100 == 2) {
                // 200 responses are always seen by TU.
                respondTo.processResponse(transactionResponse, this);
                disableRetransmissionTimer();
                disableTimeoutTimer();
                setState(TERMINATED_STATE);
            } else if (statusCode/100 == 1) {
                disableRetransmissionTimer();
                disableTimeoutTimer();
                respondTo.processResponse(transactionResponse, this);
                setState(PROCEEDING_STATE);
            } else if (300 <= statusCode && statusCode <= 699) {
                // When in either the "Calling" or "Proceeding" states,
                // reception of response with status code from 300-699
                // MUST cause the client transaction to
                // transition to "Completed".
                // The client transaction MUST pass the received response up to
                // the TU, and the client transaction MUST generate an
                // ACK request.

                respondTo.processResponse(transactionResponse, this);

                // Send back an ACK request
                try {
                    sendMessage((Request) createAck());
                } catch (SipException ex) {
                    InternalErrorHandler.handleException(ex);
                }
                if (! isReliable()) {
                    setState(COMPLETED_STATE);
                    enableTimeoutTimer(TIMER_D);
                } else {
                    // Proceed immediately to the TERMINATED state.
                    setState(TERMINATED_STATE);
                }
            }
        } else if (currentState == PROCEEDING_STATE) {
            if (statusCode / 100 == 1) {
                respondTo.processResponse(transactionResponse, this);
            } else if (statusCode / 100 == 2) {
                setState(TERMINATED_STATE);
                respondTo.processResponse(transactionResponse, this);
            } else if (300 <= statusCode && statusCode <= 699) {
                respondTo.processResponse(transactionResponse, this);
                // Send back an ACK request
                try {
                    sendMessage((Request)createAck());
                } catch (SipException ex) {
                    InternalErrorHandler.handleException(ex);
                }
                if (! isReliable()) {
                    setState(COMPLETED_STATE);
                    enableTimeoutTimer(TIMER_D);
                } else {
                    setState(TERMINATED_STATE);
                }
            }
        } else if (currentState == COMPLETED_STATE) {
            if (300 <= statusCode && statusCode <= 699) {
                // Send back an ACK request
                try {
                    sendMessage((Request)createAck());
                } catch (SipException ex) {
                    InternalErrorHandler.handleException(ex);
                }
            }

        }

    }

    /**
     * Sends specified {@link gov.nist.siplite.message.Request} on a unique
     * client transaction identifier. This method implies that the application
     * is functioning as either a User Agent Client or a Stateful proxy, hence
     * the underlying SipProvider acts statefully.
     * <p>
     * JAIN SIP defines a retransmission utility specific to user agent
     * behaviour and the default retransmission behaviour for each method.
     * <p>
     * When an application wishes to send a message, it creates a Request
     * message passes that Request to this method, this method returns the
     * cleintTransactionId generated by the SipProvider. The Request message
     * gets sent via the ListeningPoint that this SipProvider is attached to.
     * <ul>
     * <li>User Agent Client - must not send a BYE on a confirmed INVITE until
     * it has received an ACK for its 2xx response or until the server
     * transaction times out.
     * </ul>
     *
     * @throws IOException if an I/O error occured
     * @throws SipException if implementation cannot send request for any
     * other reason
     */
    public void sendRequest() throws IOException, SipException {
        Request sipRequest = getOriginalRequest();
        sendMessage(sipRequest);
    }


    /**
     * Called by the transaction stack when a retransmission timer
     * fires.
     */
    protected void fireRetransmissionTimer() {
        boolean noSend = false;
        try {
            // Resend the last request sent
            // System.out.println("fireRetransmissionTimer ");
            if (this.getState() == -1) {
                noSend = true;
            } else {
                MessageProcessor mp = this.getMessageProcessor();
                if (mp == null) {
                    noSend = true;
                } else if (mp.toExit()) {
                    noSend = true;
                }
            }
            int currentState = this.getState();
            if (!noSend && (currentState == CALLING_STATE ||
                currentState == TRYING_STATE)) {
                getMessageChannel().sendMessage(lastRequest);
            }
        } catch (IOException e) {
            raiseErrorEvent(SIPTransactionErrorEvent.TRANSPORT_ERROR);
        }
    }

    /**
     * Called by the transaction stack when a timeout timer fires.
     */
    protected void fireTimeoutTimer() {
        Dialog dialogImpl = this.getDialog();
        if (getState() == CALLING_STATE ||
                getState() == TRYING_STATE ||
                getState() == PROCEEDING_STATE) {
            // Timeout occured. If this is asociated with a transaction
            // creation then kill the dialog.
            if (dialogImpl != null) {
                if (((SIPTransactionStack)getSIPStack()).isDialogCreated
                        (this.getOriginalRequest().getMethod())) {
                    // terminate the enclosing dialog.
                    dialogImpl.setState(Dialog.TERMINATED_STATE);
                } else if (getOriginalRequest().getMethod().equals
                        (Request.BYE)) {
                    // Terminate the associated dialog on BYE Timeout.
                    dialogImpl.setState(Dialog.TERMINATED_STATE);
                }
            }
        }
        if (getState() != COMPLETED_STATE) {
            raiseErrorEvent(SIPTransactionErrorEvent.TIMEOUT_ERROR);
        } else {
            setState(TERMINATED_STATE);
        }
    }

    /**
     * Creates a new Cancel message from the Request associated with this client
     * transaction. The CANCEL request, is used to cancel the previous request
     * sent by this client transaction. Specifically, it asks the UAS to cease
     * processing the request and to generate an error response to that request.
     *
     * @return a cancel request generated from the original request.
     * @throws SipException if the request cannot be cancelled.
     */
    public Request createCancel() throws SipException {
        Request originalRequest = getOriginalRequest();
        return originalRequest.createCancelRequest();
    }

    /**
     * Creates an ACK request for this transaction
     *
     * @return an ack request generated from the original request.
     * @throws SipException if transaction is in the wrong state to be acked.
     */
    public Request createAck() throws SipException {
        Request originalRequest = getOriginalRequest();
        int statusCode = 0;

        if (originalRequest.getMethod().equals(Request.ACK)) {
            throw new SipException("Cannot ACK an ACK!",
                SipException.INVALID_OPERATION);
        } else if (lastResponse == null) {
            throw new SipException("bad Transaction state",
                SipException.INVALID_STATE);
        } else {
            statusCode = lastResponse.getStatusCode();
            if (statusCode < 200) {
                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                        "lastResponse = " + lastResponse);
                }

                throw new SipException("Cannot ACK a provisional response!",
                    SipException.INVALID_OPERATION);
            }
        }

        Request ackRequest =
            originalRequest.createAckRequest((ToHeader)lastResponse.getTo());

        // Automatic ACK at transaction layer
        if (300 <= statusCode && statusCode <= 699) {
            ViaHeader topmostVia = originalRequest.getTopmostVia();
            if (topmostVia != null) {
                ackRequest.setHeader(topmostVia);
            }
            return ackRequest;
        }

        // Pull the record route headers from the last reesponse.
        buildRouteSet(ackRequest);
        return ackRequest;


    }

    /**
     * Sets the port of the recipient.
     * @param port the new via port
     */
    protected void setViaPort(int port) { this.viaPort = port; }

    /**
     * Sets the port of the recipient.
     * @param host the new via host
     */
    protected void setViaHost(String host) { this.viaHost = host; }

    /**
     * Gets the port of the recipient.
     * @return the via port
     */
    public int getViaPort() { return this.viaPort; }

    /**
     * Gets the host of the recipient.
     * @return the via host
     */
    public String getViaHost() { return this.viaHost; }

    /**
     * Gets the via header for an outgoing request.
     * @return the via header reader
     */
    public ViaHeader getOutgoingViaHeader() {
        return this.getMessageProcessor().getViaHeader();
    }

    /**
     * Checks if connection is secure.
     * @return true if connection is secure.
     */
    public boolean isSecure() { return encapsulatedChannel.isSecure(); }

    /**
     * Clears the event pending flag.
     */
    public synchronized void clearEventPending() {
        eventPending = false;
        notify();
    }

    /**
     * Sets the event pending flag.
     */
    public synchronized void setEventPending() {
        eventPending = true;
    }

    /**
     * Create a new client transaction based on current.
     * Field lastResponse is filled by input parameter.
     * @param lastResponse last response
     * @return new instance of client transaction.
     */
    public ClientTransaction cloneWithNewLastResponse(Response lastResponse) {
        ClientTransaction clientTransaction = new ClientTransaction(
            (SIPTransactionStack)getSIPStack(), getMessageChannel());
        clientTransaction.lastResponse = lastResponse;
        clientTransaction.setOriginalRequest(getOriginalRequest());
        return clientTransaction;
    }

}