Frame numbers are unsigned.
[obnox/wireshark/wip.git] / packet-rtsp.c
index 62207931ddcae01715b31625cc57988f8a074bfd..5c95cad9655448dcc8837f1fc45d74ae19ceeaec 100644 (file)
  * Jason Lango <jal@netapp.com>
  * Liberally copied from packet-http.c, by Guy Harris <guy@alum.mit.edu>
  *
- * $Id: packet-rtsp.c,v 1.17 2000/08/21 18:36:33 guy Exp $
+ * $Id: packet-rtsp.c,v 1.50 2002/08/28 21:00:30 jmayer Exp $
  *
  * Ethereal - Network traffic analyzer
- * By Gerald Combs <gerald@zing.org>
+ * By Gerald Combs <gerald@ethereal.com>
  * Copyright 1998 Gerald Combs
  *
- * 
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
- *
- *
  */
 
 #include "config.h"
 
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
 #include <string.h>
 #include <ctype.h>
+#include <stdlib.h>
 
 #include <glib.h>
-#include "packet.h"
-#include "packet-sdp.h"
+#include <epan/packet.h>
 #include "packet-rtp.h"
 #include "packet-rtcp.h"
-#include "conversation.h"
+#include <epan/conversation.h>
+#include <epan/strutil.h>
 
 static int proto_rtsp = -1;
 static gint ett_rtsp = -1;
 
+static gint ett_rtspframe = -1;
+
 static int hf_rtsp_method = -1;
 static int hf_rtsp_url = -1;
 static int hf_rtsp_status = -1;
 
+static dissector_handle_t sdp_handle;
+static dissector_handle_t rtp_handle;
+static dissector_handle_t rtcp_handle;
+
+static GMemChunk *rtsp_vals = NULL;
+#define rtsp_hash_init_count 20
+
 #define TCP_PORT_RTSP                  554
 
-static int process_rtsp_request_or_reply(const u_char *data, int offset,
-       int linelen, proto_tree *tree);
+/*
+ * Takes an array of bytes, assumed to contain a null-terminated
+ * string, as an argument, and returns the length of the string -
+ * i.e., the size of the array, minus 1 for the null terminator.
+ */
+#define STRLEN_CONST(str)      (sizeof (str) - 1)
+
+#define RTSP_FRAMEHDR  ('$')
+
+typedef struct {
+       dissector_handle_t              dissector;
+} rtsp_interleaved_t;
+
+#define RTSP_MAX_INTERLEAVED           (8)
+
+/*
+ * Careful about dynamically allocating memory in this structure (say
+ * for dynamically increasing the size of the 'interleaved' array) -
+ * the containing structure is garbage collected and contained
+ * pointers will not be freed.
+ */
+typedef struct {
+       rtsp_interleaved_t              interleaved[RTSP_MAX_INTERLEAVED];
+} rtsp_conversation_data_t;
 
 static int
