content: /*
   * Conditions Of Use 
   * 
   * This software was developed by employees of the National Institute of
   * Standards and Technology (NIST), an agency of the Federal Government.
   * Pursuant to title 15 Untied States Code Section 105, works of NIST
   * employees are not subject to copyright protection in the United States
   * and are considered to be in the public domain.  As a result, a formal
   * license is not needed to use the software.
   * 
   * This software is provided by NIST as a service and is expressly
   * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
   * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
   * AND DATA ACCURACY.  NIST does not warrant or make any representations
   * regarding the use of the software or the results thereof, including but
   * not limited to the correctness, accuracy, reliability or usefulness of
   * the software.
   * 
   * Permission to use this software is contingent upon your acceptance
   * of the terms of this agreement
   *  
   * .
   * 
   */
  /*******************************************************************************
   * Product of NIST/ITL Advanced Networking Technologies Division (ANTD)         *
   *******************************************************************************/
  package gov2.nist.javax2.sip.message;
  
  
  import java.util.HashSet;
  import java.util.Hashtable;
  import java.util.LinkedList;
  import java.util.Set;
  import java.io.UnsupportedEncodingException;
  import java.util.Iterator;
  
  import java.text.ParseException;
  
  import javax2.sip.*;
  import javax2.sip.address.URI;
  import javax2.sip.header.*;
  import javax2.sip.message.*;
  
  import gov2.nist.core.*;
  import gov2.nist.javax2.sip.address.*;
  import gov2.nist.javax2.sip.header.*;
  import gov2.nist.javax2.sip.stack.SIPTransactionStack;
  
  /*
   * Acknowledgements: Mark Bednarek made a few fixes to this code. Jeff Keyser added two methods
   * that create responses and generate cancel requests from incoming orignial requests without the
   * additional overhead of encoding and decoding messages. Bruno Konik noticed an extraneous
   * newline added to the end of the buffer when encoding it. Incorporates a bug report from Andreas
   * Bystrom. Szabo Barna noticed a contact in a cancel request - this is a pointless header for
   * cancel. Antonis Kyardis contributed bug fixes. Jeroen van Bemmel noted that method names are
   * case sensitive, should use equals() in getting CannonicalName
   * 
   */
  
  /**
   * The SIP Request structure.
   * 
   * @version 1.2 $Revision: 1.52 $ $Date: 2009/12/16 14:58:40 $
   * @since 1.1
   * 
   * @author M. Ranganathan <br/>
   * 
   * 
   * 
   */
  
  public final class SIPRequest extends SIPMessage implements javax2.sip.message.Request, RequestExt {
  
      private static final long serialVersionUID = 3360720013577322927L;
  
      private static final String DEFAULT_USER = "ip";
  
      private static final String DEFAULT_TRANSPORT = "udp";
  
      private transient Object transactionPointer;
  
      private RequestLine requestLine;
  
      private transient Object messageChannel;
      
      
  
      private transient Object inviteTransaction; // The original invite request for a
      // given cancel request
  
      /**
       * Set of target refresh methods, currently: INVITE, UPDATE, SUBSCRIBE, NOTIFY, REFER
       * 
       * A target refresh request and its response MUST have a Contact
       */
      private static final Set<String> targetRefreshMethods = new HashSet<String>();
  
      /*
       * A table that maps a name string to its cannonical constant. This is used to speed up
       * parsing of messages .equals reduces to == if we use the constant value.
       */
      private static final Hashtable<String, String> nameTable = new Hashtable<String, String>();
  
      private static void putName(String name) {
          nameTable.put(name, name);
      }
  
      static {
          targetRefreshMethods.add(Request.INVITE);
          targetRefreshMethods.add(Request.UPDATE);
          targetRefreshMethods.add(Request.SUBSCRIBE);
          targetRefreshMethods.add(Request.NOTIFY);
          targetRefreshMethods.add(Request.REFER);
  
          putName(Request.INVITE);
          putName(Request.BYE);
          putName(Request.CANCEL);
          putName(Request.ACK);
          putName(Request.PRACK);
          putName(Request.INFO);
          putName(Request.MESSAGE);
          putName(Request.NOTIFY);
          putName(Request.OPTIONS);
          putName(Request.PRACK);
          putName(Request.PUBLISH);
          putName(Request.REFER);
          putName(Request.REGISTER);
          putName(Request.SUBSCRIBE);
          putName(Request.UPDATE);
  
      }
  
      /**
       * @return true iff the method is a target refresh
       */
      public static boolean isTargetRefresh(String ucaseMethod) {
          return targetRefreshMethods.contains(ucaseMethod);
      }
  
      /**
       * @return true iff the method is a dialog creating method
       */
      public static boolean isDialogCreating(String ucaseMethod) {
          return SIPTransactionStack.isDialogCreated(ucaseMethod);
      }
      
      /**
       * Set to standard constants to speed up processing. this makes equals comparisons run much
       * faster in the stack because then it is just identity comparision. Character by char
       * comparison is not required. The method returns the String CONSTANT corresponding to the
       * String name.
       * 
       */
      public static String getCannonicalName(String method) {
  
          if (nameTable.containsKey(method))
              return (String) nameTable.get(method);
          else
              return method;
      }
  
      /**
       * Get the Request Line of the SIPRequest.
       * 
       * @return the request line of the SIP Request.
       */
  
      public RequestLine getRequestLine() {
          return requestLine;
      }
  
      /**
       * Set 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 SIPRequest() {
          super();
      }
  
      /**
       * Convert to a formatted string for pretty printing. Note that the encode method converts
       * this into a sip message that is suitable for transmission. Note hack here if you want to
       * convert the nice curly brackets into some grotesque XML tag.
       * 
       * @return a string which can be used to examine the message contents.
       * 
       */
      public String debugDump() {
          String superstring = super.debugDump();
          stringRepresentation = "";
          sprint(SIPRequest.class.getName());
          sprint("{");
          if (requestLine != null)
              sprint(requestLine.debugDump());
          sprint(superstring);
          sprint("}");
          return stringRepresentation;
      }
  
      /**
       * Check header for constraints. (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.
       */
      public void checkHeaders() throws ParseException {
          String prefix = "Missing a required header : ";
  
          /* Check for required headers */
  
          if (getCSeq() == null) {
              throw new ParseException(prefix + CSeqHeader.NAME, 0);
          }
          if (getTo() == null) {
              throw new ParseException(prefix + ToHeader.NAME, 0);
          }
  
          if (this.callIdHeader == null || this.callIdHeader.getCallId() == null
                  || callIdHeader.getCallId().equals("")) {
              throw new ParseException(prefix + CallIdHeader.NAME, 0);
          }
          if (getFrom() == null) {
              throw new ParseException(prefix + FromHeader.NAME, 0);
          }
          if (getViaHeaders() == null) {
              throw new ParseException(prefix + ViaHeader.NAME, 0);
          }
          if (getMaxForwards() == null) {
              throw new ParseException(prefix + MaxForwardsHeader.NAME, 0);
          }
  
          if (getTopmostVia() == null)
              throw new ParseException("No via header in request! ", 0);
  
          if (getMethod().equals(Request.NOTIFY)) {
              if (getHeader(SubscriptionStateHeader.NAME) == null)
                  throw new ParseException(prefix + SubscriptionStateHeader.NAME, 0);
  
              if (getHeader(EventHeader.NAME) == null)
                  throw new ParseException(prefix + EventHeader.NAME, 0);
  
          } else if (getMethod().equals(Request.PUBLISH)) {
              /*
               * For determining the type of the published event state, the EPA MUST include a
               * single Event header field in PUBLISH requests. The value of this header field
               * indicates the event package for which this request is publishing event state.
               */
              if (getHeader(EventHeader.NAME) == null)
                  throw new ParseException(prefix + EventHeader.NAME, 0);
          }
  
          /*
           * RFC 3261 8.1.1.8 The Contact header field MUST be present and contain exactly one SIP
           * or SIPS URI in any request that can result in the establishment of a dialog. For the
           * methods defined in this specification, that includes only the INVITE request. For these
           * requests, the scope of the Contact is global. That is, the Contact header field value
           * contains the URI at which the UA would like to receive requests, and this URI MUST be
           * valid even if used in subsequent requests outside of any dialogs.
           * 
           * If the Request-URI or top Route header field value contains a SIPS URI, the Contact
           * header field MUST contain a SIPS URI as well.
           */
          if (requestLine.getMethod().equals(Request.INVITE)
                  || requestLine.getMethod().equals(Request.SUBSCRIBE)
                  || requestLine.getMethod().equals(Request.REFER)) {
              if (this.getContactHeader() == null) {
                  // Make sure this is not a target refresh. If this is a target
                  // refresh its ok not to have a contact header. Otherwise
                  // contact header is mandatory.
                  if (this.getToTag() == null)
                      throw new ParseException(prefix + ContactHeader.NAME, 0);
              }
  
              if (requestLine.getUri() instanceof SipUri) {
                  String scheme = ((SipUri) requestLine.getUri()).getScheme();
                  if ("sips".equalsIgnoreCase(scheme)) {
                      SipUri sipUri = (SipUri) this.getContactHeader().getAddress().getURI();
                      if (!sipUri.getScheme().equals("sips")) {
                          throw new ParseException("Scheme for contact should be sips:" + sipUri, 0);
                      }
                  }
              }
          }
  
          /*
           * Contact header is mandatory for a SIP INVITE request.
           */
          if (this.getContactHeader() == null
                  && (this.getMethod().equals(Request.INVITE)
                          || this.getMethod().equals(Request.REFER) || this.getMethod().equals(
                          Request.SUBSCRIBE))) {
              throw new ParseException("Contact Header is Mandatory for a SIP INVITE", 0);
          }
  
          if (requestLine != null && requestLine.getMethod() != null
                  && getCSeq().getMethod() != null
                  && requestLine.getMethod().compareTo(getCSeq().getMethod()) != 0) {
              throw new ParseException("CSEQ method mismatch with  Request-Line ", 0);
  
          }
  
      }
  
      /**
       * Set 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;
          GenericURI u = requestLine.getUri();
          if (u == null)
              return;
          if (method.compareTo(Request.REGISTER) == 0 || method.compareTo(Request.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) {
              CSeq cseq = (CSeq) this.getCSeq();
              if (cseq != null) {
                  method = getCannonicalName(cseq.getMethod());
                  requestLine.setMethod(method);
              }
          }
      }
  
      /**
       * A conveniance function to access the Request URI.
       * 
       * @return the requestURI if it exists.
       */
      public javax2.sip.address.URI getRequestURI() {
          if (this.requestLine == null)
              return null;
          else
              return (javax2.sip.address.URI) 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 ( uri == null ) {
              throw new NullPointerException("Null request URI");
          }
          if (this.requestLine == null) {
              this.requestLine = new RequestLine();
          }
          this.requestLine.setUri((GenericURI) uri);
          this.nullRequest = false;
      }
  
      /**
       * Set the method.
       * 
       * @param method is the method to set.
       * @throws IllegalArgumentException if the method is null
       */
      public void setMethod(String method) {
          if (method == null)
              throw new IllegalArgumentException("null method");
          if (this.requestLine == null) {
              this.requestLine = new RequestLine();
          }
  
          // Set to standard constants to speed up processing.
          // this makes equals compares run much faster in the
          // stack because then it is just identity comparision
  
          String meth = getCannonicalName(method);
          this.requestLine.setMethod(meth);
  
          if (this.cSeqHeader != null) {
              try {
                  this.cSeqHeader.setMethod(meth);
              } catch (ParseException e) {
              }
          }
      }
  
      /**
       * Get 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();
      }
  
      /**
       * Encode 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 if (this.isNullRequest()) {
              retval = "\r\n\r\n";
          } else {       
              retval = super.encode();
          }
          return retval;
      }
  
      /**
       * Encode only the headers and not the content.
       */
      public String encodeMessage() {
          String retval;
          if (requestLine != null) {
              this.setRequestLineDefaults();
              retval = requestLine.encode() + super.encodeSIPHeaders();
          } else if (this.isNullRequest()) {
              retval = "\r\n\r\n";
          } else
              retval = super.encodeSIPHeaders();
          return retval;
  
      }
  
      /**
       * ALias for encode above.
       */
      public String toString() {
          return this.encode();
      }
  
      /**
       * Make 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() {
          SIPRequest retval = (SIPRequest) super.clone();
          // Do not copy over the tx pointer -- this is only for internal
          // tracking.
          retval.transactionPointer = null;
          if (this.requestLine != null)
              retval.requestLine = (RequestLine) this.requestLine.clone();
  
          return retval;
      }
  
      /**
       * Compare for equality.
       * 
       * @param other object to compare ourselves with.
       */
      public boolean equals(Object other) {
          if (!this.getClass().equals(other.getClass()))
              return false;
          SIPRequest that = (SIPRequest) other;
  
          return requestLine.equals(that.requestLine) && super.equals(other);
      }
  
      /**
       * Get the message as a linked list of strings. Use this if you want to iterate through the
       * message.
       * 
       * @return a linked list containing the request line and headers encoded as strings.
       */
      public LinkedList getMessageAsEncodedStrings() {
          LinkedList retval = super.getMessageAsEncodedStrings();
          if (requestLine != null) {
              this.setRequestLineDefaults();
              retval.addFirst(requestLine.encode());
          }
          return retval;
  
      }
  
      /**
       * Match with a template. You can use this if you want to match incoming messages with a
       * pattern and do something when you find a match. This is useful for building filters/pattern
       * matching responders etc.
       * 
       * @param matchObj object to match ourselves with (null matches wildcard)
       * 
       */
      public boolean match(Object matchObj) {
          if (matchObj == null)
              return true;
          else if (!matchObj.getClass().equals(this.getClass()))
              return false;
          else if (matchObj == this)
              return true;
          SIPRequest that = (SIPRequest) matchObj;
          RequestLine rline = that.requestLine;
          if (this.requestLine == null && rline != null)
              return false;
          else if (this.requestLine == rline)
              return super.match(matchObj);
          return requestLine.match(that.requestLine) && super.match(matchObj);
  
      }
  
      /**
       * Get a dialog identifier. Generates a string that can be used as a dialog identifier.
       * 
       * @param isServer is set to true if this is the UAS and set to false if this is the UAC
       */
      public String getDialogId(boolean isServer) {
          CallID cid = (CallID) this.getCallId();
          StringBuffer retval = new StringBuffer(cid.getCallId());
          From from = (From) this.getFrom();
          To to = (To) this.getTo();
          if (!isServer) {
              // retval.append(COLON).append(from.getUserAtHostPort());
              if (from.getTag() != null) {
                  retval.append(COLON);
                  retval.append(from.getTag());
              }
              // retval.append(COLON).append(to.getUserAtHostPort());
              if (to.getTag() != null) {
                  retval.append(COLON);
                  retval.append(to.getTag());
              }
          } else {
              // retval.append(COLON).append(to.getUserAtHostPort());
              if (to.getTag() != null) {
                  retval.append(COLON);
                  retval.append(to.getTag());
              }
              // retval.append(COLON).append(from.getUserAtHostPort());
              if (from.getTag() != null) {
                  retval.append(COLON);
                  retval.append(from.getTag());
              }
          }
          return retval.toString().toLowerCase();
  
      }
  
      /**
       * Get a dialog id given the remote tag.
       */
      public String getDialogId(boolean isServer, String toTag) {
          From from = (From) this.getFrom();
          CallID cid = (CallID) this.getCallId();
          StringBuffer retval = new StringBuffer(cid.getCallId());
          if (!isServer) {
              // retval.append(COLON).append(from.getUserAtHostPort());
              if (from.getTag() != null) {
                  retval.append(COLON);
                  retval.append(from.getTag());
              }
              // retval.append(COLON).append(to.getUserAtHostPort());
              if (toTag != null) {
                  retval.append(COLON);
                  retval.append(toTag);
              }
          } else {
              // retval.append(COLON).append(to.getUserAtHostPort());
              if (toTag != null) {
                  retval.append(COLON);
                  retval.append(toTag);
              }
              // retval.append(COLON).append(from.getUserAtHostPort());
              if (from.getTag() != null) {
                  retval.append(COLON);
                  retval.append(from.getTag());
              }
          }
          return retval.toString().toLowerCase();
      }
  
      /**
       * Encode 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 SIPRequest encoded as a byte array.
       */
  
      public byte[] encodeAsBytes(String transport) {
          if (this.isNullRequest()) {
              // Encoding a null message for keepalive.
              return "\r\n\r\n".getBytes();
          } else if ( this.requestLine == null ) {
              return new byte[0];
          }
  
          byte[] rlbytes = null;
          if (requestLine != null) {
              try {
                  rlbytes = requestLine.encode().getBytes("UTF-8");
              } catch (UnsupportedEncodingException ex) {
                  InternalErrorHandler.handleException(ex);
              }
          }
          byte[] superbytes = super.encodeAsBytes(transport);
          byte[] retval = new byte[rlbytes.length + superbytes.length];
          System.arraycopy(rlbytes, 0, retval, 0, rlbytes.length);
          System.arraycopy(superbytes, 0, retval, rlbytes.length, superbytes.length);
          return retval;
      }
  
      /**
       * Creates a default SIPResponse 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 SIPResponse with the status and reason supplied, and a copy of all the original
       *         headers from this request.
       */
  
      public SIPResponse createResponse(int statusCode) {
  
          String reasonPhrase = SIPResponse.getReasonPhrase(statusCode);
          return this.createResponse(statusCode, reasonPhrase);
  
      }
  
      /**
       * Creates a default SIPResponse 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 SIPResponse with the status and reason supplied, and a copy of all the original
       *         headers from this request except the ones that are not supposed to be part of the
       *         response .
       */
  
      public SIPResponse createResponse(int statusCode, String reasonPhrase) {
          SIPResponse newResponse;
          Iterator headerIterator;
          SIPHeader nextHeader;
  
          newResponse = new SIPResponse();
          try {
              newResponse.setStatusCode(statusCode);
          } catch (ParseException ex) {
              throw new IllegalArgumentException("Bad code " + statusCode);
          }
          if (reasonPhrase != null)
              newResponse.setReasonPhrase(reasonPhrase);
          else
              newResponse.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode));
          headerIterator = getHeaders();
          while (headerIterator.hasNext()) {
              nextHeader = (SIPHeader) headerIterator.next();
              if (nextHeader instanceof From
                      || nextHeader instanceof To
                      || nextHeader instanceof ViaList
                      || nextHeader instanceof CallID
                      || (nextHeader instanceof RecordRouteList && mustCopyRR(statusCode))
                      || nextHeader instanceof CSeq
                      // We just copy TimeStamp for all headers (not just 100).
                      || nextHeader instanceof TimeStamp) {
  
                  try {
  
                      newResponse.attachHeader((SIPHeader) nextHeader.clone(), false);
                  } catch (SIPDuplicateHeaderException e) {
                      e.printStackTrace();
                  }
              }
          }
          if (MessageFactoryImpl.getDefaultServerHeader() != null) {
              newResponse.setHeader(MessageFactoryImpl.getDefaultServerHeader());
  
          }
          if (newResponse.getStatusCode() == 100) {
              // Trying is never supposed to have the tag parameter set.
              newResponse.getTo().removeParameter("tag");
  
          }
          ServerHeader server = MessageFactoryImpl.getDefaultServerHeader();
          if (server != null) {
              newResponse.setHeader(server);
          }
          return newResponse;
      }
  
      // Helper method for createResponse, to avoid copying Record-Route unless needed
      private final boolean mustCopyRR( int code ) {
      	// Only for 1xx-2xx, not for 100 or errors
      	if ( code>100 && code<300 ) {
      		return isDialogCreating( this.getMethod() ) && getToTag() == null;
      	} else return false;
      }
      
      /**
       * 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).
       * 
       * @return A CANCEL SIPRequest constructed according to RFC3261 section 9.1
       * 
       * @throws SipException
       * @throws ParseException
       */
      public SIPRequest createCancelRequest() throws SipException {
  
          // see RFC3261 9.1
  
          // A CANCEL request SHOULD NOT be sent to cancel a request other than
          // INVITE
  
          if (!this.getMethod().equals(Request.INVITE))
              throw new SipException("Attempt to create CANCEL for " + this.getMethod());
  
          /*
           * 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).
           */
          SIPRequest cancel = new SIPRequest();
          cancel.setRequestLine((RequestLine) this.requestLine.clone());
          cancel.setMethod(Request.CANCEL);
          cancel.setHeader((Header) this.callIdHeader.clone());
          cancel.setHeader((Header) this.toHeader.clone());
          cancel.setHeader((Header) cSeqHeader.clone());
          try {
              cancel.getCSeq().setMethod(Request.CANCEL);
          } catch (ParseException e) {
              e.printStackTrace(); // should not happen
          }
          cancel.setHeader((Header) this.fromHeader.clone());
  
          cancel.addFirst((Header) this.getTopmostVia().clone());
          cancel.setHeader((Header) this.maxForwardsHeader.clone());
  
          /*
           * If the request being cancelled contains a Route header field, the CANCEL request MUST
           * include that Route header field's values.
           */
          if (this.getRouteHeaders() != null) {
              cancel.setHeader((SIPHeaderList< ? >) this.getRouteHeaders().clone());
          }
          if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
              cancel.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader());
  
          }
          return cancel;
      }
  
      /**
       * Creates a default ACK SIPRequest message for this original request. Note that the
       * defaultACK SIPRequest does not include the content of the original SIPRequest. 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 SIPRequest with an ACK method.
       */
      public SIPRequest createAckRequest(To responseToHeader) {
          SIPRequest newRequest;
          Iterator headerIterator;
          SIPHeader nextHeader;
  
          newRequest = new SIPRequest();
          newRequest.setRequestLine((RequestLine) this.requestLine.clone());
          newRequest.setMethod(Request.ACK);
          headerIterator = getHeaders();
          while (headerIterator.hasNext()) {
              nextHeader = (SIPHeader) headerIterator.next();
              if (nextHeader instanceof RouteList) {
                  // Ack and cancel do not get ROUTE headers.
                  // Route header for ACK is assigned by the
                  // Dialog if necessary.
                  continue;
              } else if (nextHeader instanceof ProxyAuthorization) {
                  // Remove proxy auth header.
                  // Assigned by the Dialog if necessary.
                  continue;
              } else if (nextHeader instanceof ContentLength) {
                  // Adding content is responsibility of user.
                  nextHeader = (SIPHeader) nextHeader.clone();
                  try {
                      ((ContentLength) nextHeader).setContentLength(0);
                  } catch (InvalidArgumentException e) {
                  }
              } else if (nextHeader instanceof ContentType) {
                  // Content type header is removed since
                  // content length is 0.
                  continue;
              } else if (nextHeader instanceof CSeq) {
                  // The CSeq header field in the
                  // ACK MUST contain the same value for the
                  // sequence number as was present in the
                  // original request, but the method parameter
                  // MUST be equal to "ACK".
                  CSeq cseq = (CSeq) nextHeader.clone();
                  try {
                      cseq.setMethod(Request.ACK);
                  } catch (ParseException e) {
                  }
                  nextHeader = cseq;
              } else if (nextHeader instanceof To) {
                  if (responseToHeader != null) {
                      nextHeader = responseToHeader;
                  } else {
                      nextHeader = (SIPHeader) nextHeader.clone();
                  }
              } else if (nextHeader instanceof ContactList || nextHeader instanceof Expires) {
                  // CONTACT header does not apply for ACK requests.
                  continue;
              } else if (nextHeader instanceof ViaList) {
                  // Bug reported by Gianluca Martinello
                  // The ACK MUST contain a single Via header field,
                  // and this MUST be equal to the top Via header
                  // field of the original
                  // request.
  
                  nextHeader = (SIPHeader) ((ViaList) nextHeader).getFirst().clone();
              } else {
                  nextHeader = (SIPHeader) nextHeader.clone();
              }
  
              try {
                  newRequest.attachHeader(nextHeader, false);
              } catch (SIPDuplicateHeaderException e) {
                  e.printStackTrace();
              }
          }
          if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
              newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader());
  
          }
          return newRequest;
      }
  
      /**
       * Creates an ACK for non-2xx responses according to RFC3261 17.1.1.3
       * 
       * @return A SIPRequest with an ACK method.
       * @throws SipException
       * @throws NullPointerException
       * @throws ParseException
       * 
       * @author jvb
       */
      public final SIPRequest createErrorAck(To responseToHeader) throws SipException,
              ParseException {
  
          /*
           * The ACK request constructed by the client transaction MUST contain values for the
           * Call-ID, From, and Request-URI that are equal to the values of those header fields in
           * the request passed to the transport by the client transaction (call this the "original
           * request"). The To header field in the ACK MUST equal the To header field in the
           * response being acknowledged, and therefore will usually differ from the To header field
           * in the original request by the addition of the tag parameter. The ACK MUST contain a
           * single Via header field, and this MUST be equal to the top Via header field of the
           * original request. The CSeq header field in the ACK MUST contain the same value for the
           * sequence number as was present in the original request, but the method parameter MUST
           * be equal to "ACK".
           */
          SIPRequest newRequest = new SIPRequest();
          newRequest.setRequestLine((RequestLine) this.requestLine.clone());
          newRequest.setMethod(Request.ACK);
          newRequest.setHeader((Header) this.callIdHeader.clone());
          newRequest.setHeader((Header) this.maxForwardsHeader.clone()); // ISSUE
          // 130
          // fix
          newRequest.setHeader((Header) this.fromHeader.clone());
          newRequest.setHeader((Header) responseToHeader.clone());
          newRequest.addFirst((Header) this.getTopmostVia().clone());
          newRequest.setHeader((Header) cSeqHeader.clone());
          newRequest.getCSeq().setMethod(Request.ACK);
  
          /*
           * If the INVITE request whose response is being acknowledged had Route header fields,
           * those header fields MUST appear in the ACK. This is to ensure that the ACK can be
           * routed properly through any downstream stateless proxies.
           */
          if (this.getRouteHeaders() != null) {
              newRequest.setHeader((SIPHeaderList) this.getRouteHeaders().clone());
          }
          if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
              newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader());
  
          }
          return newRequest;
      }
  
      /**
       * Create a new default SIPRequest from the original request. Warning: the newly created
       * SIPRequest, 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.
       * 
       * Only required headers are copied.
       * <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 From and To headers otherwise we just
       *        use the incoming headers.
       * 
       * @return a new Default SIP Request which has the requestLine specified.
       * 
       */
      public SIPRequest createSIPRequest(RequestLine requestLine, boolean switchHeaders) {
          SIPRequest newRequest = new SIPRequest();
          newRequest.requestLine = requestLine;
          Iterator<SIPHeader> headerIterator = this.getHeaders();
          while (headerIterator.hasNext()) {
              SIPHeader nextHeader = (SIPHeader) headerIterator.next();
              // For BYE and cancel set the CSeq header to the
              // appropriate method.
              if (nextHeader instanceof CSeq) {
                  CSeq newCseq = (CSeq) nextHeader.clone();
                  nextHeader = newCseq;
                  try {
                      newCseq.setMethod(requestLine.getMethod());
                  } catch (ParseException e) {
                  }
              } else if (nextHeader instanceof ViaList) {
                  Via via = (Via) (((ViaList) nextHeader).getFirst().clone());
                  via.removeParameter("branch");
                  nextHeader = via;
                  // Cancel and ACK preserve the branch ID.
              } else if (nextHeader instanceof To) {
                  To to = (To) nextHeader;
                  if (switchHeaders) {
                      nextHeader = new From(to);
                      ((From) nextHeader).removeTag();
                  } else {
                      nextHeader = (SIPHeader) to.clone();
                      ((To) nextHeader).removeTag();
                  }
              } else if (nextHeader instanceof From) {
                  From from = (From) nextHeader;
                  if (switchHeaders) {
                      nextHeader = new To(from);
                      ((To) nextHeader).removeTag();
                  } else {
                      nextHeader = (SIPHeader) from.clone();
                      ((From) nextHeader).removeTag();
                  }
              } else if (nextHeader instanceof ContentLength) {
                  ContentLength cl = (ContentLength) nextHeader.clone();
                  try {
                      cl.setContentLength(0);
                  } catch (InvalidArgumentException e) {
                  }
                  nextHeader = cl;
              } else if (!(nextHeader instanceof CallID) && !(nextHeader instanceof MaxForwards)) {
                  // 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;
              }
              try {
                  newRequest.attachHeader(nextHeader, false);
              } catch (SIPDuplicateHeaderException e) {
                  e.printStackTrace();
              }
          }
          if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) {
              newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader());
  
          }
          return newRequest;
  
      }
  
      /**
       * Create 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.
       */
      public SIPRequest createBYERequest(boolean switchHeaders) {
          RequestLine requestLine = (RequestLine) this.requestLine.clone();
          requestLine.setMethod("BYE");
          return this.createSIPRequest(requestLine, switchHeaders);
      }
  
      /**
       * Create 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.
       */
      public SIPRequest createACKRequest() {
          RequestLine requestLine = (RequestLine) this.requestLine.clone();
          requestLine.setMethod(Request.ACK);
          return this.createSIPRequest(requestLine, false);
      }
  
      /**
       * Get the host from the topmost via header.
       * 
       * @return the string representation of the host from the topmost via header.
       */
      public String getViaHost() {
          Via via = (Via) this.getViaHeaders().getFirst();
          return via.getHost();
  
      }
  
      /**
       * Get 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() {
          Via via = (Via) this.getViaHeaders().getFirst();
          if (via.hasPort())
              return via.getPort();
          else
              return 5060;
      }
  
      /**
       * Get 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();
      }
  
      /**
       * Set the sip version.
       * 
       * @param sipVersion the sip version to set.
       */
      public void setSIPVersion(String sipVersion) throws ParseException {
          if (sipVersion == null || !sipVersion.equalsIgnoreCase("SIP/2.0"))
              throw new ParseException("sipVersion", 0);
          this.requestLine.setSipVersion(sipVersion);
      }
  
      /**
       * Get the SIP version.
       * 
       * @return the SIP version from the request line.
       */
      public String getSIPVersion() {
          return this.requestLine.getSipVersion();
      }
  
      /**
       * Book keeping method to return the current tx for the request if one exists.
       * 
       * @return the assigned tx.
       */
      public Object getTransaction() {
          // Return an opaque pointer to the transaction object.
          // This is for consistency checking and quick lookup.
          return this.transactionPointer;
      }
  
      /**
       * Book keeping field to set the current tx for the request.
       * 
       * @param transaction
       */
      public void setTransaction(Object transaction) {
          this.transactionPointer = transaction;
      }
  
      /**
       * Book keeping method to get the messasge channel for the request.
       * 
       * @return the message channel for the request.
       */
  
      public Object getMessageChannel() {
          // return opaque ptr to the message chanel on
          // which the message was recieved. For consistency
          // checking and lookup.
          return this.messageChannel;
      }
  
      /**
       * Set the message channel for the request ( bookkeeping field ).
       * 
       * @param messageChannel
       */
  
      public void setMessageChannel(Object messageChannel) {
          this.messageChannel = messageChannel;
      }
  
      /**
       * Generates an Id for checking potentially merged requests.
       * 
       * @return String to check for merged requests
       */
      public String getMergeId() {
          /*
           * generate an identifier from the From tag, Call-ID, and CSeq
           */
          String fromTag = this.getFromTag();
          String cseq = this.cSeqHeader.toString();
          String callId = this.callIdHeader.getCallId();
          /* NOTE : The RFC does NOT specify you need to include a Request URI 
           * This is added here for the case of Back to Back User Agents.
           */
          String requestUri = this.getRequestURI().toString();
  
          if (fromTag != null) {
              return new StringBuffer().append(requestUri).append(":").append(fromTag).append(":").append(cseq).append(":")
                      .append(callId).toString();
          } else
              return null;
  
      }
  
      /**
       * @param inviteTransaction the inviteTransaction to set
       */
      public void setInviteTransaction(Object inviteTransaction) {
          this.inviteTransaction = inviteTransaction;
      }
  
      /**
       * @return the inviteTransaction
       */
      public Object getInviteTransaction() {
          return inviteTransaction;
      }
  
     
     
      
  
  }
  