1/*
2 * Conditions Of Use
3 *
4 * This software was developed by employees of the National Institute of
5 * Standards and Technology (NIST), an agency of the Federal Government.
6 * Pursuant to title 15 Untied States Code Section 105, works of NIST
7 * employees are not subject to copyright protection in the United States
8 * and are considered to be in the public domain.  As a result, a formal
9 * license is not needed to use the software.
10 *
11 * This software is provided by NIST as a service and is expressly
12 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15 * AND DATA ACCURACY.  NIST does not warrant or make any representations
16 * regarding the use of the software or the results thereof, including but
17 * not limited to the correctness, accuracy, reliability or usefulness of
18 * the software.
19 *
20 * Permission to use this software is contingent upon your acceptance
21 * of the terms of this agreement
22 *
23 * .
24 *
25 */
26/******************************************************************************
27 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD)       *
28 ******************************************************************************/
29package gov.nist.javax.sip.parser;
30
31/*
32 *
33 * Lamine Brahimi and Yann Duponchel (IBM Zurich) noticed that the parser was
34 * blocking so I threw out some cool pipelining which ran fast but only worked
35 * when the phase of the moon matched its mood. Now things are serialized and
36 * life goes slower but more reliably.
37 *
38 */
39import gov.nist.core.*;
40import gov.nist.javax.sip.message.*;
41import gov.nist.javax.sip.header.*;
42import java.text.ParseException;
43import java.io.*;
44
45/**
46 * This implements a pipelined message parser suitable for use with a stream -
47 * oriented input such as TCP. The client uses this class by instatiating with
48 * an input stream from which input is read and fed to a message parser. It
49 * keeps reading from the input stream and process messages in a never ending
50 * interpreter loop. The message listener interface gets called for processing
51 * messages or for processing errors. The payload specified by the
52 * content-length header is read directly from the input stream. This can be
53 * accessed from the SIPMessage using the getContent and getContentBytes methods
54 * provided by the SIPMessage class.
55 *
56 * @version 1.2 $Revision: 1.23 $ $Date: 2009/08/16 17:28:28 $
57 *
58 * @author M. Ranganathan
59 *
60 * @see SIPMessageListener
61 */
62public final class PipelinedMsgParser implements Runnable {
63
64
65
66    /**
67     * The message listener that is registered with this parser. (The message
68     * listener has methods that can process correct and erroneous messages.)
69     */
70    protected SIPMessageListener sipMessageListener;
71    private Thread mythread; // Preprocessor thread
72    //private byte[] messageBody;
73    //private boolean errorFlag;
74    private Pipeline rawInputStream;
75    private int maxMessageSize;
76    private int sizeCounter;
77    //private int messageSize;
78
79    /**
80     * default constructor.
81     */
82    protected PipelinedMsgParser() {
83        super();
84
85    }
86
87    private static int uid = 0;
88
89    private static synchronized int getNewUid() {
90        return uid++;
91    }
92
93    /**
94     * Constructor when we are given a message listener and an input stream
95     * (could be a TCP connection or a file)
96     *
97     * @param sipMessageListener
98     *            Message listener which has methods that get called back from
99     *            the parser when a parse is complete
100     * @param in
101     *            Input stream from which to read the input.
102     * @param debug
103     *            Enable/disable tracing or lexical analyser switch.
104     */
105    public PipelinedMsgParser(SIPMessageListener sipMessageListener,
106            Pipeline in, boolean debug, int maxMessageSize) {
107        this();
108        this.sipMessageListener = sipMessageListener;
109        rawInputStream = in;
110        this.maxMessageSize = maxMessageSize;
111        mythread = new Thread(this);
112        mythread.setName("PipelineThread-" + getNewUid());
113
114    }
115
116    /**
117     * This is the constructor for the pipelined parser.
118     *
119     * @param mhandler
120     *            a SIPMessageListener implementation that provides the message
121     *            handlers to handle correctly and incorrectly parsed messages.
122     * @param in
123     *            An input stream to read messages from.
124     */
125
126    public PipelinedMsgParser(SIPMessageListener mhandler, Pipeline in,
127            int maxMsgSize) {
128        this(mhandler, in, false, maxMsgSize);
129    }
130
131    /**
132     * This is the constructor for the pipelined parser.
133     *
134     * @param in -
135     *            An input stream to read messages from.
136     */
137
138    public PipelinedMsgParser(Pipeline in) {
139        this(null, in, false, 0);
140    }
141
142    /**
143     * Start reading and processing input.
144     */
145    public void processInput() {
146        mythread.start();
147    }
148
149    /**
150     * Create a new pipelined parser from an existing one.
151     *
152     * @return A new pipelined parser that reads from the same input stream.
153     */
154    protected Object clone() {
155        PipelinedMsgParser p = new PipelinedMsgParser();
156
157        p.rawInputStream = this.rawInputStream;
158        p.sipMessageListener = this.sipMessageListener;
159        Thread mythread = new Thread(p);
160        mythread.setName("PipelineThread");
161        return p;
162    }
163
164    /**
165     * Add a class that implements a SIPMessageListener interface whose methods
166     * get called * on successful parse and error conditons.
167     *
168     * @param mlistener
169     *            a SIPMessageListener implementation that can react to correct
170     *            and incorrect pars.
171     */
172
173    public void setMessageListener(SIPMessageListener mlistener) {
174        sipMessageListener = mlistener;
175    }
176
177    /**
178     * read a line of input (I cannot use buffered reader because we may need to
179     * switch encodings mid-stream!
180     */
181    private String readLine(InputStream inputStream) throws IOException {
182        StringBuffer retval = new StringBuffer("");
183        while (true) {
184            char ch;
185            int i = inputStream.read();
186            if (i == -1) {
187                throw new IOException("End of stream");
188            } else
189                ch = (char) i;
190            // reduce the available read size by 1 ("size" of a char).
191            if (this.maxMessageSize > 0) {
192                this.sizeCounter--;
193                if (this.sizeCounter <= 0)
194                    throw new IOException("Max size exceeded!");
195            }
196            if (ch != '\r')
197                retval.append(ch);
198            if (ch == '\n') {
199                break;
200            }
201        }
202        return retval.toString();
203    }
204
205    /**
206     * This is input reading thread for the pipelined parser. You feed it input
207     * through the input stream (see the constructor) and it calls back an event
208     * listener interface for message processing or error. It cleans up the
209     * input - dealing with things like line continuation
210     */
211    public void run() {
212
213        Pipeline inputStream = this.rawInputStream;
214        // inputStream = new MyFilterInputStream(this.rawInputStream);
215        // I cannot use buffered reader here because we may need to switch
216        // encodings to read the message body.
217        try {
218            while (true) {
219                this.sizeCounter = this.maxMessageSize;
220                // this.messageSize = 0;
221                StringBuffer inputBuffer = new StringBuffer();
222
223                if (Debug.parserDebug)
224                    Debug.println("Starting parse!");
225
226                String line1;
227                String line2 = null;
228
229                while (true) {
230                    try {
231                        line1 = readLine(inputStream);
232                        // ignore blank lines.
233                        if (line1.equals("\n")) {
234                            if (Debug.parserDebug) {
235                                Debug.println("Discarding blank line. ");
236                            }
237                            continue;
238                        } else
239                            break;
240                    } catch (IOException ex) {
241                        Debug.printStackTrace(ex);
242                        this.rawInputStream.stopTimer();
243                        return;
244
245                    }
246                }
247
248                inputBuffer.append(line1);
249                // Guard against bad guys.
250                this.rawInputStream.startTimer();
251
252                Debug.println("Reading Input Stream");
253                while (true) {
254                    try {
255                        line2 = readLine(inputStream);
256                        inputBuffer.append(line2);
257                        if (line2.trim().equals(""))
258                            break;
259                    } catch (IOException ex) {
260                        this.rawInputStream.stopTimer();
261                        Debug.printStackTrace(ex);
262                        return;
263
264                    }
265                }
266
267                // Stop the timer that will kill the read.
268                this.rawInputStream.stopTimer();
269                inputBuffer.append(line2);
270                StringMsgParser smp = new StringMsgParser(sipMessageListener);
271                smp.readBody = false;
272                SIPMessage sipMessage = null;
273
274                try {
275                    if (Debug.debug) {
276                        Debug.println("About to parse : " + inputBuffer.toString());
277                    }
278                    sipMessage = smp.parseSIPMessage(inputBuffer.toString());
279                    if (sipMessage == null) {
280                        this.rawInputStream.stopTimer();
281                        continue;
282                    }
283                } catch (ParseException ex) {
284                    // Just ignore the parse exception.
285                    Debug.logError("Detected a parse error", ex);
286                    continue;
287                }
288
289                if (Debug.debug) {
290                    Debug.println("Completed parsing message");
291                }
292                ContentLength cl = (ContentLength) sipMessage
293                        .getContentLength();
294                int contentLength = 0;
295                if (cl != null) {
296                    contentLength = cl.getContentLength();
297                } else {
298                    contentLength = 0;
299                }
300
301                if (Debug.debug) {
302                    Debug.println("contentLength " + contentLength);
303                }
304
305                if (contentLength == 0) {
306                    sipMessage.removeContent();
307                } else if (maxMessageSize == 0
308                        || contentLength < this.sizeCounter) {
309                    byte[] message_body = new byte[contentLength];
310                    int nread = 0;
311                    while (nread < contentLength) {
312                        // Start my starvation timer.
313                        // This ensures that the other end
314                        // writes at least some data in
315                        // or we will close the pipe from
316                        // him. This prevents DOS attack
317                        // that takes up all our connections.
318                        this.rawInputStream.startTimer();
319                        try {
320
321                            int readlength = inputStream.read(message_body,
322                                    nread, contentLength - nread);
323                            if (readlength > 0) {
324                                nread += readlength;
325                            } else {
326                                break;
327                            }
328                        } catch (IOException ex) {
329                            Debug.logError("Exception Reading Content",ex);
330                            break;
331                        } finally {
332                            // Stop my starvation timer.
333                            this.rawInputStream.stopTimer();
334                        }
335                    }
336                    sipMessage.setMessageContent(message_body);
337                }
338                // Content length too large - process the message and
339                // return error from there.
340                if (sipMessageListener != null) {
341                    try {
342                        sipMessageListener.processMessage(sipMessage);
343                    } catch (Exception ex) {
344                        // fatal error in processing - close the
345                        // connection.
346                        break;
347                    }
348                }
349            }
350        } finally {
351            try {
352                inputStream.close();
353            } catch (IOException e) {
354                InternalErrorHandler.handleException(e);
355            }
356        }
357    }
358
359    public void close() {
360        try {
361            this.rawInputStream.close();
362        } catch (IOException ex) {
363            // Ignore.
364        }
365    }
366}
367/*
368 * $Log: PipelinedMsgParser.java,v $
369 * Revision 1.23  2009/08/16 17:28:28  mranga
370 * Issue number:  208
371 * Obtained from:
372 * Submitted by:
373 * Reviewed by:
374 *
375 * Add authentication mechanism that uses H(username:domain:password)
376 *
377 * Revision 1.22  2009/07/17 18:58:02  emcho
378 * Converts indentation tabs to spaces so that we have a uniform indentation policy in the whole project.
379 *
380 * Revision 1.21  2008/05/24 04:10:01  mranga
381 *
382 * Issue number:   158
383 * Obtained from:
384 * Submitted by:
385 * Reviewed by:   mranga
386 *
387 * Deliver tx timeout for Canceled INVITE. Fix pipeline thread exit.
388 *
389 * Revision 1.20  2008/05/22 19:38:07  jbemmel
390 * Fix for issue 149: the logic wasn't always closing the internal socket pipe,
391 * causing the pipe reader thread to block indefinitely
392 *
393 * Repeatedly starting/stopping the stack then gives hanging threads
394 * Revision 1.19 2007/01/28 13:06:21 mranga
395 * Issue number: 99 Obtained from: Submitted by: Reviewed by: mranga
396 *
397 * Fixed PRACK handling null pointer exception (for proxy case) and cleanup of
398 * unused variables.
399 *
400 * CVS: ----------------------------------------------------------------------
401 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
402 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
403 * has been taken from another system, CVS: then name the system in this line,
404 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
405 * contributed to the project by someone else; i.e., CVS: they sent us a patch
406 * or a set of diffs, then include their name/email CVS: address here. If this
407 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
408 * pre-commit code reviews and someone else has CVS: reviewed your changes,
409 * include their name(s) here. CVS: If you have not had it reviewed then delete
410 * this line.
411 *
412 * Revision 1.18 2006/07/13 09:02:10 mranga Issue number: Obtained from:
413 * Submitted by: jeroen van bemmel Reviewed by: mranga Moved some changes from
414 * jain-sip-1.2 to java.net
415 *
416 * CVS: ----------------------------------------------------------------------
417 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
418 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
419 * has been taken from another system, CVS: then name the system in this line,
420 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
421 * contributed to the project by someone else; i.e., CVS: they sent us a patch
422 * or a set of diffs, then include their name/email CVS: address here. If this
423 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
424 * pre-commit code reviews and someone else has CVS: reviewed your changes,
425 * include their name(s) here. CVS: If you have not had it reviewed then delete
426 * this line.
427 *
428 * Revision 1.4 2006/06/19 06:47:27 mranga javadoc fixups
429 *
430 * Revision 1.3 2006/06/17 10:18:14 mranga Added some synchronization to the
431 * sequence number checking. Small javadoc fixups
432 *
433 * Revision 1.2 2006/06/16 15:26:28 mranga Added NIST disclaimer to all public
434 * domain files. Clean up some javadoc. Fixed a leak
435 *
436 * Revision 1.1.1.1 2005/10/04 17:12:35 mranga
437 *
438 * Import
439 *
440 *
441 * Revision 1.16 2004/11/30 23:28:14 mranga Issue number: 44 Submitted by: Rob
442 * Daugherty Reviewed by: M. Ranganathan
443 *
444 * TCP Pipelining truncates content when other end of pipe is closed.
445 *
446 * Revision 1.15 2004/05/30 18:55:56 mranga Reviewed by: mranga Move to timers
447 * and eliminate the Transaction scanner Thread to improve scalability and
448 * reduce cpu usage.
449 *
450 * Revision 1.14 2004/05/16 14:13:22 mranga Reviewed by: mranga Fixed the
451 * use-count issue reported by Peter Parnes. Added property to prevent against
452 * content-length dos attacks.
453 *
454 * Revision 1.13 2004/03/19 04:22:22 mranga Reviewed by: mranga Added IO Pacing
455 * for long writes - split write into chunks and flush after each chunk to avoid
456 * socket back pressure.
457 *
458 * Revision 1.12 2004/03/18 22:01:19 mranga Reviewed by: mranga Get rid of the
459 * PipedInputStream from pipelined parser to avoid a copy.
460 *
461 * Revision 1.11 2004/03/07 22:25:23 mranga Reviewed by: mranga Added a new
462 * configuration parameter that instructs the stack to drop a server connection
463 * after server transaction termination set
464 * gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS=false for this Default behavior
465 * is true.
466 *
467 * Revision 1.10 2004/02/29 15:32:58 mranga Reviewed by: mranga bug fixes on
468 * limiting the max message size.
469 *
470 * Revision 1.9 2004/02/29 00:46:34 mranga Reviewed by: mranga Added new
471 * configuration property to limit max message size for TCP transport. The
472 * property is gov.nist.javax.sip.MAX_MESSAGE_SIZE
473 *
474 * Revision 1.8 2004/02/25 21:43:03 mranga Reviewed by: mranga Added a couple of
475 * todo's and removed some debug printlns that could slow code down by a bit.
476 *
477 * Revision 1.7 2004/02/25 20:52:46 mranga Reviewed by: mranga Fix TCP transport
478 * so messages in excess of 8192 bytes are accepted.
479 *
480 * Revision 1.6 2004/01/22 18:39:41 mranga Reviewed by: M. Ranganathan Moved the
481 * ifdef SIMULATION and associated tags to the first column so Prep preprocessor
482 * can deal with them.
483 *
484 * Revision 1.5 2004/01/22 14:23:45 mranga Reviewed by: mranga Fixed some minor
485 * formatting issues.
486 *
487 * Revision 1.4 2004/01/22 13:26:31 sverker Issue number: Obtained from:
488 * Submitted by: sverker Reviewed by: mranga
489 *
490 * Major reformat of code to conform with style guide. Resolved compiler and
491 * javadoc warnings. Added CVS tags.
492 *
493 * CVS: ----------------------------------------------------------------------
494 * CVS: Issue number: CVS: If this change addresses one or more issues, CVS:
495 * then enter the issue number(s) here. CVS: Obtained from: CVS: If this change
496 * has been taken from another system, CVS: then name the system in this line,
497 * otherwise delete it. CVS: Submitted by: CVS: If this code has been
498 * contributed to the project by someone else; i.e., CVS: they sent us a patch
499 * or a set of diffs, then include their name/email CVS: address here. If this
500 * is your work then delete this line. CVS: Reviewed by: CVS: If we are doing
501 * pre-commit code reviews and someone else has CVS: reviewed your changes,
502 * include their name(s) here. CVS: If you have not had it reviewed then delete
503 * this line.
504 *
505 */
506