-is_content_sdp(const u_char *line, int linelen)
+dissect_rtspinterleaved(tvbuff_t *tvb, int offset, packet_info *pinfo,
+       proto_tree *tree)
 {
-       const char      *hdr = "Content-Type:";
-       size_t          hdrlen = strlen(hdr);
-       const char      *type = "application/sdp";
-       size_t          typelen = strlen(type);
+       proto_tree      *rtspframe_tree;
+       proto_item      *ti;
+       int             orig_offset;
+       guint8          rf_start;       /* always RTSP_FRAMEHDR */
+       guint8          rf_chan;        /* interleaved channel id */
+       guint16         rf_len;         /* packet length */
+       gint            framelen;
+       tvbuff_t        *next_tvb;
+       conversation_t  *conv;
+       rtsp_conversation_data_t        *data;
+       dissector_handle_t              dissector;
 
-       if (linelen < hdrlen || strncasecmp(hdr, line, hdrlen))
-               return 0;
+       orig_offset = offset;
+       rf_start = tvb_get_guint8(tvb, offset);
+       rf_chan = tvb_get_guint8(tvb, offset+1);
+       rf_len = tvb_get_ntohs(tvb, offset+2);
+
+       if (check_col(pinfo->cinfo, COL_INFO))
+               col_add_fstr(pinfo->cinfo, COL_INFO,
+                       "Interleaved channel 0x%02x, %u bytes",
+                       rf_chan, rf_len);
+
+       if (tree == NULL) {
+               /*
+                * We're not building a full protocol tree; all we care
+                * about is setting the column info.
+                */
+               return -1;
+       }
 
-       line += hdrlen;
-       linelen -= hdrlen;
+       ti = proto_tree_add_protocol_format(tree, proto_rtsp, tvb, offset, 4,
+               "RTSP Interleaved Frame, Channel: 0x%02x, %u bytes",
+               rf_chan, rf_len);
+       rtspframe_tree = proto_item_add_subtree(ti, ett_rtspframe);
+
+       proto_tree_add_text(rtspframe_tree, tvb, offset, 1,
+               "Magic: 0x%02x",
+               rf_start);
+       offset += 1;
+
+       proto_tree_add_text(rtspframe_tree, tvb, offset, 1,
+               "Channel: 0x%02x",
+               rf_chan);
+       offset += 1;
+
+       proto_tree_add_text(rtspframe_tree, tvb, offset, 2,
+               "Length: %u bytes",
+               rf_len);
+       offset += 2;
+
+       /*
+        * We set the actual length of the tvbuff for the interleaved
+        * stuff to the minimum of what's left in the tvbuff and the
+        * length in the header.
+        *
+        * XXX - what if there's nothing left in the tvbuff?
+        * We'd want a BoundsError exception to be thrown, so
+        * that a Short Frame would be reported.
+        */
+       framelen = tvb_length_remaining(tvb, offset);
+       if (framelen > rf_len)
+               framelen = rf_len;
+       next_tvb = tvb_new_subset(tvb, offset, framelen, rf_len);
+
+       conv = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
+               pinfo->srcport, pinfo->destport, 0);
+
+       if (conv &&
+           (data = conversation_get_proto_data(conv, proto_rtsp)) &&
+           rf_chan < RTSP_MAX_INTERLEAVED &&
+           (dissector = data->interleaved[rf_chan].dissector)) {
+               call_dissector(dissector, next_tvb, pinfo, tree);
+       } else {
+               proto_tree_add_text(rtspframe_tree, tvb, offset, rf_len,
+                       "Data (%u bytes)", rf_len);
+       }
+
+       offset += rf_len;
+
+       return offset - orig_offset;
+}
+
+static void process_rtsp_request(tvbuff_t *tvb, int offset, const guchar *data,
+       size_t linelen, proto_tree *tree);
+
+static void process_rtsp_reply(tvbuff_t *tvb, int offset, const guchar *data,
+       size_t linelen, proto_tree *tree);
+
+typedef enum {
+       RTSP_REQUEST,
+       RTSP_REPLY,
+       NOT_RTSP
+} rtsp_type_t;
+
+static const char *rtsp_methods[] = {
+       "DESCRIBE", "ANNOUNCE", "GET_PARAMETER", "OPTIONS",
+       "PAUSE", "PLAY", "RECORD", "REDIRECT", "SETUP",
+       "SET_PARAMETER", "TEARDOWN"
+};
+
+#define RTSP_NMETHODS  (sizeof rtsp_methods / sizeof rtsp_methods[0])
+
+static rtsp_type_t
+is_rtsp_request_or_reply(const guchar *line, size_t linelen)
+{
+       unsigned        ii;
+
+       /* Is this an RTSP reply? */
+       if (linelen >= 5 && strncasecmp("RTSP/", line, 5) == 0) {
+               /*
+                * Yes.
+                */
+               return RTSP_REPLY;
+       }
+
+       /*
+        * Is this an RTSP request?
+        * Check whether the line begins with one of the RTSP request
+        * methods.
+        */
+       for (ii = 0; ii < RTSP_NMETHODS; ii++) {
+               size_t len = strlen(rtsp_methods[ii]);
+               if (linelen >= len &&
+                   strncasecmp(rtsp_methods[ii], line, len) == 0)
+                       return RTSP_REQUEST;
+       }
+       return NOT_RTSP;
+}
+
+static const char rtsp_content_type[] = "Content-Type:";
+
+static int
+is_content_sdp(const guchar *line, size_t linelen)
+{
+       static const char type[] = "application/sdp";
+       size_t          typelen = STRLEN_CONST(type);
+
+       line += STRLEN_CONST(rtsp_content_type);
+       linelen -= STRLEN_CONST(rtsp_content_type);
        while (linelen > 0 && (*line == ' ' || *line == '\t')) {
                line++;
                linelen--;
        }
 
        if (linelen < typelen || strncasecmp(type, line, typelen))
-               return 0;
+               return FALSE;
 
        line += typelen;
        linelen -= typelen;
        if (linelen > 0 && !isspace(*line))
-               return 0;
+               return FALSE;
 
-       return 1;
+       return TRUE;
 }
 
 static const char rtsp_transport[] = "Transport:";
 static const char rtsp_sps[] = "server_port=";
 static const char rtsp_cps[] = "client_port=";
