FileDocCategorySizeDatePackage
Request.javaAPI DocphoneME MR2 API (J2ME)32038Wed May 02 18:00:42 BST 2007gov.nist.siplite.message

Request.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.message;

import gov.nist.core.*;
import gov.nist.siplite.address.*;
import gov.nist.siplite.header.*;
import gov.nist.siplite.SIPConstants;
import java.util.*;
import java.io.UnsupportedEncodingException;
import javax.microedition.sip.SipException;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
import gov.nist.microedition.io.j2me.sip.DistributedRandom;

/**
 * The SIP Request structure-- this belongs to the parser who fills it up.
 *
 * @version JAIN-SIP-1.1
 *
 * <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
 *
 */
public final class Request extends Message {
    /** Acknowledgement request. */
    public static final String ACK = "ACK";

    /** End of session request. */
    public static final String BYE = "BYE";

    /** Terminate session request. */
    public static final String CANCEL = "CANCEL";

    /** Invitation request. */
    public static final String INVITE = "INVITE";

    /** Optional settings request. */
    public static final String OPTIONS = "OPTIONS";

    /** Regsitration request. */
    public static final String REGISTER = "REGISTER";

    /** Notification request. */
    public static final String NOTIFY = "NOTIFY";

    /** Subscription for notification request. */
    public static final String SUBSCRIBE = "SUBSCRIBE";

    /** Message request. */
    public static final String MESSAGE = "MESSAGE";

    /** Redirection request. */
    public static final String REFER = "REFER";

    /** Basic information request. */
    public static final String INFO = "INFO";

    /** PRACK ??? RFC. */
    public static final String PRACK = "PRACK";

    /** Update request. */
    public static final String UPDATE = "UPDATE";

    /** Publish request. */
    public static final String PUBLISH = "PUBLISH";

    /** Default user name is "ip". */
    public static final String DEFAULT_USER = "ip";

    /** Default time to live is 1 second. */
    public static final int DEFAULT_TTL = 1;

    /** Default transport is "udp". */
    public static final String DEFAULT_TRANSPORT = SIPConstants.TRANSPORT_UDP;

    /** Default method is to intiate an INVITE. */
    public static final String DEFAULT_METHOD = INVITE;

    /** Current transaction pointer. */
    private Object transactionPointer;

    /** Current requestline. */
    protected RequestLine requestLine;


    /**
     * Gets the Request Line of the Request.
     * @return the request line of the SIP Request.
     */
    public RequestLine getRequestLine() {
        return requestLine;
    }

    /**
     * Sets the request line of the SIP Request.
     * @param requestLine is the request line to set in the SIP Request.
     */
    public void setRequestLine(RequestLine requestLine) {
        this.requestLine = requestLine;
    }

    /**
     * Constructor.
     */
    public Request() { super(); }

    /**
     * Checks header for constraints.
     * <pre>
     * (1) Invite options and bye requests can only have SIP URIs in the
     * contact headers.
     * (2) Request must have cseq, to and from and via headers.
     * (3) Method in request URI must match that in CSEQ.
     * </pre>
     */
    protected void checkHeaders() throws ParseException {
        String prefix = "Missing Header ";

        /* Check for required headers */

        if (getCSeqHeader() == null) {
            throw new ParseException(prefix + Header.CSEQ, 0);
        }
        if (getTo() == null) {
            throw new ParseException(prefix + Header.TO, 0);
        }
        if (getFromHeader() == null) {
            throw new ParseException(prefix + Header.FROM, 0);
        }
        if (getViaHeaders() == null) {
            throw new ParseException(prefix + Header.VIA, 0);
        }

    /*
     * BUGBUG
     * Need to revisit this check later...
     * for now we just leave this to the
     * application to catch.
     */

        if (requestLine != null && requestLine.getMethod() != null &&
                getCSeqHeader().getMethod() != null &&
                compareToIgnoreCase
                (requestLine.getMethod(), getCSeqHeader().getMethod()) != 0) {
            throw
                    new ParseException
                    ("CSEQ method mismatch with Request-Line ", 0);

        }

    }

