/* * This file was derived from libdissipate, an open source SIP library. The original file * was modified on 1/23/2001. Please see * http://www.div8.net/dissipate for more information. * * Copyright (c) 2000 Billy Biggs * * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This library 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 Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. * */ /** * class SipMessage * * Representation of a single SIP message. SipMessage is used for creating and for parsing individual SIP * messages. It also contains a retransmission timer for use when sending the * message. * * This code has been generated using C2J++ * C2J++ is based on Chris Laffra's C2J (laffra@watson.ibm.com) * Read general disclaimer distributed with C2J++ before using this code * For information about C2J++, send mail to Ilya_Tilevich@ibi.com */ package org.mitre.jsip; import java.util.*; public class SipMessage { /** * SipMessage */ public SipMessage() { setDefaultVars(); } /** * Create SipMessage from parameter message * * @param parseinput */ public SipMessage(String parseinput) throws BadHeaderException { setDefaultVars(); parseMessage( parseinput ); } /** * Sets the type of SIP message this represents. See @ref * SipMessage::getType() for a list of possible values. * * @param newtype */ public void setType(int newtype) { type = newtype; } /** * Returns the type of SIP message represented. * * @return MsgType */ public int getType() { return type; } /** * Sets the method on this SIP message. Valid only for SIP requests. * * @param newmethod */ public void setMethod(int newmethod) { meth = newmethod; } /** * Returns the method of this SIP message. Valid only for SIP requests. * * @return Method */ public int getMethod() { return meth; } /** * Returns true if the message has a body. * * @return bool */ public boolean haveBody() { return ( getContentLength() > 0 ); } /** * Sets the body of the SIP message. * * @param newbody */ public void setBody(String newbody) { if ( messageSize > 0 ) { messagebody = newbody.substring( 0, messageSize ); } else { messagebody = newbody; } if ( messagebody != null ) messageSize = messagebody.length(); } /** * Generates and returns the version string for this message. * * @return String */ public String getVersionString() { String ver = "SIP/2.0"; return ver; } /** * Generates and returns the start line for this message. * * @return String */ public String startLine() { String line = ""; switch ( type ) { case Request: line += Sip.getMethodString( meth ) + " "; line += requesturi.reqUri() + " "; line += getVersionString() + "\r\n"; break; case Response: line += getVersionString() + " "; line += String.valueOf( status.getCode() ) + " "; line += status.getReasonPhrase() + "\r\n"; break; case BadType: line += "Bad Message Type"; break; } return line; } /** * Generates and returns all of the heads of this message. * * @return String */ public String messageHeaders() { String headers = ""; SipHeader curheader = null; headers += vialist.getViaList(); for ( ListIterator li = headerlist.listIterator(); li.hasNext(); ) { curheader = (SipHeader)li.next(); if ( curheader.id != SipHeader.BadHeader ) { headers += SipHeader.getHeaderString( curheader.id ) + ": "; headers += curheader.data + "\r\n"; } } if( contactlist.getListLength() > 0 ) { headers += SipHeader.getHeaderString( SipHeader.Contact ) + ": "; headers += contactlist.getUriList(); headers += "\r\n"; } if( recordroute.getListLength() > 0 ) { headers += SipHeader.getHeaderString( SipHeader.Record_Route ) + ": "; headers += recordroute.getUriList(); headers += "\r\n"; } return headers; } /** * Returns the message body. * * @return String */ public String messageBody() { return messagebody; } public int getContentLength() { return ( messagebody == null ) ? 0 : messagebody.length(); } /** * Generates and returns the SIP message in full. * * @return String */ public String message() { String msg; msg = startLine() + messageHeaders() + "\r\n"; if ( haveBody() ) { msg += messageBody(); } return msg; } /** * Parses the given input as a SIP message. * * @param parseinput */ public void parseMessage(String parseinput) throws BadHeaderException { StringBuffer fullmessage; ; int wherecrlf; int wherecrlfcrlf; fullmessage = new StringBuffer( parseinput ); // remove \r from the message for ( int i = 0; i < fullmessage.length(); i++ ) { if ( fullmessage.charAt( i ) == '\r' ) fullmessage.deleteCharAt( i ); } // int i; // while ((i = fullmessage.indexOf( "\r\n")) >= 0 ) // fullmessage = fullmessage.substring(0, i) + fullmessage.substring(i+1); // fullmessage.replace( '\r', '\n' ); String messageString = fullmessage.toString(); wherecrlf = messageString.indexOf( "\n" ); wherecrlfcrlf = messageString.indexOf( "\n\n" ); if( wherecrlfcrlf == -1 ) wherecrlfcrlf = messageString.length(); // Start line is the first line parseStartLine( messageString.substring( 0, wherecrlf ) ); // Headers go on until \n\n parseHeaders( messageString.substring( wherecrlf + 1, wherecrlfcrlf ) ); // The body is the rest (if there is more) if (wherecrlfcrlf < fullmessage.length()) setBody( fullmessage.substring( wherecrlfcrlf + 2 ) ); checkMessage(); // see if we've got all the appropriate headers } /** * Inserts a new header into the message. * * @param id * @param data */ public void insertHeader( int id, String data) { headerlist.add( new SipHeader( id, data ) ); } /** * Returns true if the message contains the specified header. * * @param id * @return bool */ public boolean hasHeader(int id) { SipHeader curheader = null; for (ListIterator li = headerlist.listIterator(); li.hasNext(); ) { curheader = (SipHeader)li.next(); if ( curheader.id == id ) { return true; } } return false; } /** * Returns the data contained in the specified header. * * @param id * @return String */ public String getHeaderData(int id) { SipHeader curheader = null; for (ListIterator li = headerlist.listIterator(); li.hasNext(); ) { curheader = (SipHeader)li.next(); if ( curheader.id == id ) { return curheader.data; } } return null; } /** * Sets the request URI for the message. Relevant only if it is a SIP * request. This will also set the destination host and port for the * message. * * @param newrequri */ public void setRequestUri(SipUri newrequri) { requesturi = newrequri; } /** * Returns the request URI for this message. Relevant only if it is a * SIP request. * * @return SipUri */ public SipUri getRequestUri() { return requesturi; } /** * Sets the status of the message. Relevant only if it is a SIP * response. * * @param stat */ public void setStatus(SipStatus stat) { status = stat; } /** * Returns the status of the message. Relevant only if it is a SIP * response. * * @return SipStatus & */ public SipStatus getStatus() { return status; } /** * Returns a reference to the via list for this message. Usually * useful for getting and setting the topmost via entry. * * @return SipViaList */ public SipViaList getViaList() { return vialist; } /** * Returns a reference to the Record-Route for this message. * * @return SipUriList & */ public SipUriList getRecordRoute() { return recordroute; } /** * Returns a reference to the contact list for this message. * * @return SipUriList & */ public SipUriList getContactList() { return contactlist; } /** * Sets the via list to be a copy of an existing via list. Useful for * building responses. * * @param copylist */ public void setViaList(SipViaList copylist) { vialist = copylist; } /** * Sets the contact list for this message. * * @param newclist */ public void setContactList(SipUriList newclist) { contactlist = newclist; } /** * Sets the record route for this message. * * @param newrr */ public void setRecordRoute(SipUriList newrr) { recordroute = newrr; } /** * Returns the timestamp of the last transmission. * * @return timeval * */ public Date getTimestamp() { return timestamp; } /** * Recalculates the timestamp on the message. */ public void setTimestamp() { timestamp = new Date(); } /** * Returns the last time tick. * * @return int */ public int lastTimeTick() { return lastt; } /** * Sets the current time tick. * * @param newtt */ public void setTimeTick(int newtt) { lastt = newtt; } /** * Returns the current value of the retransmission counter for this * message. * * @return int */ public int sendCount() { return sendcount; } /** * Increments the retransmission counter. */ public void incrSendCount() { sendcount++; } /** * Static method to create a new SIP Call ID. * * @return String */ public static String createCallId() { int uniqid; String theid; Random rand = new Random(); uniqid = rand.nextInt(MAX_CALLID); // should be sufficient // fix for 3com carrier gw theid = String.valueOf( uniqid ) + "@" + Sip.getLocalAddress(); return theid; } /** * Add a url to the contact list */ public void addContact( SipUri contact ) { contactlist.add( contact ); } /** * setDefaultVars */ private void setDefaultVars() { // headerlist.setAutoDelete( true ); setType( Request ); setMethod( Sip.BadMethod ); lastt = 0; sendcount = 0; timestamp = new Date(); messageSize = 0; vialist = new SipViaList(); recordroute = new SipUriList(); contactlist = new SipUriList(); headerlist = new ArrayList(); } /** * parseStartLine * @param startline */ private void parseStartLine(String startline) { StringTokenizer st = new StringTokenizer(startline.trim()); String method = st.nextToken(); String requri = st.nextToken(); String verstr = ""; // the verstr could actually be a response with several words while ( st.hasMoreTokens() ) verstr += st.nextToken(); if ( method.startsWith( "SIP" ) ) { // Input is a response setType( Response ); try { status = new SipStatus( Integer.parseInt(requri) ); } catch (NumberFormatException nfe) { System.err.println("Couldn't set status code for " + requri + ". Default to 500"); status = new SipStatus( 500 ); } status.setReasonPhrase( verstr ); } else { setType( Request ); setMethod( Sip.matchMethod( method ) ); setRequestUri( new SipUri( requri ) ); } } /** * parseHeaders * @param inbuf */ private void parseHeaders(String inbuf) throws BadHeaderException { int lastheader; int id; String curheader = ""; String curbody = ""; lastheader = 0; HeaderParser hp = new HeaderParser( inbuf ); while ( hp.hasMoreLines() ) { String line = hp.getLine(); int pos = line.indexOf(':'); if ( pos < 0 ) { System.err.println("HMM, in parse header with line of " + line); return; // nothing here } curheader = line.substring( 0, pos ); curbody = line.substring( pos + 1 ).trim(); id = SipHeader.matchHeader( curheader ); if ( id == SipHeader.Content_Length ) { try { messageSize = Integer.parseInt( curbody ); } catch ( NumberFormatException nfe ) {} } if ( id == SipHeader.Via ) { vialist.parseVia( curbody ); } else if( id == SipHeader.Record_Route ) { recordroute.parseList( curbody ); } else if( id == SipHeader.Contact ) { contactlist.parseList( curbody ); } else { if ( id != SipHeader.BadHeader ) { insertHeader( id, curbody ); } } } } private void checkMessage() throws BadHeaderException { // The message should have several manditory fields. Make // sure they're there final int [] manditoryHeaders = { SipHeader.Call_ID, SipHeader.CSeq, SipHeader.From, SipHeader.To }; for ( int i = 0; i < manditoryHeaders.length; i++ ) { // check for their existance. If not there, throw the exception if ( ! hasHeader( manditoryHeaders[i] ) ) { throw new BadHeaderException( "Message is missing header: " + SipHeader.getHeaderString( manditoryHeaders[i] ) ); } } // make sure there's a "Via" if ( vialist.size() == 0 ) { throw new BadHeaderException( "Message is missing header: " + SipHeader.getHeaderString( SipHeader.Via ) ); } // a couple of unique checks // All responses and all requests except CANCEL should have Content-Length if ( ( type == Response ) || ( meth != Sip.CANCEL ) ) { if ( ! hasHeader( SipHeader.Content_Length ) ) { throw new BadHeaderException( "Message is missing header: " + SipHeader.getHeaderString( SipHeader.Content_Length ) ); } } // All 405 responses should have an Allow field if ( ( type == Response ) && ( status.getCode() == 405 ) ) { if ( ! hasHeader( SipHeader.Allow ) ) { throw new BadHeaderException( "Message is missing header: " + SipHeader.getHeaderString( SipHeader.Allow ) ); } } } // class variables static final int Request = 0; static final int Response = 1; static final int BadType = 2; // class variables private SipViaList vialist; private ArrayList headerlist; private SipUriList recordroute; private SipUriList contactlist; private int type; private int meth; private SipStatus status; private SipUri requesturi; private String messagebody; private int messageSize; private static final int MAX_CALLID = Integer.MAX_VALUE - 1;; Date timestamp; int lastt; int sendcount; }