-static const char rtsp_rtp[] = "rtp/avp";
+static const char rtsp_rtp[] = "rtp/";
+static const char rtsp_inter[] = "interleaved=";
 
 static void
-rtsp_create_conversation(const u_char *trans_begin, const u_char *trans_end)
+rtsp_create_conversation(packet_info *pinfo, const guchar *line_begin,
+       size_t line_len)
 {
        conversation_t  *conv;
-       u_char          tbuf[256];
-       u_char          *tmp;
-       int             c_data_port, c_mon_port;
-       int             s_data_port, s_mon_port;
+       guchar          buf[256];
+       guchar          *tmp;
+       guint           c_data_port, c_mon_port;
+       guint           s_data_port, s_mon_port;
+       address         null_addr;
 
-       strncpy(tbuf, trans_begin, trans_end - trans_begin);
-       tbuf[sizeof(tbuf)-1] = 0;
+       if (line_len > sizeof(buf) - 1) {
+               /*
+                * Don't overflow the buffer.
+                */
+               line_len = sizeof(buf) - 1;
+       }
+       memcpy(buf, line_begin, line_len);
+       buf[line_len] = '\0';
 
-       tmp = tbuf + strlen(rtsp_transport);
+       tmp = buf + STRLEN_CONST(rtsp_transport);
        while (*tmp && isspace(*tmp))
                tmp++;
-       if (strncasecmp(tmp, rtsp_rtp, strlen(rtsp_rtp)) != 0)
+       if (strncasecmp(tmp, rtsp_rtp, strlen(rtsp_rtp)) != 0) {
+               g_warning("Frame %u: rtsp: unknown transport", pinfo->fd->num);
                return;
+       }
 
        c_data_port = c_mon_port = 0;
        s_data_port = s_mon_port = 0;
-       if ((tmp = strstr(tbuf, rtsp_sps))) {
+       if ((tmp = strstr(buf, rtsp_sps))) {
                tmp += strlen(rtsp_sps);
-               if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1)
-                       g_warning("rtsp: failed to parse server_port");
+               if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1) {
+                       g_warning("Frame %u: rtsp: bad server_port",
+                               pinfo->fd->num);
+                       return;
+               }
        }
-       if ((tmp = strstr(tbuf, rtsp_cps))) {
+       if ((tmp = strstr(buf, rtsp_cps))) {
                tmp += strlen(rtsp_cps);
-               if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1)
-                       g_warning("rtsp: failed to parse client_port");
+               if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1) {
+                       g_warning("Frame %u: rtsp: bad client_port",
+                               pinfo->fd->num);
+                       return;
+               }
        }
-       if (!c_data_port || !s_data_port)
+       if (!c_data_port) {
+               rtsp_conversation_data_t        *data;
+               guint                           s_data_chan, s_mon_chan;
+               int                             i;
+
+               /*
+                * Deal with RTSP TCP-interleaved conversations.
+                */
+               if ((tmp = strstr(buf, rtsp_inter)) == NULL) {
+                       /*
+                        * No interleaved or server_port - probably a
+                        * SETUP request, rather than reply.
+                        */
+                       return;
+               }
+               tmp += strlen(rtsp_inter);
+               i = sscanf(tmp, "%u-%u", &s_data_chan, &s_mon_chan);
+               if (i < 1) {
+                       g_warning("Frame %u: rtsp: bad interleaved",
+                               pinfo->fd->num);
+                       return;
+               }
+               conv = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
+                       pinfo->srcport, pinfo->destport, 0);
+               if (!conv) {
+                       conv = conversation_new(&pinfo->src, &pinfo->dst,
+                               pinfo->ptype, pinfo->srcport, pinfo->destport,
+                               0);
+               }
+               data = conversation_get_proto_data(conv, proto_rtsp);
+               if (!data) {
+                       data = g_mem_chunk_alloc(rtsp_vals);
+                       conversation_add_proto_data(conv, proto_rtsp, data);
+               }
+               if (s_data_chan < RTSP_MAX_INTERLEAVED) {
+                       data->interleaved[s_data_chan].dissector =
+                               rtp_handle;
+               }
+               if (i > 1 && s_mon_chan < RTSP_MAX_INTERLEAVED) {
+                       data->interleaved[s_mon_chan].dissector =
+                               rtcp_handle;
+               }
                return;