    /**
     * Sets the default values in the request URI if necessary.
     */
    protected void setDefaults() {
        // The request line may be unparseable (set to null by the
        // exception handler.
        if (requestLine == null)
            return;
        String method = requestLine.getMethod();
        // The requestLine may be malformed!
        if (method == null)
            return;
        URI u = requestLine.getUri();
        if (u == null)
            return;
        if (method.compareTo(REGISTER) == 0
                || method.compareTo(INVITE) == 0) {
            if (u instanceof SipURI) {
                SipURI sipUri = (SipURI) u;
                sipUri.setUserParam(DEFAULT_USER);
                try {
                    sipUri.setTransportParam(DEFAULT_TRANSPORT);
                } catch (ParseException ex) {}
            }
        }
    }

    /**
     * Patch up the request line as necessary.
     */
    protected void setRequestLineDefaults() {
        String method = requestLine.getMethod();
        if (method == null) {
            CSeqHeader cseq = (CSeqHeader) this.getCSeqHeader();
            if (cseq != null) {
                method = cseq.getMethod();
                requestLine.setMethod(method);
            }
        }
    }

    /**
     * A conveniance function to access the Request URI.
     * @return the requestURI if it exists.
     */
    public URI getRequestURI() {
        if (this.requestLine == null)
            return null;
        else
            return this.requestLine.getUri();
    }

    /**
     * Sets the RequestURI of Request. The Request-URI is a SIP or
     * SIPS URI or a general URI. It indicates the user or service to which
     * this request is being addressed. SIP elements MAY support
     * Request-URIs with schemes other than "sip" and "sips", for
     * example the "tel" URI scheme. SIP elements MAY translate
     * non-SIP URIs using any mechanism at their disposal, resulting
     * in SIP URI, SIPS URI, or some other scheme.
     *
     * @param uri the new Request URI of this request message
     */
    public void setRequestURI(URI uri) {
        if (this.requestLine == null) {
            this.requestLine = new RequestLine();
        }
        this.requestLine.setUri((URI)uri);
    }

    /**
     * Sets the method.
     * @param method is the method to set.
     * @throws IllegalArgumentException if the method is null
     */
    public void setMethod(String method) throws IllegalArgumentException {
        if (method == null)
            throw new IllegalArgumentException("null method");
        if (this.requestLine == null) {
            this.requestLine = new RequestLine();
        }
        this.requestLine.setMethod(method);
        if (this.cSeqHeader != null) {
            this.cSeqHeader.setMethod(method);
        }
    }

    /**
     * Gets the method from the request line.
     * @return the method from the request line if the method exits and
     * null if the request line or the method does not exist.
     */
    public String getMethod() {
        if (requestLine == null)
            return null;
        else
            return requestLine.getMethod();
    }

    /**
     * Encodes the SIP Request as a string.
     *
     * @return an encoded String containing the encoded SIP Message.
     */

    public String encode() {
        String retval;
        if (requestLine != null) {
            this.setRequestLineDefaults();
            retval = requestLine.encode() + super.encode();
        } else
            retval = super.encode();
        return retval;
    }

    /**
     * Alias for encode above.
     * @return encoded string of object contents
     */
    public String toString() { return this.encode(); }

    /**
     * Makes a clone (deep copy) of this object.
     * You can use this if you
     * want to modify a request while preserving the original
     *
     * @return a deep copy of this object.
     */

    public Object clone() {

        Request retval = (Request) super.clone();
        if (this.requestLine != null) {
            retval.requestLine = (RequestLine) this.requestLine.clone();
            retval.setRequestLineDefaults();
        }
        return retval;
    }

