506 lines
19 KiB
Java
506 lines
19 KiB
Java
/*
|
|
* 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 gov.nist.javax.sip.parser;
|
|
|
|
/*
|
|
*
|
|
* Lamine Brahimi and Yann Duponchel (IBM Zurich) noticed that the parser was
|
|
* blocking so I threw out some cool pipelining which ran fast but only worked
|
|
* when the phase of the moon matched its mood. Now things are serialized and
|
|
* life goes slower but more reliably.
|
|
*
|
|
*/
|
|
import gov.nist.core.*;
|
|
import gov.nist.javax.sip.message.*;
|
|
import gov.nist.javax.sip.header.*;
|
|
import java.text.ParseException;
|
|
import java.io.*;
|
|
|
|
/**
|
|
* This implements a pipelined message parser suitable for use with a stream -
|
|
* oriented input such as TCP. The client uses this class by instatiating with
|
|
* an input stream from which input is read and fed to a message parser. It
|
|
* keeps reading from the input stream and process messages in a never ending
|
|
* interpreter loop. The message listener interface gets called for processing
|
|
* messages or for processing errors. The payload specified by the
|
|
* content-length header is read directly from the input stream. This can be
|
|
* accessed from the SIPMessage using the getContent and getContentBytes methods
|
|
* provided by the SIPMessage class.
|
|
*
|
|
* @version 1.2 $Revision: 1.23 $ $Date: 2009/08/16 17:28:28 $
|
|
*
|
|
* @author M. Ranganathan
|
|
*
|
|
* @see SIPMessageListener
|
|
*/
|
|
public final class PipelinedMsgParser implements Runnable {
|
|
|
|
|
|
|
|
/**
|
|
* The message listener that is registered with this parser. (The message
|
|
* listener has methods that can process correct and erroneous messages.)
|
|
*/
|
|
protected SIPMessageListener sipMessageListener;
|
|
private Thread mythread; // Preprocessor thread
|
|
//private byte[] messageBody;
|
|
//private boolean errorFlag;
|
|
private Pipeline rawInputStream;
|
|
private int maxMessageSize;
|
|
private int sizeCounter;
|
|
//private int messageSize;
|
|
|
|
/**
|
|
* default constructor.
|
|
*/
|
|
protected PipelinedMsgParser() {
|
|
super();
|
|
|
|
}
|
|
|
|
private static int uid = 0;
|
|
|
|
private static synchronized int getNewUid() {
|
|
return uid++;
|
|
}
|
|
|
|
/**
|
|
* Constructor when we are given a message listener and an input stream
|
|
* (could be a TCP connection or a file)
|
|
*
|
|
* @param sipMessageListener
|
|
* Message listener which has methods that get called back from
|
|
* the parser when a parse is complete
|
|
* @param in
|
|
* Input stream from which to read the input.
|
|
* @param debug
|
|
* Enable/disable tracing or lexical analyser switch.
|
|
*/
|
|
public PipelinedMsgParser(SIPMessageListener sipMessageListener,
|
|
Pipeline in, boolean debug, int maxMessageSize) {
|
|
this();
|
|
this.sipMessageListener = sipMessageListener;
|
|
rawInputStream = in;
|
|
this.maxMessageSize = maxMessageSize;
|
|
mythread = new Thread(this);
|
|
mythread.setName("PipelineThread-" + getNewUid());
|
|
|
|
}
|
|
|
|
/**
|
|
* This is the constructor for the pipelined parser.
|
|
*
|
|
* @param mhandler
|
|
* a SIPMessageListener implementation that provides the message
|
|
* handlers to handle correctly and incorrectly parsed messages.
|
|
* @param in
|
|
* An input stream to read messages from.
|
|
*/
|
|
|
|
public PipelinedMsgParser(SIPMessageListener mhandler, Pipeline in,
|
|
int maxMsgSize) {
|
|
this(mhandler, in, false, maxMsgSize);
|
|
}
|
|
|
|
/**
|
|
* This is the constructor for the pipelined parser.
|
|
*
|
|
* @param in -
|
|
* An input stream to read messages from.
|
|
*/
|
|
|
|
public PipelinedMsgParser(Pipeline in) {
|
|
this(null, in, false, 0);
|
|
}
|
|
|
|
/**
|
|
* Start reading and processing input.
|
|
*/
|
|
public void processInput() {
|
|
mythread.start();
|
|
}
|
|
|
|
/**
|
|
* Create a new pipelined parser from an existing one.
|
|
*
|
|
* @return A new pipelined parser that reads from the same input stream.
|
|
*/
|
|
protected Object clone() {
|
|
PipelinedMsgParser p = new PipelinedMsgParser();
|
|
|
|
p.rawInputStream = this.rawInputStream;
|
|
p.sipMessageListener = this.sipMessageListener;
|
|
Thread mythread = new Thread(p);
|
|
mythread.setName("PipelineThread");
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Add a class that implements a SIPMessageListener interface whose methods
|
|
* get called * on successful parse and error conditons.
|
|
*
|
|
* @param mlistener
|
|
* a SIPMessageListener implementation that can react to correct
|
|
* and incorrect pars.
|
|
*/
|
|
|
|
public void setMessageListener(SIPMessageListener mlistener) {
|
|
sipMessageListener = mlistener;
|
|
}
|
|
|
|
/**
|
|
* read a line of input (I cannot use buffered reader because we may need to
|
|
* switch encodings mid-stream!
|
|
*/
|
|
private String readLine(InputStream inputStream) throws IOException {
|
|
StringBuffer retval = new StringBuffer("");
|
|
while (true) {
|
|
char ch;
|
|
int i = inputStream.read();
|
|
if (i == -1) {
|
|
throw new IOException("End of stream");
|
|
} else
|
|
ch = (char) i;
|
|
// reduce the available read size by 1 ("size" of a char).
|
|
if (this.maxMessageSize > 0) {
|
|
this.sizeCounter--;
|
|
if (this.sizeCounter <= 0)
|
|
throw new IOException("Max size exceeded!");
|
|
}
|
|
if (ch != '\r')
|
|
retval.append(ch);
|
|
if (ch == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
return retval.toString();
|
|
}
|
|
|
|
/**
|
|
* This is input reading thread for the pipelined parser. You feed it input
|
|
* through the input stream (see the constructor) and it calls back an event
|
|
* listener interface for message processing or error. It cleans up the
|
|
* input - dealing with things like line continuation
|
|
*/
|
|
public void run() {
|
|
|
|
Pipeline inputStream = this.rawInputStream;
|
|
// inputStream = new MyFilterInputStream(this.rawInputStream);
|
|
// I cannot use buffered reader here because we may need to switch
|
|
// encodings to read the message body.
|
|
try {
|
|
while (true) {
|
|
this.sizeCounter = this.maxMessageSize;
|
|
// this.messageSize = 0;
|
|
StringBuffer inputBuffer = new StringBuffer();
|
|
|
|
if (Debug.parserDebug)
|
|
Debug.println("Starting parse!");
|
|
|
|
String line1;
|
|
String line2 = null;
|
|
|
|
while (true) {
|
|
try {
|
|
line1 = readLine(inputStream);
|
|
// ignore blank lines.
|
|
if (line1.equals("\n")) {
|
|
if (Debug.parserDebug) {
|
|
Debug.println("Discarding blank line. ");
|
|
}
|
|
continue;
|
|
} else
|
|
break;
|
|
} catch (IOException ex) {
|
|
Debug.printStackTrace(ex);
|
|
this.rawInputStream.stopTimer();
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
inputBuffer.append(line1);
|
|
// Guard against bad guys.
|
|
this.rawInputStream.startTimer();
|
|
|
|
Debug.println("Reading Input Stream");
|
|
while (true) {
|
|
try {
|
|
line2 = readLine(inputStream);
|
|
inputBuffer.append(line2);
|
|
if (line2.trim().equals(""))
|
|
break;
|
|
} catch (IOException ex) {
|
|
this.rawInputStream.stopTimer();
|
|
Debug.printStackTrace(ex);
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
// Stop the timer that will kill the read.
|
|
this.rawInputStream.stopTimer();
|
|
inputBuffer.append(line2);
|
|
StringMsgParser smp = new StringMsgParser(sipMessageListener);
|
|
smp.readBody = false;
|
|
SIPMessage sipMessage = null;
|
|
|
|
try {
|
|
if (Debug.debug) {
|
|
Debug.println("About to parse : " + inputBuffer.toString());
|
|
}
|
|
sipMessage = smp.parseSIPMessage(inputBuffer.toString());
|
|
if (sipMessage == null) {
|
|
this.rawInputStream.stopTimer();
|
|
continue;
|
|
}
|
|
} catch (ParseException ex) {
|
|
// Just ignore the parse exception.
|
|
Debug.logError("Detected a parse error", ex);
|
|
continue;
|
|
}
|
|
|
|
if (Debug.debug) {
|
|
Debug.println("Completed parsing message");
|
|
}
|
|
ContentLength cl = (ContentLength) sipMessage
|
|
.getContentLength();
|
|
int contentLength = 0;
|
|
if (cl != null) {
|
|
contentLength = cl.getContentLength();
|
|
} else {
|
|
contentLength = 0;
|
|
}
|
|
|
|
if (Debug.debug) {
|
|
Debug.println("contentLength " + contentLength);
|
|
}
|
|
|
|
if (contentLength == 0) {
|
|
sipMessage.removeContent();
|
|
} else if (maxMessageSize == 0
|
|
|| contentLength < this.sizeCounter) {
|
|
byte[] message_body = new byte[contentLength];
|
|
int nread = 0;
|
|
while (nread < contentLength) {
|
|
// Start my starvation timer.
|
|
// This ensures that the other end
|
|
// writes at least some data in
|
|
// or we will close the pipe from
|
|
// him. This prevents DOS attack
|
|
// that takes up all our connections.
|
|
this.rawInputStream.startTimer();
|
|
try {
|
|
|
|
int readlength = inputStream.read(message_body,
|
|
nread, contentLength - nread);
|
|
if (readlength > 0) {
|
|
nread += readlength;
|
|
} else {
|
|
break;
|
|
}
|
|
} catch (IOException ex) {
|
|
Debug.logError("Exception Reading Content",ex);
|
|
break;
|
|
} finally {
|
|
// Stop my starvation timer.
|
|
this.rawInputStream.stopTimer();
|
|
}
|
|
}
|
|
sipMessage.setMessageContent(message_body);
|
|
}
|
|
// Content length too large - process the message and
|
|
// return error from there.
|
|
if (sipMessageListener != null) {
|
|
try {
|
|
sipMessageListener.processMessage(sipMessage);
|
|
} catch (Exception ex) {
|
|
// fatal error in processing - close the
|
|
// connection.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
try {
|
|
inputStream.close();
|
|
} catch (IOException e) {
|
|
InternalErrorHandler.handleException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void close() {
|
|
try {
|
|
this.rawInputStream.close();
|
|
} catch (IOException ex) {
|
|
// Ignore.
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* $Log: PipelinedMsgParser.java,v $
|
|
* Revision 1.23 2009/08/16 17:28:28 mranga
|
|
* Issue number: 208
|
|
* Obtained from:
|
|
* Submitted by:
|
|
* Reviewed by:
|
|
*
|
|
* Add authentication mechanism that uses H(username:domain:password)
|
|
*
|
|
* Revision 1.22 2009/07/17 18:58:02 emcho
|
|
* Converts indentation tabs to spaces so that we have a uniform indentation policy in the whole project.
|
|
*
|
|
* Revision 1.21 2008/05/24 04:10:01 mranga
|
|
*
|
|
* Issue number: 158
|
|
* Obtained from:
|
|
* Submitted by:
|
|
* Reviewed by: mranga
|
|
*
|
|
* Deliver tx timeout for Canceled INVITE. Fix pipeline thread exit.
|
|
*
|
|
* Revision 1.20 2008/05/22 19:38:07 jbemmel
|
|
* Fix for issue 149: the logic wasn't always closing the internal socket pipe,
|
|
* causing the pipe reader thread to block indefinitely
|
|
*
|
|
* Repeatedly starting/stopping the stack then gives hanging threads
|
|
* Revision 1.19 2007/01/28 13:06:21 mranga
|
|
* Issue number: 99 Obtained from: Submitted by: Reviewed by: mranga
|
|
*
|
|
* Fixed PRACK handling null pointer exception (for proxy case) and cleanup of
|
|
* unused variables.
|
|
*
|
|
* CVS: ----------------------------------------------------------------------
|
|
* CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
|
|
* then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
|
|
* has been taken from another system, CVS: then name the system in this line,
|
|
* otherwise delete it. CVS: Submitted by: CVS: If this code has been
|
|
* contributed to the project by someone else; i.e., CVS: they sent us a patch
|
|
* or a set of diffs, then include their name/email CVS: address here. If this
|
|
* is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
|
|
* pre-commit code reviews and someone else has CVS: reviewed your changes,
|
|
* include their name(s) here. CVS: If you have not had it reviewed then delete
|
|
* this line.
|
|
*
|
|
* Revision 1.18 2006/07/13 09:02:10 mranga Issue number: Obtained from:
|
|
* Submitted by: jeroen van bemmel Reviewed by: mranga Moved some changes from
|
|
* jain-sip-1.2 to java.net
|
|
*
|
|
* CVS: ----------------------------------------------------------------------
|
|
* CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
|
|
* then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
|
|
* has been taken from another system, CVS: then name the system in this line,
|
|
* otherwise delete it. CVS: Submitted by: CVS: If this code has been
|
|
* contributed to the project by someone else; i.e., CVS: they sent us a patch
|
|
* or a set of diffs, then include their name/email CVS: address here. If this
|
|
* is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
|
|
* pre-commit code reviews and someone else has CVS: reviewed your changes,
|
|
* include their name(s) here. CVS: If you have not had it reviewed then delete
|
|
* this line.
|
|
*
|
|
* Revision 1.4 2006/06/19 06:47:27 mranga javadoc fixups
|
|
*
|
|
* Revision 1.3 2006/06/17 10:18:14 mranga Added some synchronization to the
|
|
* sequence number checking. Small javadoc fixups
|
|
*
|
|
* Revision 1.2 2006/06/16 15:26:28 mranga Added NIST disclaimer to all public
|
|
* domain files. Clean up some javadoc. Fixed a leak
|
|
*
|
|
* Revision 1.1.1.1 2005/10/04 17:12:35 mranga
|
|
*
|
|
* Import
|
|
*
|
|
*
|
|
* Revision 1.16 2004/11/30 23:28:14 mranga Issue number: 44 Submitted by: Rob
|
|
* Daugherty Reviewed by: M. Ranganathan
|
|
*
|
|
* TCP Pipelining truncates content when other end of pipe is closed.
|
|
*
|
|
* Revision 1.15 2004/05/30 18:55:56 mranga Reviewed by: mranga Move to timers
|
|
* and eliminate the Transaction scanner Thread to improve scalability and
|
|
* reduce cpu usage.
|
|
*
|
|
* Revision 1.14 2004/05/16 14:13:22 mranga Reviewed by: mranga Fixed the
|
|
* use-count issue reported by Peter Parnes. Added property to prevent against
|
|
* content-length dos attacks.
|
|
*
|
|
* Revision 1.13 2004/03/19 04:22:22 mranga Reviewed by: mranga Added IO Pacing
|
|
* for long writes - split write into chunks and flush after each chunk to avoid
|
|
* socket back pressure.
|
|
*
|
|
* Revision 1.12 2004/03/18 22:01:19 mranga Reviewed by: mranga Get rid of the
|
|
* PipedInputStream from pipelined parser to avoid a copy.
|
|
*
|
|
* Revision 1.11 2004/03/07 22:25:23 mranga Reviewed by: mranga Added a new
|
|
* configuration parameter that instructs the stack to drop a server connection
|
|
* after server transaction termination set
|
|
* gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS=false for this Default behavior
|
|
* is true.
|
|
*
|
|
* Revision 1.10 2004/02/29 15:32:58 mranga Reviewed by: mranga bug fixes on
|
|
* limiting the max message size.
|
|
*
|
|
* Revision 1.9 2004/02/29 00:46:34 mranga Reviewed by: mranga Added new
|
|
* configuration property to limit max message size for TCP transport. The
|
|
* property is gov.nist.javax.sip.MAX_MESSAGE_SIZE
|
|
*
|
|
* Revision 1.8 2004/02/25 21:43:03 mranga Reviewed by: mranga Added a couple of
|
|
* todo's and removed some debug printlns that could slow code down by a bit.
|
|
*
|
|
* Revision 1.7 2004/02/25 20:52:46 mranga Reviewed by: mranga Fix TCP transport
|
|
* so messages in excess of 8192 bytes are accepted.
|
|
*
|
|
* Revision 1.6 2004/01/22 18:39:41 mranga Reviewed by: M. Ranganathan Moved the
|
|
* ifdef SIMULATION and associated tags to the first column so Prep preprocessor
|
|
* can deal with them.
|
|
*
|
|
* Revision 1.5 2004/01/22 14:23:45 mranga Reviewed by: mranga Fixed some minor
|
|
* formatting issues.
|
|
*
|
|
* Revision 1.4 2004/01/22 13:26:31 sverker Issue number: Obtained from:
|
|
* Submitted by: sverker Reviewed by: mranga
|
|
*
|
|
* Major reformat of code to conform with style guide. Resolved compiler and
|
|
* javadoc warnings. Added CVS tags.
|
|
*
|
|
* CVS: ----------------------------------------------------------------------
|
|
* CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
|
|
* then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
|
|
* has been taken from another system, CVS: then name the system in this line,
|
|
* otherwise delete it. CVS: Submitted by: CVS: If this code has been
|
|
* contributed to the project by someone else; i.e., CVS: they sent us a patch
|
|
* or a set of diffs, then include their name/email CVS: address here. If this
|
|
* is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
|
|
* pre-commit code reviews and someone else has CVS: reviewed your changes,
|
|
* include their name(s) here. CVS: If you have not had it reviewed then delete
|
|
* this line.
|
|
*
|
|
*/
|