+       }
+
+       /*
+        * We only want to match on the destination address, not the
+        * source address, because the server might send back a packet
+        * from an address other than the address to which its client
+        * sent the packet, so we construct a conversation with no
+        * second address.
+        */
+       SET_ADDRESS(&null_addr, pinfo->src.type, 0, NULL);
 
-       conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_data_port,
-               c_data_port, 0);
-       old_conversation_set_dissector(conv, dissect_rtp);
+       conv = conversation_new(&pinfo->dst, &null_addr, PT_UDP, c_data_port,
+               s_data_port, NO_ADDR2 | (!s_data_port ? NO_PORT2 : 0));
+       conversation_set_dissector(conv, rtp_handle);
 
-       if (!c_mon_port || !s_mon_port)
+       if (!c_mon_port)
                return;
 
-       conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_mon_port,
-               c_mon_port, 0);
-       old_conversation_set_dissector(conv, dissect_rtcp);
+       conv = conversation_new(&pinfo->dst, &null_addr, PT_UDP, c_mon_port,
+               s_mon_port, NO_ADDR2 | (!s_mon_port ? NO_PORT2 : 0));
+       conversation_set_dissector(conv, rtcp_handle);
 }
 
-static void dissect_rtsp(const u_char *pd, int offset, frame_data *fd,
-       proto_tree *tree)
+static const char rtsp_content_length[] = "Content-Length:";
+
+static int
+rtsp_get_content_length(const guchar *line_begin, size_t line_len)
 {
-       proto_tree      *rtsp_tree;
-       proto_item      *ti;
-       const u_char    *data, *dataend;
-       const u_char    *linep, *lineend, *eol;
-       int             linelen;
-       u_char          c;
-       int             is_sdp = 0;
-       int             end_offset;
+       guchar          buf[256];
+       guchar          *tmp;
+       long            content_length;
+       char            *p;
+       guchar          *up;
 
-       OLD_CHECK_DISPLAY_AS_DATA(proto_rtsp, pd, offset, fd, tree);
+       if (line_len > sizeof(buf) - 1) {
+               /*
+                * Don't overflow the buffer.
+                */
+               line_len = sizeof(buf) - 1;
+       }
+       memcpy(buf, line_begin, line_len);
+       buf[line_len] = '\0';
 
-       data = &pd[offset];
-       dataend = data + END_OF_FRAME;
-       end_offset = offset + END_OF_FRAME;
+       tmp = buf + STRLEN_CONST(rtsp_content_length);
+       while (*tmp && isspace(*tmp))
+               tmp++;
+       content_length = strtol(tmp, &p, 10);
+       up = p;
+       if (up == tmp || (*up != '\0' && !isspace(*up)))
+               return -1;      /* not a valid number */
+       return content_length;
+}
 