    /**
     * Compares for equality.
     *
     * @param other object to compare ourselves with.
     * @return true if objects match
     */
    public boolean equals(Object other) {
        if (! this.getClass().equals(other.getClass()))
            return false;
        Request that = (Request) other;

        boolean retval = requestLine.equals(that.requestLine) &&
                super.equals(other);

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION && !retval) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "this ... >>>>" + encode());
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "other ... >>>>" + that.encode());
        }

        return retval;
    }

    /**
     * Encodes this into a byte array.
     * This is used when the body has been set as a binary array
     * and you want to encode the body as a byte array for transmission.
     *
     * @return a byte array containing the Request encoded as a byte
     * array.
     */
    public byte[] encodeAsBytes() {
        byte[] rlbytes = null;
        if (requestLine != null) {
            try {
                rlbytes = requestLine.encode().getBytes("UTF-8");
            } catch (UnsupportedEncodingException ex) {
                InternalErrorHandler.handleException(ex);
            }
        }
        byte[] superbytes = super.encodeAsBytes();
        byte[] retval = new byte[rlbytes.length + superbytes.length];
        int i = 0;
        System.arraycopy(rlbytes, 0, retval, 0, rlbytes.length);
        System.arraycopy
                (superbytes, 0, retval, rlbytes.length, superbytes.length);
        return retval;
    }

    /**
     * Creates a default Response message for this request. Note
     * You must add the necessary tags to outgoing responses if need
     * be. For efficiency, this method does not clone the incoming
     * request. If you want to modify the outgoing response, be sure
     * to clone the incoming request as the headers are shared and
     * any modification to the headers of the outgoing response will
     * result in a modification of the incoming request.
     * Tag fields are just copied from the incoming request.
     * Contact headers are removed from the incoming request.
     * Added by Jeff Keyser.
     *
     * @param statusCode Status code for the response.
     * Reason phrase is generated.
     *
     * @return A Response with the status and reason supplied, and a copy
     *of all the original headers from this request.
     */
    public Response createResponse(int statusCode) {
        String reasonPhrase = Response.getReasonPhrase(statusCode);
        return createResponse(statusCode, reasonPhrase);
    }

    /**
     * Creates a default Response message for this request. Note
     * You must add the necessary tags to outgoing responses if need
     * be. For efficiency, this method does not clone the incoming
     * request. If you want to modify the outgoing response, be sure
     * to clone the incoming request as the headers are shared and
     * any modification to the headers of the outgoing response will
     * result in a modification of the incoming request.
     * Tag fields are just copied from the incoming request.
     * Contact headers are removed from the incoming request.
     * Added by Jeff Keyser. Route headers are not added to the
     * response.
     *
     * @param statusCode Status code for the response.
     * @param reasonPhrase Reason phrase for this response.
     * @return A Response with the status and reason supplied.
     * @throws IllegalArgumentException if some argument has an invalid value.
     */
    public Response createResponse(int statusCode,
            String reasonPhrase)
            throws IllegalArgumentException {
        Response newResponse;
        Enumeration headerIterator;
        Header nextHeader;

        newResponse = new Response();
        try {
            newResponse.setStatusCode(statusCode);
        } catch (ParseException ex) {
            throw new IllegalArgumentException("Bad code " + statusCode);
        }

        if (reasonPhrase != null) {
            newResponse.setReasonPhrase(reasonPhrase);
        } else {
            newResponse.setReasonPhrase(Response.getReasonPhrase(statusCode));
        }

        headerIterator = super.getHeaders();

        // Time stamp header should be stamped with delay but
        // we dont support this.
        while (headerIterator.hasMoreElements()) {
            nextHeader = (Header)headerIterator.nextElement();
            if (nextHeader instanceof FromHeader ||
                    nextHeader instanceof ToHeader ||
                    nextHeader instanceof ViaList ||
                    nextHeader instanceof CallIdHeader ||
                    nextHeader instanceof RecordRouteList ||
                    nextHeader instanceof CSeqHeader ||
                    // RFC 3265, 3.1.1 200-class responses to SUBSCRIBE
                    // requests also MUST contain an "Expires" header.
                    nextHeader instanceof ExpiresHeader ||
                    Utils.equalsIgnoreCase(nextHeader.getName(),
                        Header.TIMESTAMP)) {
                try {
                    newResponse.attachHeader(nextHeader, false);
                } catch (SipException ex) {
                    if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                        Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                            "Request.createResponse(): can't attach header '" +
                                nextHeader.getHeaderName() + "'.");
                        ex.printStackTrace();
                    }
                }
            } else if (Utils.equalsIgnoreCase(nextHeader.getName(),
                        Header.REQUIRE)) {
                /*
                 * RFC3262, SECTION 3
                 * If the next header contains "Require" header with option
                 * tag as "100rel", we should add this header and also include 
                 * RSeq header field
                 */
                boolean isReliableProvResponse = Header.isReliableTagPresent(
                        nextHeader.getHeaderValue());
                
                if (isReliableProvResponse) {
                    try {
                        newResponse.attachHeader(nextHeader, true);
                    } catch (SipException ex) {
                        if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                            Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                                "Request.createResponse(): can't attach header" 
                                    +  nextHeader.getHeaderName() + "'.");
                            // ex.printStackTrace();
                        }
                    }
                }
                
            }
        }

        // RFC 3903, p. 5:
        // The Record-Route header field has no meaning in PUBLISH
        // requests or responses, and MUST be ignored if present.
        //
        // RFC 3261, p. 63:
        // Registrars MUST ignore the Record-Route header field if it is
        // included in a REGISTER request. Registrars MUST NOT include a
        // Record-Route header field in any response to a REGISTER request.
        String method = getMethod();
        if (method.equals(Request.PUBLISH) ||
                method.equals(Request.REGISTER)) {
            newResponse.removeHeader(Header.RECORD_ROUTE);
        }

        return newResponse;
    }

    /**
     * Creates a default SIPResquest message that would cancel
     * this request. Note that tag assignment and removal of
     * is left to the caller (we use whatever tags are present in the
     * original request). Acknowledgement: Added by Jeff Keyser.
     *
     * @return A CANCEL Request with a copy all the original headers
     * from this request except for Require, ProxyRequire.
     * @throws SipException if the request can't be created.
     */
    public Request createCancelRequest() throws SipException {
        Request newRequest;
        Enumeration headerIterator;
        Header nextHeader;

        newRequest = new Request();

        // JSR180: Request-URI  // copy from original request
        RequestLine cancelRequestLine = 
            (RequestLine)this.getRequestLine().clone();
        cancelRequestLine.setMethod(CANCEL);
        newRequest.setRequestLine(cancelRequestLine);
        newRequest.setMethod(CANCEL);

        // JSR180: To           // copy from original request
        ToHeader toHeader = this.getTo();
        if (toHeader != null) {
            newRequest.setHeader(toHeader);
        }

        // JSR180: From         // copy from original request
        FromHeader fromHeader = this.getFromHeader();
        if (fromHeader != null) {
            newRequest.setHeader(fromHeader);
        }

        // JSR180: CSeq         // same value for the sequence
        // number as was present in the original request, but
        // the method parameter MUST be equal to "CANCEL"
        CSeqHeader cseqHeader = (CSeqHeader)this.getCSeqHeader().clone();
        if (cseqHeader != null) {
            cseqHeader.setMethod(CANCEL);
            newRequest.setHeader(cseqHeader);
        }

        // JSR180: Call-ID      // copy from original request
        CallIdHeader callIdHeader = this.getCallId();
        if (callIdHeader != null) {
            newRequest.setHeader(callIdHeader);
        }

        // JSR180: Via          // single value equal to the
        // top Via header field of the request being cancelled
        ViaHeader viaHeader = this.getTopmostVia();
        if (viaHeader != null) {
            newRequest.setHeader(viaHeader);
        }

        // JSR180: Route        // If the request being cancelled
        // contains a Route header field, the CANCEL request MUST
        // include that Route header field's values
        RouteList routeList = this.getRouteHeaders();
        if (routeList != null) {
            newRequest.setHeaders(routeList.getHeaders());
        }

        // JSR180: Max-Forwards (TBD)// header field serves to limit the
        // number of hops a request can transit on the way to its destination.
        // Current version: copy from original request
        MaxForwardsHeader mfHeader =
            (MaxForwardsHeader)getHeader(Header.MAX_FORWARDS);
        if (mfHeader != null) {
            newRequest.setHeader(mfHeader);
        }

        return newRequest;
    }

    /**
     * Creates a default ACK Request message for this original request.
     * Note that the defaultACK Request does not include the
     * content of the original Request. If responseToHeader
     * is null then the toHeader of this request is used to
     * construct the ACK. Note that tag fields are just copied
     * from the original SIP Request. Added by Jeff Keyser.
     *
     * @param responseToHeader To header to use for this request.
     * @return A Request with an ACK method.
     * @throws SipException if the request can't be created.
     */
    public Request createAckRequest(ToHeader responseToHeader)
            throws SipException {
        Request newRequest;
        Enumeration headerIterator;
        Header nextHeader;

        newRequest = new Request();
        newRequest.setRequestLine
                ((RequestLine)this.requestLine.clone());
        newRequest.setMethod(ACK);
        headerIterator = getHeaders();
        while (headerIterator.hasMoreElements()) {
            nextHeader = (Header)headerIterator.nextElement();
            if (nextHeader.getHeaderName().equals
                    (Header.ROUTE)) {

                // Route header for ACK is assigned by the
                // Dialog if necessary.
                continue;
            } else if (nextHeader.getHeaderName().equals
                    (Header.PROXY_AUTHORIZATION)) {
                // Remove proxy auth header.
                // Assigned by the Dialog if necessary.
                continue;
            } else if (nextHeader instanceof ContentLengthHeader) {
                // Adding content is responsibility of user.
                nextHeader = (Header) nextHeader.clone();
                ((ContentLengthHeader)nextHeader).setContentLength(0);

            } else if (nextHeader instanceof ContentTypeHeader) {
                // Content type header is removed since
                // content length is 0. Bug fix from
                // Antonis Kyardas.
                continue;
            } else if (nextHeader instanceof CSeqHeader) {
                CSeqHeader cseq = (CSeqHeader) nextHeader.clone();
                cseq.setMethod(ACK);
                nextHeader = cseq;
            } else if (nextHeader instanceof ToHeader) {
                if (responseToHeader != null) {
                    nextHeader = responseToHeader;
                } else {
                    nextHeader = (Header) nextHeader.clone();
                }
            } else {
                nextHeader = (Header) nextHeader.clone();
            }

            newRequest.attachHeader(nextHeader, false);
        }

        return newRequest;
    }

    /**
     * Creates a new default Request from the original request. Warning:
     * the newly created Request, shares the headers of
     * this request but we generate any new headers that we need to modify
     * so the original request is umodified. However, if you modify the
     * shared headers after this request is created, then the newly
     * created request will also be modified.
     * If you want to modify the original request
     * without affecting the returned Request
     * make sure you clone it before calling this method.
     * Following are the differences between the original request headers
     * and the generated request headers.
     * <ul>
     * <li>
     * Contact headers are not included in the newly created request.
     * Setting the appropriate sequence number is the responsibility of
     * the caller. </li>
     * <li> RouteList is not copied for ACK and CANCEL </li>
     * <li> Note that we DO NOT copy the body of the
     * argument into the returned header. We do not copy the content
     * type header from the original request either. These have to be
     * added seperately and the content length has to be correctly set
     * if necessary the content length is set to 0 in the returned header.
     * </li>
     * <li>Contact List is not copied from the original request.</li>
     * <li>RecordRoute List is not included from original request. </li>
     * <li>Via header is not included from the original request. </li>
     * </ul>
     *
     * @param requestLine is the new request line.
     *
     * @param switchHeaders is a boolean flag that causes to and from
     * headers to switch (set this to true if you are the
     * server of the transaction and are generating a BYE
     * request). If the headers are switched, we generate
     * new FromHeader and To headers otherwise we just use the
     * incoming headers.
     *
     * @return a new Default SIP Request which has the requestLine specified.
     *
     * @throws SipException if the request can't be created.
     */
    public Request createRequest(RequestLine requestLine,
            boolean switchHeaders) throws SipException {
        Request newRequest = new Request();
        newRequest.requestLine = requestLine;
        Enumeration headerIterator = this.getHeaders();
        while (headerIterator.hasMoreElements()) {
            Header nextHeader =
                    (Header)headerIterator.nextElement();
            // For BYE and cancel set the CSeqHeader header to the
            // appropriate method.
            if (nextHeader instanceof CSeqHeader) {
                CSeqHeader newCseq = (CSeqHeader) nextHeader.clone();
                nextHeader = newCseq;
                newCseq.setMethod(requestLine.getMethod());
            } else if (requestLine.getMethod().equals(ACK) &&
                    nextHeader instanceof ContactList) {
                // ACKS never get Contact headers.
                continue;
            } else if (nextHeader instanceof ViaList) {
                ViaHeader via = (ViaHeader)
                (((ViaList)nextHeader).getFirst().clone());
                via.removeParameter(SIPConstants.GENERAL_BRANCH);
                nextHeader = via;
                // Cancel and ACK preserve the branch ID.
            } else if (nextHeader instanceof RouteList) {
                continue; // Route is kept by dialog.
            } else if (nextHeader instanceof RecordRouteList) {
                continue; // RR is added by the caller.
            } else if (nextHeader instanceof ContactList) {
                continue;
            } else if (nextHeader instanceof ToHeader) {
                ToHeader to = (ToHeader) nextHeader;
                if (switchHeaders) {
                    nextHeader = new FromHeader(to);
                    ((FromHeader) nextHeader).removeTag();
                } else {
                    nextHeader = (Header) to.clone();
                    ((ToHeader) nextHeader).removeTag();
                }
            } else if (nextHeader instanceof FromHeader) {
                FromHeader from = (FromHeader) nextHeader;
                if (switchHeaders) {
                    nextHeader = new ToHeader(from);
                    ((ToHeader) nextHeader).removeTag();
                } else {
                    nextHeader = (Header) from.clone();
                    ((FromHeader) nextHeader).removeTag();
                }
            } else if (nextHeader instanceof ContentLengthHeader) {
                ContentLengthHeader cl =
                        (ContentLengthHeader)
                        nextHeader.clone();
                cl.setContentLength(0);
                nextHeader = cl;
            } else if (nextHeader instanceof ContentTypeHeader) {
                continue;
            } else if (nextHeader instanceof MaxForwardsHeader) {
                // Header is regenerated if the request is to be switched
                if (switchHeaders) {
                    MaxForwardsHeader mf  =
                            (MaxForwardsHeader)
                            nextHeader.clone();
                    mf.setMaxForwards(70);
                    nextHeader = mf;
                }
            } else if (!(nextHeader instanceof CallIdHeader) &&
                    !(nextHeader instanceof MaxForwardsHeader)) {
                // Route is kept by dialog.
                // RR is added by the caller.
                // Contact is added by the Caller
                // Any extension headers must be added
                // by the caller.
                continue;
            }

            newRequest.attachHeader(nextHeader, false);

        }
        return newRequest;

    }

    /**
     * Creates a BYE request from this request.
     *
     * @param switchHeaders is a boolean flag that causes from and
     * isServerTransaction to headers to be swapped. Set this
     * to true if you are the server of the dialog and are generating
     * a BYE request for the dialog.
     * @return a new default BYE request.
     * @throws SipException if the request can't be created.
     */
    public Request createBYERequest(boolean switchHeaders) throws SipException {
        RequestLine rl = (RequestLine) requestLine.clone();
        rl.setMethod(BYE);
        return createRequest(rl, switchHeaders);
    }

    /**
     * Creates an ACK request from this request. This is suitable for
     * generating an ACK for an INVITE client transaction.
     *
     * @return an ACK request that is generated from this request.
     * @throws SipException if the request can't be created.
     */
    public Request createACKRequest() throws SipException {
        RequestLine rl = (RequestLine) requestLine.clone();
        rl.setMethod(ACK);
        return createRequest(rl, false);
    }

    /**
     * Gets the host from the topmost via header.
     *
     * @return the string representation of the host from the topmost via
     * header.
     */
    public String getViaHost() {
        ViaHeader via = (ViaHeader) this.getViaHeaders().getFirst();
        return via.getHost();

    }

    /**
     * Gets the port from the topmost via header.
     *
     * @return the port from the topmost via header (5060 if there is
     * no port indicated).
     */
    public int getViaPort() {
        ViaHeader via = (ViaHeader) this.getViaHeaders().getFirst();
        if (via.hasPort())
            return via.getPort();
        else
            return 5060;
    }

    /**
     * Gets the first line encoded.
     *
     * @return a string containing the encoded request line.
     */
    public String getFirstLine() {
        if (requestLine == null)
            return null;
        else
            return this.requestLine.encode();
    }

    /**
     * Sets the sip version.
     *
     * @param sipVersion the sip version to set.
     */

    public void setSIPVersion(String sipVersion)
    throws ParseException {
        if (sipVersion == null || !sipVersion.equals("SIP/2.0"))
            throw new ParseException("sipVersion", 0);
        this.requestLine.setSIPVersion(sipVersion);
    }

    /**
     * Gets the SIP version.
     *
     * @return the SIP version from the request line.
     */
    public String getSIPVersion() {
        return this.requestLine.getSipVersion();
    }

    /**
     * Gets the transaction pointer.
     * @return the transaction pointer
     */
    public Object getTransaction() {
        // Return an opaque pointer to the transaction object.
        // This is for consistency checking and quick lookup.
        return this.transactionPointer;
    }

    /**
     * Sets the transaction pointer.
     * @param transaction thenew transaction pointer
     */
    public void setTransaction(Object transaction) {
        this.transactionPointer = transaction;
    }

    /**
     * Gets the Accept-Contact header (null if one does not exist).
     * @return Accept-Contact header
     */
    public AcceptContactHeader getAcceptContact() {
        return (AcceptContactHeader) getHeader(Header.ACCEPT_CONTACT);
    }

}