+static int
+dissect_rtspmessage(tvbuff_t *tvb, int offset, packet_info *pinfo,
+       proto_tree *tree)
+{
+       proto_tree      *rtsp_tree;
+       proto_item      *ti = NULL;
+       const guchar    *line;
+       gint            next_offset;
+       const guchar    *linep, *lineend;
+       int             orig_offset;
+       size_t          linelen;
+       guchar          c;
+       gboolean        is_mime_header;
+       int             is_sdp = FALSE;
+       int             datalen;
+       int             content_length;
+       int             reported_datalen;
+
+       orig_offset = offset;
        rtsp_tree = NULL;
        if (tree) {
-               ti = proto_tree_add_item(tree, proto_rtsp, NullTVB, offset, 
-                       END_OF_FRAME, FALSE);
+               ti = proto_tree_add_item(tree, proto_rtsp, tvb, offset, -1,
+                   FALSE);
                rtsp_tree = proto_item_add_subtree(ti, ett_rtsp);
        }
 
-       if (check_col(fd, COL_PROTOCOL))
-               col_add_str(fd, COL_PROTOCOL, "RTSP");
-       if (check_col(fd, COL_INFO)) {
+       if (check_col(pinfo->cinfo, COL_INFO)) {
                /*
                 * Put the first line from the buffer into the summary
-                * if it's an RTSP request or reply. Otherwise, just call 
-                * it a continuation.
+                * if it's an RTSP request or reply (but leave out the
+                * line terminator).
+                * Otherwise, just call it a continuation.
                 */
-               lineend = find_line_end(data, dataend, &eol);
-               linelen = lineend - data;
-               if (process_rtsp_request_or_reply(data, offset, linelen,
-                               rtsp_tree))
-                       col_add_str(fd, COL_INFO, format_text(data, linelen));
-               else
-                       col_add_str(fd, COL_INFO, "Continuation");
+               linelen = tvb_find_line_end(tvb, offset, -1, &next_offset,
+                   FALSE);
+               line = tvb_get_ptr(tvb, offset, linelen);
+               switch (is_rtsp_request_or_reply(line, linelen)) {
+
+               case RTSP_REQUEST:
+               case RTSP_REPLY:
+                       col_add_str(pinfo->cinfo, COL_INFO,
+                           format_text(line, linelen));
+                       break;
+
+               default:
+                       col_set_str(pinfo->cinfo, COL_INFO, "Continuation");
+                       break;
+               }
        }
 
-       if (offset >= end_offset)
-               goto bad_len;
-       while (data < dataend) {
+       /*
+        * We haven't yet seen a Content-Length header.
+        */
+       content_length = -1;
+
+       /*
+        * Process the packet data, a line at a time.
+        */
+       while (tvb_offset_exists(tvb, offset)) {
+               /*
+                * We haven't yet concluded that this is a MIME header.
+                */
+               is_mime_header = FALSE;
+
                /*
                 * Find the end of the line.
                 */
-               lineend = find_line_end(data, dataend, &eol);
-               linelen = lineend - data;
+               linelen = tvb_find_line_end(tvb, offset, -1, &next_offset,
+                   FALSE);
+
+               /*
+                * Get a buffer that refers to the line.
+                */
+               line = tvb_get_ptr(tvb, offset, linelen);
+               lineend = line + linelen;
 
                /*
                 * OK, does it look like an RTSP request or
                 * response?
                 */
-               if (process_rtsp_request_or_reply(data, offset, linelen,
-                               rtsp_tree))
+               switch (is_rtsp_request_or_reply(line, linelen)) {
+
+               case RTSP_REQUEST:
+                       if (rtsp_tree != NULL)
+                               process_rtsp_request(tvb, offset, line, linelen,
+                                   rtsp_tree);
+                       goto is_rtsp;
+
+               case RTSP_REPLY:
+                       if (rtsp_tree != NULL)
+                               process_rtsp_reply(tvb, offset, line, linelen,
+                                   rtsp_tree);
                        goto is_rtsp;
 
+               case NOT_RTSP:
+                       break;
+               }
+
                /*
                 * No.  Does it look like a blank line (as would
                 * appear at the end of an RTSP request)?
                 */
-               if (linelen == 1) {
-                       if (*data == '\n')
-                               goto is_rtsp;
-               }
-               if (linelen == 2) {
-                       if (strncmp(data, "\r\n", 2) == 0 ||
-                           strncmp(data, "\n\r", 2) == 0)
-                               goto is_rtsp;
-               }
+               if (linelen == 0)
+                       goto is_rtsp;   /* Yes. */
 
                /*
                 * No.  Does it look like a MIME header?
                 */
-               linep = data;
+               linep = line;
                while (linep < lineend) {
                        c = *linep++;
                        if (!isprint(c))
@@ -247,8 +532,15 @@ static void dissect_rtsp(const u_char *pd, int offset, frame_data *fd,
                                 * This ends the token; we consider
                                 * this to be a MIME header.
                                 */
-                               if (is_content_sdp(data, linelen))
-                                       is_sdp = 1;
+                               is_mime_header = TRUE;
+                               goto is_rtsp;
+
+                       case ' ':
+                       case '\t':
+                               /*
+                                * LWS (RFC-2616, 4.2); continue the previous
+                                * header.
+                                */
                                goto is_rtsp;
                        }
                }
@@ -265,128 +557,281 @@ static void dissect_rtsp(const u_char *pd, int offset, frame_data *fd,
                 * Put this line.
                 */
                if (rtsp_tree) {
-                       proto_tree_add_text(rtsp_tree, NullTVB, offset, linelen, "%s",
-                               format_text(data, linelen));
+                       proto_tree_add_text(rtsp_tree, tvb, offset,
+                           next_offset - offset, "%s",
+                           tvb_format_text(tvb, offset, next_offset - offset));
                }
-               if (linelen > strlen(rtsp_transport) &&
-                       strncasecmp(data, rtsp_transport,
-                               strlen(rtsp_transport)) == 0)
-                       rtsp_create_conversation(data, data + linelen);
-               offset += linelen;
-               data = lineend;
+               if (is_mime_header) {
+                       /*
+                        * Process some MIME headers specially.
+                        */
+#define MIME_HDR_MATCHES(header) \
+       (linelen > STRLEN_CONST(header) && \
+        strncasecmp(line, (header), STRLEN_CONST(header)) == 0)
+
+                       if (MIME_HDR_MATCHES(rtsp_transport)) {
+                               /*
+                                * Based on the port numbers specified
+                                * in the Transport: header, set up
+                                * a conversation that will be dissected
+                                * with the appropriate dissector.
+                                */
+                               rtsp_create_conversation(pinfo, line, linelen);
+                       } else if (MIME_HDR_MATCHES(rtsp_content_type)) {
+                               /*
+                                * If the Content-Type: header says this
+                                * is SDP, dissect the payload as SDP.
+                                */
+                               if (is_content_sdp(line, linelen))
+                                       is_sdp = TRUE;
+                       } else if (MIME_HDR_MATCHES(rtsp_content_length)) {
+                               /*
+                                * Only the amount specified by the
+                                * Content-Length: header should be treated
+                                * as payload.
+                                */
+                               content_length = rtsp_get_content_length(line,
+                                   linelen);
+                       }
+               }
+               offset = next_offset;
        }
 
-       if (is_sdp) {
-               dissect_sdp(pd, offset, fd, tree);
-               if (check_col(fd, COL_PROTOCOL))
-                       col_add_str(fd, COL_PROTOCOL, "RTSP/SDP");
-       }
-       else if (data < dataend) {
-               proto_tree_add_text(rtsp_tree, NullTVB, offset, END_OF_FRAME,
-                   "Data (%d bytes)", END_OF_FRAME);
+       /*
+        * If a content length was supplied, the amount of data to be
+        * processed as RTSP payload is the minimum of the content
+        * length and the amount of data remaining in the frame.
+        *
+        * If no content length was supplied, the amount of data to be
+        * processed is the amount of data remaining in the frame.
+        */
+       datalen = tvb_length_remaining(tvb, offset);
+       if (content_length != -1) {
+               if (datalen > content_length)
+                       datalen = content_length;
+
+               /*
+                * XXX - for now, if the content length is greater
+                * than the amount of data left in this frame (not
+                * the amount of *captured* data left in the frame
+                * minus the current offset, but the amount of *actual*
+                * data that was reported to be in the frame minus
+                * the current offset), limit it to the amount
+                * of data left in this frame.
+                *
+                * If we ever handle data that crosses frame
+                * boundaries, we'll need to remember the actual
+                * content length.
+                */
+               reported_datalen = tvb_reported_length_remaining(tvb, offset);
+               if (content_length > reported_datalen)
+                       content_length = reported_datalen;
        }
-       return;
 
-bad_len:
-       proto_tree_add_text(rtsp_tree, NullTVB, end_offset, 0,
-               "Unexpected end of packet");
-}
+       if (datalen > 0) {
+               /*
+                * There's stuff left over; process it.
+                */
+               if (is_sdp) {
+                       tvbuff_t *new_tvb;
+
+                       /*
+                        * Fix up the top-level item so that it doesn't
+                        * include the SDP stuff.
+                        */
+                       if (ti != NULL)
+                               proto_item_set_len(ti, offset);
+
+                       /*
+                        * Now create a tvbuff for the SDP stuff and
+                        * dissect it.
+                        *
+                        * The amount of data to be processed that's
+                        * available in the tvbuff is "datalen", which
+                        * is the minimum of the amount of data left in
+                        * the tvbuff and any specified content length.
+                        *
+                        * The amount of data to be processed that's in
+                        * this frame, regardless of whether it was
+                        * captured or not, is "content_length",
+                        * which, if no content length was specified,
+                        * is -1, i.e. "to the end of the frame.
+                        */
+                       new_tvb = tvb_new_subset(tvb, offset, datalen,
+                           content_length);
+                       call_dissector(sdp_handle, new_tvb, pinfo, tree);
+               } else {
+                       if (tvb_get_guint8(tvb, offset) == RTSP_FRAMEHDR) {
+                               /*
+                                * This is interleaved stuff; don't
+                                * treat it as raw data - set "datalen"
+                                * to 0, so we won't skip the offset
+                                * past it, which will cause our
+                                * caller to process that stuff itself.
+                                */
+                               datalen = 0;
+                       } else {
+                               proto_tree_add_text(rtsp_tree, tvb, offset,
+                                   datalen, "Data (%d bytes)", datalen);
+                       }
+               }
 
-const char *rtsp_methods[] = {
-       "DESCRIBE", "ANNOUNCE", "GET_PARAMETER", "OPTIONS",
-       "PAUSE", "PLAY", "RECORD", "REDIRECT", "SETUP",
-       "SET_PARAMETER", "TEARDOWN"
-};
-const int rtsp_nmethods = sizeof(rtsp_methods) / sizeof(*rtsp_methods);
+               /*
+                * We've processed "datalen" bytes worth of data
+                * (which may be no data at all); advance the
+                * offset past whatever data we've processed, so they
+                * don't process it.
+                */
+               offset += datalen;
+       }
+       return offset - orig_offset;
+}
 
-static int
-process_rtsp_request_or_reply(const u_char *data, int offset, int linelen,
-       proto_tree *tree)
+static void
+process_rtsp_request(tvbuff_t *tvb, int offset, const guchar *data,
+       size_t linelen, proto_tree *tree)
 {
-       int             ii;
-       const u_char    *lineend = data + linelen;
-
-       /* Reply */
-       if (linelen >= 5 && !strncasecmp("RTSP/", data, 5)) {
-               if (tree) {
-                       /* status code */
-                       const u_char *status = data;
-                       const u_char *status_start;
-                       unsigned int status_i = 0;
-                       while (status < lineend && !isspace(*status))
-                               status++;
-                       while (status < lineend && isspace(*status))
-                               status++;
-                       status_start = status;
-                       while (status < lineend && isdigit(*status))
-                               status_i = status_i * 10 + *status++ - '0';
-                       proto_tree_add_uint_hidden(tree, hf_rtsp_status, NullTVB,
-                               offset + (status_start - data),
-                               status - status_start, status_i);
-               }
-               return TRUE;
-       }
+       const guchar    *lineend = data + linelen;
+       unsigned        ii;
+       const guchar    *url;
+       const guchar    *url_start;
+       guchar          *tmp_url;
 
        /* Request Methods */
-       for (ii = 0; ii < rtsp_nmethods; ii++) {
+       for (ii = 0; ii < RTSP_NMETHODS; ii++) {
                size_t len = strlen(rtsp_methods[ii]);
                if (linelen >= len && !strncasecmp(rtsp_methods[ii], data, len))
                        break;
        }
-       if (ii == rtsp_nmethods)
-               return FALSE;
+       if (ii == RTSP_NMETHODS) {
+               /*
+                * We got here because "is_rtsp_request_or_reply()" returned
+                * RTSP_REQUEST, so we know one of the request methods
+                * matched, so we "can't get here".
+                */
+               g_assert_not_reached();
+       }
 
-       if (tree) {
-               const u_char *url;
-               const u_char *url_start;
-               u_char *tmp_url;
-
-               /* method name */
-               proto_tree_add_string_hidden(tree, hf_rtsp_method, NullTVB, offset,
-                       strlen(rtsp_methods[ii]), rtsp_methods[ii]);
-
-               /* URL */
-               url = data;
-               while (url < lineend && !isspace(*url))
-                       url++;
-               while (url < lineend && isspace(*url))
-                       url++;
-               url_start = url;
-               while (url < lineend && !isspace(*url))
-                       url++;
-               tmp_url = g_malloc(url - url_start + 1);
-               memcpy(tmp_url, url_start, url - url_start);
-               tmp_url[url - url_start] = 0;
-               proto_tree_add_string_hidden(tree, hf_rtsp_url, NullTVB,
-                       offset + (url_start - data), url - url_start, tmp_url);
-               g_free(tmp_url);
+       /* Method name */
+       proto_tree_add_string_hidden(tree, hf_rtsp_method, tvb, offset,
+               strlen(rtsp_methods[ii]), rtsp_methods[ii]);
+
+       /* URL */
+       url = data;
+       while (url < lineend && !isspace(*url))
+               url++;
+       while (url < lineend && isspace(*url))
+               url++;
+       url_start = url;
+       while (url < lineend && !isspace(*url))
+               url++;
+       tmp_url = g_malloc(url - url_start + 1);
+       memcpy(tmp_url, url_start, url - url_start);
+       tmp_url[url - url_start] = 0;
+       proto_tree_add_string_hidden(tree, hf_rtsp_url, tvb,
+               offset + (url_start - data), url - url_start, tmp_url);
+       g_free(tmp_url);
+}
+
+static void
+process_rtsp_reply(tvbuff_t *tvb, int offset, const guchar *data,
+       size_t linelen, proto_tree *tree)
+{
+       const guchar    *lineend = data + linelen;
+       const guchar    *status = data;
+       const guchar    *status_start;
+       unsigned int    status_i;
+
+       /* status code */
+       while (status < lineend && !isspace(*status))
+               status++;
+       while (status < lineend && isspace(*status))
+               status++;
+       status_start = status;
+       status_i = 0;
+       while (status < lineend && isdigit(*status))
+               status_i = status_i * 10 + *status++ - '0';
+       proto_tree_add_uint_hidden(tree, hf_rtsp_status, tvb,
+               offset + (status_start - data),
+               status - status_start, status_i);
+}
+
+static void
+dissect_rtsp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+       int             offset = 0;
+       int             len;
+
+       if (check_col(pinfo->cinfo, COL_PROTOCOL))
+               col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTSP");
+       if (check_col(pinfo->cinfo, COL_INFO))
+               col_clear(pinfo->cinfo, COL_INFO);
+
+       while (tvb_offset_exists(tvb, offset)) {
+               len = (tvb_get_guint8(tvb, offset) == RTSP_FRAMEHDR)
+                       ? dissect_rtspinterleaved(tvb, offset, pinfo, tree)
+                       : dissect_rtspmessage(tvb, offset, pinfo, tree);
+               if (len == -1)
+                       break;
+               offset += len;
+
+               /*
+                * OK, we've set the Protocol and Info columns for the
+                * first RTSP message; make the columns non-writable,
+                * so that we don't change it for subsequent RTSP messages.
+                */
+               col_set_writable(pinfo->cinfo, FALSE);
        }
-       return TRUE;
+}
+
+static void
+rtsp_init(void)
+{
+/* Routine to initialize rtsp protocol before each capture or filter pass. */
+/* Release any memory if needed.  Then setup the memory chunks.                */
+
+       if (rtsp_vals)
+               g_mem_chunk_destroy(rtsp_vals);
+
+       rtsp_vals = g_mem_chunk_new("rtsp_vals",
+               sizeof(rtsp_conversation_data_t),
+               rtsp_hash_init_count * sizeof(rtsp_conversation_data_t),
+               G_ALLOC_AND_FREE);
 }
 
 void
 proto_register_rtsp(void)
 {
        static gint *ett[] = {
+               &ett_rtspframe,
                &ett_rtsp,
        };
        static hf_register_info hf[] = {
        { &hf_rtsp_method,
-       { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0 }},
+       { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0, "", HFILL }},
        { &hf_rtsp_url,
-       { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0 }},
+       { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0, "", HFILL }},
        { &hf_rtsp_status,
-       { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0 }},
+       { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0, "", HFILL }},
        };
 
         proto_rtsp = proto_register_protocol("Real Time Streaming Protocol",
-               "rtsp");
+               "RTSP", "rtsp");
        proto_register_field_array(proto_rtsp, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
+
+       register_init_routine(rtsp_init);       /* register re-init routine */
 }
 
 void
 proto_reg_handoff_rtsp(void)
 {
-       old_dissector_add("tcp.port", TCP_PORT_RTSP, dissect_rtsp);
+       dissector_handle_t rtsp_handle;
+
+       rtsp_handle = create_dissector_handle(dissect_rtsp, proto_rtsp);
+       dissector_add("tcp.port", TCP_PORT_RTSP, rtsp_handle);
+
+       sdp_handle = find_dissector("sdp");
+       rtp_handle = find_dissector("rtp");
+       rtcp_handle = find_dissector("rtcp");
 }