/* packet-rtmpt.c
 * Routines for Real Time Messaging Protocol packet dissection
 * metatech <metatech@flashmail.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*  This dissector is called RTMPT to avoid a conflict with
*   the other RTMP protocol (Routing Table Maintenance Protocol) implemented in packet-atalk.c
*   (RTMPT normally stands for RTMP-Tunnel via http)
*
*   RTMP in a nutshell
*
*   The protocol has very few "magic words" to facilitate detection,
*   but rather has "magic lengths".
*   This protocol has plenty of special cases and few general rules,
*   especially regarding the lengths and the structures.
*
*   Documentation:
*      RTMP protocol description on Wiki of Red5 Open Source Flash Server at
*
*          http://trac.red5.org/wiki/Codecs/RTMPSpecification
*
*      and the pages to which it links:
*
*          http://osflash.org/documentation/rtmp
*          http://wiki.gnashdev.org/RTMP
*          http://wiki.gnashdev.org/RTMP_Messages_Decoded
*          http://www.acmewebworks.com/Downloads/openCS/TheAMF.pdf
*          http://www.gnashdev.org/files/rtmp-decoded.pdf
*
*   It's also available from Adobe at
*
*          http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf
*
*   For AMF, see:
*
*          http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf
*
*   for AMF0 and
*
*          http://amf3cplusplus.googlecode.com/svn-history/r4/trunk/doc/amf3_spec_05_05_08.pdf
*
*   for AMF3.
*
*   For FLV, see:
*
*          http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
*
*   Default TCP port is 1935
*/

#include "config.h"


#include <epan/packet.h>
#include <wsutil/pint.h>

#include <epan/prefs.h>
#include <epan/to_str.h>
#include "packet-tcp.h"

/* #define DEBUG_RTMPT 1 */

void proto_register_rtmpt(void);
void proto_reg_handoff_rtmpt(void);

void proto_register_amf(void);

static int proto_rtmpt = -1;

static int hf_rtmpt_handshake_c0 = -1;
static int hf_rtmpt_handshake_s0 = -1;
static int hf_rtmpt_handshake_c1 = -1;
static int hf_rtmpt_handshake_s1 = -1;
static int hf_rtmpt_handshake_c2 = -1;
static int hf_rtmpt_handshake_s2 = -1;

static int hf_rtmpt_header_format = -1;
static int hf_rtmpt_header_csid = -1;
static int hf_rtmpt_header_timestamp = -1;
static int hf_rtmpt_header_timestamp_delta = -1;
static int hf_rtmpt_header_body_size = -1;
static int hf_rtmpt_header_typeid = -1;
static int hf_rtmpt_header_streamid = -1;
static int hf_rtmpt_header_ets = -1;

static int hf_rtmpt_scm_chunksize = -1;
static int hf_rtmpt_scm_csid = -1;
static int hf_rtmpt_scm_seq = -1;
static int hf_rtmpt_scm_was = -1;
static int hf_rtmpt_scm_limittype = -1;

static int hf_rtmpt_ucm_eventtype = -1;

static int hf_rtmpt_function_call = -1;
static int hf_rtmpt_function_response = -1;

static int hf_rtmpt_audio_control = -1;
static int hf_rtmpt_audio_format = -1;
static int hf_rtmpt_audio_rate = -1;
static int hf_rtmpt_audio_size = -1;
static int hf_rtmpt_audio_type = -1;
static int hf_rtmpt_audio_data = -1;

static int hf_rtmpt_video_control = -1;
static int hf_rtmpt_video_type = -1;
static int hf_rtmpt_video_format = -1;
static int hf_rtmpt_video_data = -1;

static int hf_rtmpt_tag_type = -1;
static int hf_rtmpt_tag_datasize = -1;
static int hf_rtmpt_tag_timestamp = -1;
static int hf_rtmpt_tag_ets = -1;
static int hf_rtmpt_tag_streamid = -1;
static int hf_rtmpt_tag_tagsize = -1;

static gint ett_rtmpt = -1;
static gint ett_rtmpt_handshake = -1;
static gint ett_rtmpt_header = -1;
static gint ett_rtmpt_body = -1;
static gint ett_rtmpt_ucm = -1;
static gint ett_rtmpt_audio_control = -1;
static gint ett_rtmpt_video_control = -1;
static gint ett_rtmpt_tag = -1;
static gint ett_rtmpt_tag_data = -1;

static dissector_handle_t rtmpt_tcp_handle;
static dissector_handle_t rtmpt_http_handle;

static gboolean rtmpt_desegment = TRUE;

/* Native Bandwidth Detection (using the checkBandwidth(), onBWCheck(),
 * onBWDone() calls) transmits a series of increasing size packets over
 * the course of 2 seconds. On a fast link the largest packet can just
 * exceed 256KB, but setting the limit there can cause massive memory
 * usage in some scenarios. For now make it a preference, but a better fix
 * is really needed.
 * See https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=6898
 */
static guint rtmpt_max_packet_size = 32768;

#define RTMP_PORT                     1935 /* Not IANA registered */

#define RTMPT_MAGIC                   0x03
#define RTMPT_HANDSHAKE_OFFSET_1         1
#define RTMPT_HANDSHAKE_OFFSET_2      1538
#define RTMPT_HANDSHAKE_OFFSET_3      3074
#define RTMPT_HANDSHAKE_LENGTH_1      1537
#define RTMPT_HANDSHAKE_LENGTH_2      3073
#define RTMPT_HANDSHAKE_LENGTH_3      1536
#define RTMPT_DEFAULT_CHUNK_SIZE       128

#define RTMPT_ID_MAX                     65599
#define RTMPT_TYPE_HANDSHAKE_1        0x100001
#define RTMPT_TYPE_HANDSHAKE_2        0x100002
#define RTMPT_TYPE_HANDSHAKE_3        0x100003

#define RTMPT_TYPE_CHUNK_SIZE         0x01
#define RTMPT_TYPE_ABORT_MESSAGE      0x02
#define RTMPT_TYPE_ACKNOWLEDGEMENT    0x03
#define RTMPT_TYPE_UCM                0x04
#define RTMPT_TYPE_WINDOW             0x05
#define RTMPT_TYPE_PEER_BANDWIDTH     0x06
#define RTMPT_TYPE_AUDIO_DATA         0x08
#define RTMPT_TYPE_VIDEO_DATA         0x09
#define RTMPT_TYPE_DATA_AMF3          0x0F
#define RTMPT_TYPE_SHARED_AMF3        0x10
#define RTMPT_TYPE_COMMAND_AMF3       0x11
#define RTMPT_TYPE_DATA_AMF0          0x12
#define RTMPT_TYPE_SHARED_AMF0        0x13
#define RTMPT_TYPE_COMMAND_AMF0       0x14
#define RTMPT_TYPE_AGGREGATE          0x16

#define RTMPT_UCM_STREAM_BEGIN        0x00
#define RTMPT_UCM_STREAM_EOF          0x01
#define RTMPT_UCM_STREAM_DRY          0x02
#define RTMPT_UCM_SET_BUFFER          0x03
#define RTMPT_UCM_STREAM_ISRECORDED   0x04
#define RTMPT_UCM_PING_REQUEST        0x06
#define RTMPT_UCM_PING_RESPONSE       0x07

#define RTMPT_TEXT_RTMP_HEADER        "RTMP Header"
#define RTMPT_TEXT_RTMP_BODY          "RTMP Body"

static const value_string rtmpt_handshake_vals[] = {
        { RTMPT_TYPE_HANDSHAKE_1,           "Handshake C0+C1" },
        { RTMPT_TYPE_HANDSHAKE_2,           "Handshake S0+S1+S2" },
        { RTMPT_TYPE_HANDSHAKE_3,           "Handshake C2" },
        { 0, NULL }
};

static const value_string rtmpt_opcode_vals[] = {
        { RTMPT_TYPE_CHUNK_SIZE,            "Set Chunk Size" },
        { RTMPT_TYPE_ABORT_MESSAGE,         "Abort Message" },
        { RTMPT_TYPE_ACKNOWLEDGEMENT,       "Acknowledgement" },
        { RTMPT_TYPE_UCM,                   "User Control Message" },
        { RTMPT_TYPE_WINDOW,                "Window Acknowledgement Size" },
        { RTMPT_TYPE_PEER_BANDWIDTH,        "Set Peer Bandwidth" },
        { RTMPT_TYPE_AUDIO_DATA,            "Audio Data" },
        { RTMPT_TYPE_VIDEO_DATA,            "Video Data" },
        { RTMPT_TYPE_DATA_AMF3,             "AMF3 Data" },
        { RTMPT_TYPE_SHARED_AMF3,           "AMF3 Shared Object" },
        { RTMPT_TYPE_COMMAND_AMF3,          "AMF3 Command" },
        { RTMPT_TYPE_DATA_AMF0,             "AMF0 Data" },
        { RTMPT_TYPE_SHARED_AMF0,           "AMF0 Shared Object" },
        { RTMPT_TYPE_COMMAND_AMF0,          "AMF0 Command" },
        { RTMPT_TYPE_AGGREGATE,             "Aggregate" },
        { 0, NULL }
};

static const value_string rtmpt_limit_vals[] = {
/* These are a complete guess, from the order of the documented
 * options - the values aren't actually specified */
        { 0,                                "Hard" },
        { 1,                                "Soft" },
        { 2,                                "Dynamic" },
        { 0, NULL }
};

static const value_string rtmpt_ucm_vals[] = {
        { RTMPT_UCM_STREAM_BEGIN,           "Stream Begin" },
        { RTMPT_UCM_STREAM_EOF,             "Stream EOF" },
        { RTMPT_UCM_STREAM_DRY,             "Stream Dry" },
        { RTMPT_UCM_SET_BUFFER,             "Set Buffer Length" },
        { RTMPT_UCM_STREAM_ISRECORDED,      "Stream Is Recorded" },
        { RTMPT_UCM_PING_REQUEST,           "Ping Request" },
        { RTMPT_UCM_PING_RESPONSE,          "Ping Response" },
        { 0, NULL }
};

static const value_string rtmpt_tag_vals[] = {
        { RTMPT_TYPE_AUDIO_DATA,            "Audio Tag" },
        { RTMPT_TYPE_VIDEO_DATA,            "Video Tag" },
        { RTMPT_TYPE_DATA_AMF0,             "Script Tag" },
        { 0, NULL }
};

/* [Spec] http://www.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf       */
/* [DevG] http://help.adobe.com/en_US/flashmediaserver/devguide/index.html "working with Live Video" => Adding metadata to a live stream */
/* [SWF] http://www.adobe.com/content/dam/Adobe/en/devnet/swf/pdf/swf_file_format_spec_v10.pdf */
static const value_string rtmpt_audio_codecs[] = {
        {  0,                               "Uncompressed" },             /* [DevG] */
        {  1,                               "ADPCM" },                    /* [DevG] */
        {  2,                               "MP3" },                      /* [DevG] */
        {  3,                               "Uncompressed, little-endian"}, /* [SWF] */
        {  4,                               "Nellymoser 16kHz" },          /* [SWF] */
        {  5,                               "Nellymoser 8kHz" },          /* [DevG] [SWF]*/
        {  6,                               "Nellymoser" },               /* [DevG] [SWF]*/
        {  7,                               "G711A" },                    /* [Spec] */
        {  8,                               "G711U" },                    /* [Spec] */
        {  9,                               "Nellymoser 16kHz" },         /* [Spec] */
        { 10,                               "HE-AAC" },                   /* [DevG] */
        { 11,                               "SPEEX" },                    /* [DevG] */
        { 0, NULL }
};

static const value_string rtmpt_audio_rates[] = {
        { 0,                                "5.5 kHz" },
        { 1,                                "11 kHz" },
        { 2,                                "22 kHz" },
        { 3,                                "44 kHz" },
        { 0, NULL }
};

static const value_string rtmpt_audio_sizes[] = {
        { 0,                                "8 bit" },
        { 1,                                "16 bit" },
        { 0, NULL }
};

static const value_string rtmpt_audio_types[] = {
        { 0,                                "mono" },
        { 1,                                "stereo" },
        { 0, NULL }
};

/* from FLV v10.1 section E.4.3.1 */
static const value_string rtmpt_video_types[] = {
        { 1,                                "keyframe" },
        { 2,                                "inter-frame" },
        { 3,                                "disposable inter-frame" },
        { 4,                                "generated key frame" },
        { 5,                                "video info/command frame" },
        { 0, NULL }
};

/* from FLV v10.1 section E.4.3.1 */
static const value_string rtmpt_video_codecs[] = {
        { 2,                                "Sorensen H.263" },
        { 3,                                "Screen video" },
        { 4,                                "On2 VP6" },
        { 5,                                "On2 VP6+alpha" },
        { 6,                                "Screen video version 2" },
        { 7,                                "H.264" },
        { 0, NULL }
};

static int proto_amf = -1;

static int hf_amf_version = -1;
static int hf_amf_header_count = -1;
static int hf_amf_header_name = -1;
static int hf_amf_header_must_understand = -1;
static int hf_amf_header_length = -1;
/* static int hf_amf_header_value_type = -1; */
static int hf_amf_message_count = -1;
static int hf_amf_message_target_uri = -1;
static int hf_amf_message_response_uri = -1;
static int hf_amf_message_length = -1;

static int hf_amf_amf0_type = -1;
static int hf_amf_amf3_type = -1;
static int hf_amf_number = -1;
static int hf_amf_integer = -1;
static int hf_amf_boolean = -1;
static int hf_amf_stringlength = -1;
static int hf_amf_string = -1;
static int hf_amf_string_reference = -1;
static int hf_amf_object_reference = -1;
static int hf_amf_date = -1;
/* static int hf_amf_longstringlength = -1; */
static int hf_amf_longstring = -1;
static int hf_amf_xml_doc = -1;
static int hf_amf_xmllength = -1;
static int hf_amf_xml = -1;
static int hf_amf_int64 = -1;
static int hf_amf_bytearraylength = -1;
static int hf_amf_bytearray = -1;

static int hf_amf_object = -1;
static int hf_amf_traitcount = -1;
static int hf_amf_classnamelength = -1;
static int hf_amf_classname = -1;
static int hf_amf_membernamelength = -1;
static int hf_amf_membername = -1;
static int hf_amf_trait_reference = -1;
static int hf_amf_ecmaarray = -1;
static int hf_amf_strictarray = -1;
static int hf_amf_array = -1;
static int hf_amf_arraylength = -1;
static int hf_amf_arraydenselength = -1;

static int hf_amf_end_of_object_marker = -1;
static int hf_amf_end_of_associative_part = -1;
static int hf_amf_end_of_dynamic_members = -1;

static gint ett_amf = -1;
static gint ett_amf_headers = -1;
static gint ett_amf_messages = -1;
static gint ett_amf_value = -1;
static gint ett_amf_property = -1;
static gint ett_amf_string = -1;
static gint ett_amf_array_element = -1;
static gint ett_amf_traits = -1;
static gint ett_amf_trait_member = -1;

/* AMF0 type markers */
#define AMF0_NUMBER              0x00
#define AMF0_BOOLEAN             0x01
#define AMF0_STRING              0x02
#define AMF0_OBJECT              0x03
#define AMF0_MOVIECLIP           0x04
#define AMF0_NULL                0x05
#define AMF0_UNDEFINED           0x06
#define AMF0_REFERENCE           0x07
#define AMF0_ECMA_ARRAY          0x08
#define AMF0_END_OF_OBJECT       0x09
#define AMF0_STRICT_ARRAY        0x0A
#define AMF0_DATE                0x0B
#define AMF0_LONG_STRING         0x0C
#define AMF0_UNSUPPORTED         0x0D
#define AMF0_RECORDSET           0x0E
#define AMF0_XML                 0x0F
#define AMF0_TYPED_OBJECT        0x10
#define AMF0_AMF3_MARKER         0x11
#define AMF0_INT64               0x22

/* AMF3 type markers */
#define AMF3_UNDEFINED           0x00
#define AMF3_NULL                0x01
#define AMF3_FALSE               0x02
#define AMF3_TRUE                0x03
#define AMF3_INTEGER             0x04
#define AMF3_DOUBLE              0x05
#define AMF3_STRING              0x06
#define AMF3_XML_DOC             0x07
#define AMF3_DATE                0x08
#define AMF3_ARRAY               0x09
#define AMF3_OBJECT              0x0A
#define AMF3_XML                 0x0B
#define AMF3_BYTEARRAY           0x0C

static const value_string amf0_type_vals[] = {
        { AMF0_NUMBER,                "Number" },
        { AMF0_BOOLEAN,               "Boolean" },
        { AMF0_STRING,                "String" },
        { AMF0_OBJECT,                "Object" },
        { AMF0_MOVIECLIP,             "Movie clip" },
        { AMF0_NULL,                  "Null" },
        { AMF0_UNDEFINED,             "Undefined" },
        { AMF0_REFERENCE,             "Reference" },
        { AMF0_ECMA_ARRAY,            "ECMA array" },
        { AMF0_END_OF_OBJECT,         "End of object" },
        { AMF0_STRICT_ARRAY,          "Strict array" },
        { AMF0_DATE,                  "Date" },
        { AMF0_LONG_STRING,           "Long string" },
        { AMF0_UNSUPPORTED,           "Unsupported" },
        { AMF0_RECORDSET,             "Record set" },
        { AMF0_XML,                   "XML" },
        { AMF0_TYPED_OBJECT,          "Typed object" },
        { AMF0_AMF3_MARKER,           "Switch to AMF3" },
        { AMF0_INT64,                 "Int64" },
        { 0, NULL }
};

static const value_string amf3_type_vals[] = {
        { AMF3_UNDEFINED,             "Undefined" },
        { AMF3_NULL,                  "Null" },
        { AMF3_FALSE,                 "False" },
        { AMF3_TRUE,                  "True" },
        { AMF3_INTEGER,               "Integer" },
        { AMF3_DOUBLE,                "Double" },
        { AMF3_STRING,                "String" },
        { AMF3_XML_DOC,               "XML document" },
        { AMF3_DATE,                  "Date" },
        { AMF3_ARRAY,                 "Array" },
        { AMF3_OBJECT,                "Object" },
        { AMF3_XML,                   "XML" },
        { AMF3_BYTEARRAY,             "ByteArray" },
        { 0, NULL }
};

/* Holds the reassembled data for a packet during un-chunking
 */
typedef struct rtmpt_packet {
        guint32          seq;
        guint32          lastseq;

        int              resident;
        union {
                guint8  *p;
                guint32  offset;
        } data;

        /* used during unchunking */
        int              want;
        int              have;
        int              chunkwant;
        int              chunkhave;

        guint8           bhlen;
        guint8           mhlen;

        /* Chunk Basic Header */
        guint8           fmt;   /* byte 0 */
        guint32          id;    /* byte 0 */

        /* Chunk Message Header (offsets assume bhlen == 1) */
        guint32          ts;    /* bytes 1-3, or from ETS @ mhlen-4 if -1 */
        guint32          len;   /* bytes 4-6 */
        guint8           cmd;   /* byte 7 */
        guint32          src;   /* bytes 8-11 */

        guint32          txid;
        gint             isresponse;
        gint             otherframe;

} rtmpt_packet_t;

/* Represents a header or a chunk that is split over two TCP
 * segments
 */
typedef struct rtmpt_frag {
        int     ishdr;
        guint32 seq;
        guint32 lastseq;
        int     have;
        int     len;

        union {
                guint8 d[18]; /* enough for a complete header (3 + 11 + 4) */
                guint32 id;
        } saved;
} rtmpt_frag_t;

/* The full message header information for the last packet on a particular
 * ID - used for defaulting short headers
 */
typedef struct rtmpt_id {
        guint32 ts;   /* bytes 1-3 */
        guint32 tsd;
        guint32 len;  /* bytes 4-6 */
        guint32 src;  /* bytes 8-11 */
        guint8  cmd;  /* byte 7 */

        wmem_tree_t *packets;
} rtmpt_id_t;

/* Historical view of a whole TCP connection
 */
typedef struct rtmpt_conv {
        wmem_tree_t *seqs[2];
        wmem_tree_t *frags[2];
        wmem_tree_t *ids[2];
        wmem_tree_t *packets[2];
        wmem_tree_t *chunksize[2];
        wmem_tree_t *txids[2];
} rtmpt_conv_t;

#ifdef DEBUG_RTMPT
static void rtmpt_debug(const char *fmt, ...)
{
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
}
#define RTMPT_DEBUG rtmpt_debug
#else
static void rtmpt_debug(const char *fmt, ...){ (void)fmt; }
#define RTMPT_DEBUG 1 ? (void)0 : rtmpt_debug
#endif

/* Header length helpers */

static gint rtmpt_basic_header_length(gint id)
{
        switch (id & 0x3f) {
        case 0: return 2;
        case 1: return 3;
        default: return 1;
        }
}

static gint rtmpt_message_header_length(gint id)
{
        switch ((id>>6) & 3) {
        case 0: return 11;
        case 1: return 7;
        case 2: return 3;
        default: return 0;
        }
}

/* Lightweight access to AMF0 blobs - more complete dissection is done
 * in dissect_rtmpt_body_command */

static gint
rtmpt_get_amf_length(tvbuff_t *tvb, gint offset)
{
        guint8  iObjType;
        gint    remain  = tvb_reported_length_remaining(tvb, offset);
        guint32 depth   = 0;
        gint    itemlen = 0;
        gint    rv      = 0;

        while (rv == 0 || depth > 0) {

                if (depth > 0) {
                        if (remain-rv < 2)
                                return remain;
                        itemlen = tvb_get_ntohs(tvb, offset+rv) + 2;
                        if (remain-rv<itemlen+1)
                                return remain;
                        rv += itemlen;
                }

                if (remain-rv < 1)
                        return remain;
                iObjType = tvb_get_guint8(tvb, offset+rv);

                if (depth > 0 && itemlen == 2 && iObjType == AMF0_END_OF_OBJECT) {
                        rv++;
                        depth--;
                        continue;
                }

                switch (iObjType) {
                case AMF0_NUMBER:
                        itemlen = 9;
                        break;
                case AMF0_BOOLEAN:
                        itemlen = 2;
                        break;
                case AMF0_STRING:
                        if (remain-rv < 3)
                                return remain;
                        itemlen = tvb_get_ntohs(tvb, offset+rv+1) + 3;
                        break;
                case AMF0_NULL:
                case AMF0_UNDEFINED:
                case AMF0_UNSUPPORTED:
                        itemlen= 1;
                        break;
                case AMF0_DATE:
                        itemlen = 11;
                        break;
                case AMF0_LONG_STRING:
                case AMF0_XML:
                        if (remain-rv < 5)
                                return remain;
                        itemlen = tvb_get_ntohl(tvb, offset+rv+1) + 5;
                        break;
                case AMF0_INT64:
                        itemlen = 9;
                        break;
                case AMF0_OBJECT:
                        itemlen = 1;
                        depth++;
                        break;
                case AMF0_ECMA_ARRAY:
                        itemlen = 5;
                        depth++;
                        break;
                default:
                        return remain;
                }

                if (remain-rv < itemlen)
                        return remain;
                rv += itemlen;

        }

        return rv;
}

static gchar *
rtmpt_get_amf_param(tvbuff_t *tvb, gint offset, gint param, const gchar *prop)
{
        guint32 remain = tvb_reported_length_remaining(tvb, offset);
        guint32 itemlen;
        guint32 iStringLength;

        while (remain > 0 && param > 0) {
                itemlen = rtmpt_get_amf_length(tvb, offset);
                offset += itemlen;
                remain -= itemlen;
                param--;
        }

        if (remain > 0 && param == 0) {
                guint8 iObjType = tvb_get_guint8(tvb, offset);

                if (!prop && iObjType == AMF0_STRING && remain >= 3) {
                        iStringLength = tvb_get_ntohs(tvb, offset+1);
                        if (remain >= iStringLength+3) {
                                return tvb_get_string_enc(wmem_packet_scope(), tvb, offset+3, iStringLength, ENC_ASCII);
                        }
                }

                if (prop && iObjType == AMF0_OBJECT) {
                        offset++;
                        remain--;

                        while (remain > 2) {
                                guint32 iPropLength = tvb_get_ntohs(tvb, offset);
                                if (remain < 2+iPropLength+3)
                                        break;

                                if (tvb_strneql(tvb, offset+2, prop, strlen(prop)) == 0) {
                                        if (tvb_get_guint8(tvb, offset+2+iPropLength) != AMF0_STRING)
                                                break;

                                        iStringLength = tvb_get_ntohs(tvb, offset+2+iPropLength+1);
                                        if (remain < 2+iPropLength+3+iStringLength)
                                                break;

                                        return tvb_get_string_enc(wmem_packet_scope(), tvb, offset+2+iPropLength+3, iStringLength, ENC_ASCII);
                                }

                                itemlen = rtmpt_get_amf_length(tvb, offset+2+iPropLength);
                                offset += 2+iPropLength+itemlen;
                                remain -= 2+iPropLength+itemlen;
                        }
                }
        }

        return NULL;
}

static guint32
rtmpt_get_amf_txid(tvbuff_t *tvb, gint offset)
{
        guint32 remain = tvb_reported_length_remaining(tvb, offset);

        if (remain > 0) {
                guint32 itemlen = rtmpt_get_amf_length(tvb, offset);
                if (remain<itemlen)
                        return 0;
                offset += itemlen;
                remain -= itemlen;
        }
        if (remain >= 9) {
                guint8 iObjType = tvb_get_guint8(tvb, offset);
                if (iObjType == AMF0_NUMBER) {
                        return (guint32)tvb_get_ntohieee_double(tvb, offset+1);
                }
        }

        return 0;
}


/* Generate a useful description for various packet types */

static gchar *
rtmpt_get_packet_desc(tvbuff_t *tvb, guint32 offset, guint32 remain, rtmpt_conv_t *rconv, int cdir, rtmpt_packet_t *tp, gint *deschasopcode)
{
        if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE || tp->cmd == RTMPT_TYPE_ABORT_MESSAGE ||
            tp->cmd == RTMPT_TYPE_ACKNOWLEDGEMENT || tp->cmd == RTMPT_TYPE_WINDOW) {
                if (tp->len >= 4 && remain >= 4) {
                        *deschasopcode = TRUE;
                        return wmem_strdup_printf(wmem_packet_scope(), "%s %d",
                                                val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"),
                                                tvb_get_ntohl(tvb, offset));
                }

        } else if (tp->cmd == RTMPT_TYPE_PEER_BANDWIDTH) {
                if (tp->len >= 5 && remain >= 5) {
                        *deschasopcode = TRUE;
                        return wmem_strdup_printf(wmem_packet_scope(), "%s %d,%s",
                                                val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"),
                                                tvb_get_ntohl(tvb, offset),
                                                val_to_str(tvb_get_guint8(tvb, offset+4), rtmpt_limit_vals, "Unknown (%d)"));
                }

        } else if (tp->cmd == RTMPT_TYPE_UCM) {
                guint16 iUCM = -1;
                const gchar *sFunc;
                const gchar *sParam = "";

                if (tp->len < 2 || remain < 2)
                        return NULL;

                iUCM = tvb_get_ntohs(tvb, offset);
                sFunc = try_val_to_str(iUCM, rtmpt_ucm_vals);
                if (sFunc == NULL) {
                        *deschasopcode = TRUE;
                        sFunc = wmem_strdup_printf(wmem_packet_scope(), "User Control Message 0x%01x", iUCM);
                }

                if (iUCM == RTMPT_UCM_STREAM_BEGIN || iUCM == RTMPT_UCM_STREAM_EOF ||
                    iUCM == RTMPT_UCM_STREAM_DRY || iUCM == RTMPT_UCM_STREAM_ISRECORDED) {
                        if (tp->len >= 6 && remain >= 6) {
                                sParam = wmem_strdup_printf(wmem_packet_scope(), " %d", tvb_get_ntohl(tvb, offset+2));
                        }
                } else if (iUCM == RTMPT_UCM_SET_BUFFER) {
                        if (tp->len >= 10 && remain >= 10) {
                                sParam = wmem_strdup_printf(wmem_packet_scope(), " %d,%dms",
                                                          tvb_get_ntohl(tvb, offset+2),
                                                          tvb_get_ntohl(tvb, offset+6));
                        }
                }

                return wmem_strdup_printf(wmem_packet_scope(), "%s%s", sFunc, sParam);

        } else if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 ||
                   tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
                guint32 slen = 0;
                guint32 soff = 0;
                gchar *sFunc = NULL;
                gchar *sParam = NULL;

                if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
                        soff = 1;
                }
                if (tp->len >= 3+soff && remain >= 3+soff) {
                        slen = tvb_get_ntohs(tvb, offset+1+soff);
                }
                if (slen > 0) {
                        sFunc = tvb_get_string_enc(wmem_packet_scope(), tvb, offset+3+soff, slen, ENC_ASCII);
                        RTMPT_DEBUG("got function call '%s'\n", sFunc);

                        if (strcmp(sFunc, "connect") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 2, "app");
                        } else if (strcmp(sFunc, "play") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, NULL);
                        } else if (strcmp(sFunc, "play2") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, "streamName");
                        } else if (strcmp(sFunc, "releaseStream") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, NULL);
                        } else if (strcmp(sFunc, "FCPublish") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, NULL);
                        } else if (strcmp(sFunc, "publish") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, NULL);
                        } else if (strcmp(sFunc, "onStatus") == 0) {
                                if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3) {
                                        sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, "code");
                                } else {
                                        sParam = rtmpt_get_amf_param(tvb, offset+soff, 1, "code");
                                }
                        } else if (strcmp(sFunc, "onPlayStatus") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 1, "code");
                        } else if (strcmp(sFunc, "_result") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, "code");
                                tp->isresponse = TRUE;
                        } else if (strcmp(sFunc, "_error") == 0) {
                                sParam = rtmpt_get_amf_param(tvb, offset+soff, 3, "code");
                                tp->isresponse = TRUE;
                        }

                        if (tp->txid != 0 && tp->otherframe == 0) {
                                tp->otherframe = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->txids[cdir^1], tp->txid));
                                if (tp->otherframe) {
                                        RTMPT_DEBUG("got otherframe=%d\n", tp->otherframe);
                                }
                        }
                }

                if (sFunc) {
                        if (sParam) {
                                return wmem_strdup_printf(wmem_packet_scope(), "%s('%s')", sFunc, sParam);
                        } else {
                                return wmem_strdup_printf(wmem_packet_scope(), "%s()", sFunc);
                        }
                }
        }

        return NULL;
}


/* Tree dissection helpers for various packet body forms */

static void
dissect_rtmpt_body_scm(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree, guint scm)
{
        switch (scm) {
        case RTMPT_TYPE_CHUNK_SIZE:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_chunksize, tvb, offset, 4, ENC_BIG_ENDIAN);
                break;
        case RTMPT_TYPE_ABORT_MESSAGE:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_csid, tvb, offset, 4, ENC_BIG_ENDIAN);
                break;
        case RTMPT_TYPE_ACKNOWLEDGEMENT:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_seq, tvb, offset, 4, ENC_BIG_ENDIAN);
                break;
        case RTMPT_TYPE_UCM:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_ucm_eventtype, tvb, offset, 2, ENC_BIG_ENDIAN);
                break;
        case RTMPT_TYPE_WINDOW:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN);
                break;
        case RTMPT_TYPE_PEER_BANDWIDTH:
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN);
                proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_limittype, tvb, offset+4, 1, ENC_BIG_ENDIAN);
                break;
        }
}

static gint
dissect_amf0_value_type(tvbuff_t *tvb, gint offset, proto_tree *tree, gboolean *amf3_encoding, proto_item *parent_ti);

/*
 * A "property list" is a sequence of name/value pairs, terminated by
 * and "end of object" indicator.  AMF0 "object"s and "ECMA array"s
 * are encoded as property lists.
 */
static gint
dissect_amf0_property_list(tvbuff_t *tvb, gint offset, proto_tree *tree, guint *countp, gboolean *amf3_encoding)
{
        proto_item *prop_ti;
        proto_tree *prop_tree;
        proto_tree *name_tree;
        guint       iStringLength;
        gchar      *iStringValue;
        guint       count = 0;

        /*
         * XXX - at least as I read "3.1 AVM+ Type Marker" in the AMF0
         * specification, the AVM+ Type Marker only affects "the following
         * Object".  For now, we have a single "AMF3 encoding" flag, and
         * set it when we see the type marker, and never clear it.
         */
        for (;;) {
                /* UTF-8: property name */
                iStringLength = tvb_get_ntohs(tvb, offset);
                if (iStringLength == 0 &&
                    tvb_get_guint8(tvb, offset + 2) == AMF0_END_OF_OBJECT)
                        break;
                count++;
                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, offset + 2, iStringLength, ENC_ASCII);
                prop_tree = proto_tree_add_subtree_format(tree, tvb, offset, -1,
                                              ett_amf_property, &prop_ti, "Property '%s'",
                                              iStringValue);

                name_tree = proto_tree_add_subtree_format(prop_tree, tvb,
                                              offset, 2+iStringLength,
                                              ett_amf_string, NULL, "Name: %s", iStringValue);

                proto_tree_add_uint(name_tree, hf_amf_stringlength, tvb, offset, 2, iStringLength);
                offset += 2;
                proto_tree_add_item(name_tree, hf_amf_string, tvb, offset, iStringLength, ENC_UTF_8|ENC_NA);
                offset += iStringLength;

                /* value-type: property value */
                offset = dissect_amf0_value_type(tvb, offset, prop_tree, amf3_encoding, prop_ti);
                proto_item_set_end(prop_ti, tvb, offset);
        }
        proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, offset, 3, ENC_NA);
        offset += 3;

        *countp = count;

        return offset;
}

static gint
dissect_amf0_value_type(tvbuff_t *tvb, gint offset, proto_tree *tree, gboolean *amf3_encoding, proto_item *parent_ti)
{
        guint8      iObjType;
        proto_item *ti;
        proto_tree *val_tree;
        gint        iValueOffset = offset;
        guint32     iIntegerValue;
        double      iDoubleValue;
        gboolean    iBooleanValue;
        guint       iStringLength;
        gchar      *iStringValue;
        guint       iArrayLength;
        guint       i;
        nstime_t    t;
        gint64      iInteger64Value;
        guint       count;

        iObjType = tvb_get_guint8(tvb, offset);
        if (parent_ti != NULL)
                proto_item_append_text(parent_ti, " %s",
                                       val_to_str_const(iObjType, amf0_type_vals, "Unknown"));
        switch (iObjType) {

        case AMF0_OBJECT:
                /*
                 * For object types, make the top-level protocol tree
                 * item a field for that type.
                 */
                ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA);
                val_tree = proto_item_add_subtree(ti, ett_amf_value);
                break;

        case AMF0_ECMA_ARRAY:
                /*
                 * For ECMA array types, make the top-level protocol tree
                 * item a field for that type.
                 */
                ti = proto_tree_add_item(tree, hf_amf_ecmaarray, tvb, offset, -1, ENC_NA);
                val_tree = proto_item_add_subtree(ti, ett_amf_value);
                break;

        case AMF0_STRICT_ARRAY:
                /*
                 * For strict array types, make the top-level protocol tree
                 * item a field for that type.
                 */
                ti = proto_tree_add_item(tree, hf_amf_strictarray, tvb, offset, -1, ENC_NA);
                val_tree = proto_item_add_subtree(ti, ett_amf_value);
                break;

        default:
                /*
                 * For all other types, make it just a text item; the
                 * field for that type will be used for the value.
                 */
                val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti,
                                         val_to_str_const(iObjType, amf0_type_vals, "Unknown"));
                break;
        }

        proto_tree_add_uint(val_tree, hf_amf_amf0_type, tvb, iValueOffset, 1, iObjType);
        iValueOffset++;

        switch (iObjType) {
        case AMF0_NUMBER:
                iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
                proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue);
                iValueOffset += 8;
                proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
                break;
        case AMF0_BOOLEAN:
                iBooleanValue = tvb_get_guint8(tvb, iValueOffset);
                proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, iValueOffset, 1, iBooleanValue);
                iValueOffset += 1;
                proto_item_append_text(ti, iBooleanValue ? " true" : " false");
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, iBooleanValue ? " true" : " false");
                break;
        case AMF0_STRING:
                iStringLength = tvb_get_ntohs(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
                iValueOffset += 2;
                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
                if (iStringLength != 0)
                        proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
                iValueOffset += iStringLength;
                proto_item_append_text(ti, " '%s'", iStringValue);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " '%s'", iStringValue);
                break;
        case AMF0_OBJECT:
                iValueOffset = dissect_amf0_property_list(tvb, iValueOffset, val_tree, &count, amf3_encoding);
                proto_item_append_text(ti, " (%u items)", count);
                break;
        case AMF0_NULL:
        case AMF0_UNDEFINED:
                break;
        case AMF0_REFERENCE:
                iIntegerValue = tvb_get_ntohs(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, 2, iIntegerValue);
                iValueOffset += 2;
                proto_item_append_text(ti, " %d", iIntegerValue);
                break;
        case AMF0_ECMA_ARRAY:
                /*
                 * Counted list type, with end marker. The count appears to be
                 * more of a hint than a rule, and is sometimes sent as 0 or
                 * invalid.
                 *
                 * Basically the same as OBJECT but with the extra count field.
                 * There being many strange encoders/metadata injectors out
                 * there, sometimes you see a valid count and no end marker.
                 * Figuring out which you've got for a deeply nested structure
                 * is non-trivial.
                 */
                iArrayLength = tvb_get_ntohl(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength);
                iValueOffset += 4;
                iValueOffset = dissect_amf0_property_list(tvb, iValueOffset, val_tree, &count, amf3_encoding);
                proto_item_append_text(ti, " (%u items)", count);
                break;
        case AMF0_END_OF_OBJECT:
                proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, iValueOffset, 3, ENC_NA);
                iValueOffset += 3;
                break;
        case AMF0_STRICT_ARRAY:
                /*
                 * Counted list type, without end marker. Number of values
                 * is determined by count, values are assumed to form a
                 * [0..N-1] numbered array and are presented as plain AMF
                 * types, not OBJECT or ECMA_ARRAY style named properties.
                 */
                iArrayLength = tvb_get_ntohl(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength);
                iValueOffset += 4;
                for (i = 0; i < iArrayLength; i++)
                        iValueOffset = dissect_amf0_value_type(tvb, iValueOffset, val_tree, amf3_encoding, NULL);
                proto_item_append_text(ti, " (%u items)", iArrayLength);
                break;
        case AMF0_DATE:
                iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
                t.secs = (time_t)(iDoubleValue/1000);
                t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000);
                proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t);
                iValueOffset += 8;
                proto_item_append_text(ti, " %s", abs_time_to_str(wmem_packet_scope(), &t, ABSOLUTE_TIME_LOCAL, TRUE));
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " %s", abs_time_to_str(wmem_packet_scope(), &t, ABSOLUTE_TIME_LOCAL, TRUE));
                /* time-zone */
                iValueOffset += 2;
                break;
        case AMF0_LONG_STRING:
        case AMF0_XML: /* same representation */
                iStringLength = tvb_get_ntohl(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
                iValueOffset += 4;
                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
                if (iStringLength != 0)
                        proto_tree_add_string(val_tree, (iObjType == AMF0_XML) ? hf_amf_xml_doc : hf_amf_longstring, tvb, iValueOffset, iStringLength, iStringValue);
                iValueOffset += iStringLength;
                proto_item_append_text(ti, " '%s'", iStringValue);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " '%s'", iStringValue);
                break;
        case AMF0_UNSUPPORTED:
                break;
        case AMF0_TYPED_OBJECT:
                /* class-name */
                iStringLength = tvb_get_ntohs(tvb, iValueOffset);
                proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
                iValueOffset += 2;
                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
                proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
                iValueOffset += iStringLength;
                iValueOffset = dissect_amf0_property_list(tvb, iValueOffset, val_tree, &count, amf3_encoding);
                break;
        case AMF0_AMF3_MARKER:
                *amf3_encoding = TRUE;
                break;
        case AMF0_INT64:
                iInteger64Value = tvb_get_ntoh64(tvb, iValueOffset);
                proto_tree_add_int64(val_tree, hf_amf_int64, tvb, iValueOffset, 8, iInteger64Value);
                iValueOffset += 8;
                proto_item_append_text(ti," %" G_GINT64_MODIFIER "d", iInteger64Value);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti," %" G_GINT64_MODIFIER "d", iInteger64Value);
                break;
        default:
                /*
                 * If we can't determine the length, don't carry on;
                 * just skip to the end of the tvbuff.
                 */
                iValueOffset = tvb_reported_length(tvb);
                break;
        }
        proto_item_set_end(ti, tvb, iValueOffset);
        return iValueOffset;
}

static guint32
amf_get_u29(tvbuff_t *tvb, int offset, guint *lenp)
{
        guint   len = 0;
        guint8  iByte;
        guint32 iValue;

        iByte  = tvb_get_guint8(tvb, offset);
        iValue = (iByte & 0x7F);
        offset++;
        len++;
        if (!(iByte & 0x80)) {
                /* 1 byte value */
                *lenp = len;
                return iValue;
        }
        iByte = tvb_get_guint8(tvb, offset);
        iValue = (iValue << 7) | (iByte & 0x7F);
        offset++;
        len++;
        if (!(iByte & 0x80)) {
                /* 2 byte value */
                *lenp = len;
                return iValue;
        }
        iByte = tvb_get_guint8(tvb, offset);
        iValue = (iValue << 7) | (iByte & 0x7F);
        offset++;
        len++;
        if (!(iByte & 0x80)) {
                /* 3 byte value */
                *lenp = len;
                return iValue;
        }
        iByte = tvb_get_guint8(tvb, offset);
        iValue = (iValue << 8) | iByte;
        len++;
        *lenp = len;
        return iValue;
}

static gint
dissect_amf3_value_type(tvbuff_t *tvb, gint offset, proto_tree *tree, proto_item *parent_ti)
{
        guint8      iObjType;
        proto_item *ti;
        proto_tree *val_tree;
        gint        iValueOffset = offset;
        guint       iValueLength;
        guint32     iIntegerValue;
        double      iDoubleValue;
        guint       iStringLength;
        gchar      *iStringValue;
        guint       iArrayLength;
        proto_item *subval_ti;
        proto_tree *subval_tree;
        guint       i;
        gboolean    iTypeIsDynamic;
        guint       iTraitCount;
        proto_item *traits_ti;
        proto_tree *traits_tree;
        proto_tree *name_tree;
        proto_tree *member_tree;
        guint8     *iByteArrayValue;

        iObjType = tvb_get_guint8(tvb, offset);
        if (parent_ti != NULL)
                proto_item_append_text(parent_ti, " %s",
                                       val_to_str_const(iObjType, amf3_type_vals, "Unknown"));
        switch (iObjType) {

        case AMF3_ARRAY:
                /*
                 * For array types, make the top-level protocol tree
                 * item a field for that type.
                 */
                ti = proto_tree_add_item(tree, hf_amf_array, tvb, offset, -1, ENC_NA);
                val_tree = proto_item_add_subtree(ti, ett_amf_value);
                break;

        case AMF3_OBJECT:
                /*
                 * For object types, make the top-level protocol tree
                 * item a field for that type.
                 */
                ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA);
                val_tree = proto_item_add_subtree(ti, ett_amf_value);
                break;

        default:
                /*
                 * For all other types, make it just a text item; the
                 * field for that type will be used for the value.
                 */
                val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti,
                                         val_to_str_const(iObjType, amf3_type_vals, "Unknown"));
                break;
        }

        proto_tree_add_uint(val_tree, hf_amf_amf3_type, tvb, iValueOffset, 1, iObjType);
        iValueOffset++;

        switch (iObjType) {
        case AMF3_UNDEFINED:
        case AMF3_NULL:
                break;
        case AMF3_FALSE:
                proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, FALSE);
                proto_item_append_text(ti, " false");
                break;
        case AMF3_TRUE:
                proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, TRUE);
                proto_item_append_text(ti, " true");
                break;
        case AMF3_INTEGER:
                /* XXX - signed or unsigned? */
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                proto_tree_add_uint(val_tree, hf_amf_integer, tvb, iValueOffset, iValueLength, iIntegerValue);
                proto_item_append_text(ti, " %u", iIntegerValue);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " %u", iIntegerValue);
                iValueOffset += iValueLength;
                break;
        case AMF3_DOUBLE:
                iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
                proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue);
                iValueOffset += 8;
                proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
                if (parent_ti != NULL)
                        proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
                break;
        case AMF3_STRING:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        /* the upper 28 bits of the integer value is a string length */
                        iStringLength = iIntegerValue >> 1;
                        proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength);
                        iValueOffset += iValueLength;
                        iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
                        if (iStringLength != 0)
                                proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
                        iValueOffset += iStringLength;
                        proto_item_append_text(ti, " '%s'", iStringValue);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " '%s'", iStringValue);
                } else {
                        /* the upper 28 bits of the integer value are a string reference index */
                        proto_tree_add_uint(val_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        iValueOffset += iValueLength;
                        proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
                }
                break;
        case AMF3_DATE:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        /*
                         * The upper 28 bits of the integer value are
                         * ignored; what follows is a double
                         * containing milliseconds since the Epoch.
                         */
                        nstime_t t;

                        iValueOffset += iValueLength;
                        iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
                        t.secs = (time_t)(iDoubleValue/1000);
                        t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000);
                        proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t);
                        iValueOffset += 8;
                        proto_item_append_text(ti, "%s", abs_time_to_str(wmem_packet_scope(), &t, ABSOLUTE_TIME_LOCAL, TRUE));
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, "%s", abs_time_to_str(wmem_packet_scope(), &t, ABSOLUTE_TIME_LOCAL, TRUE));
                } else {
                        /* the upper 28 bits of the integer value are an object reference index */
                        proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        iValueOffset += iValueLength;
                        proto_item_append_text(ti, " object reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " object reference %u", iIntegerValue >> 1);
                }
                break;
        case AMF3_ARRAY:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        /*
                         * The upper 28 bits of the integer value are
                         * a count of the number of elements in
                         * the dense portion of the array.
                         */
                        iArrayLength = iIntegerValue >> 1;
                        proto_tree_add_uint(val_tree, hf_amf_arraydenselength, tvb, iValueOffset, iValueLength, iArrayLength);
                        iValueOffset += iValueLength;

                        /*
                         * The AMF3 spec bit on the Array type is slightly
                         * confusingly written, but seems to be saying that
                         * the associative portion of the array follows the
                         * size of the dense portion of the array, and the
                         * dense portion of the array follows the associative
                         * portion.
                         *
                         * Dissect the associative portion.
                         */
                        for (;;) {
                                /* Fetch the name */
                                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                                if (iIntegerValue & 0x00000001) {
                                        /* the upper 28 bits of the integer value is a string length */
                                        iStringLength = iIntegerValue >> 1;
                                        if (iStringLength == 0) {
                                                /* null name marks the end of the associative part */
                                                proto_tree_add_item(val_tree, hf_amf_end_of_associative_part, tvb, iValueOffset, iValueLength, ENC_NA);
                                                iValueOffset += iValueLength;
                                                break;
                                        }
                                        iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
                                        subval_tree = proto_tree_add_subtree(val_tree, tvb, iValueOffset, iStringLength,
                                                                    ett_amf_array_element, &subval_ti, iStringValue);
                                        proto_tree_add_uint(subval_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength);
                                        iValueOffset += iValueLength;
                                        proto_tree_add_string(subval_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
                                } else {
                                        /* the upper 28 bits of the integer value are a string reference index */
                                        subval_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength,
                                                        ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1);
                                        proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                                }

                                /* Fetch the value */
                                iObjType = tvb_get_guint8(tvb, offset);
                                proto_item_append_text(subval_ti, "%s",
                                                       val_to_str_const(iObjType, amf3_type_vals, "Unknown"));

                                iValueOffset = dissect_amf3_value_type(tvb, iValueOffset, subval_tree, subval_ti);
                        }

                        /*
                         * Dissect the dense portion.
                         */
                        for (i = 0; i < iArrayLength; i++)
                                iValueOffset = dissect_amf3_value_type(tvb, iValueOffset, val_tree, NULL);

                        proto_item_set_end(ti, tvb, iValueOffset);
                } else {
                        /* the upper 28 bits of the integer value are an object reference index */
                        proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
                }
                break;
        case AMF3_OBJECT:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        if (iIntegerValue & 0x00000002) {
                                if (iIntegerValue & 0x00000004) {
                                        /*
                                         * U29O-traits-ext; the rest of
                                         * iIntegerValue is not significant,
                                         * and, worse, we have idea what
                                         * follows the class name, or even
                                         * how many bytes follow the class
                                         * name - that's by convention between
                                         * the client and server.
                                         */
                                        iValueOffset += iValueLength;
                                } else {
                                        /*
                                         * U29O-traits; the 0x00000008 bit
                                         * specifies whether the type is
                                         * dynamic.
                                         */
                                        iTypeIsDynamic = (iIntegerValue & 0x00000008) ? TRUE : FALSE;
                                        iTraitCount = iIntegerValue >> 4;
                                        proto_tree_add_uint(val_tree, hf_amf_traitcount, tvb, iValueOffset, iValueLength, iTraitCount);
                                        iValueOffset += iValueLength;
                                        iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                                        if (iIntegerValue & 0x00000001) {
                                                /* the upper 28 bits of the integer value is a string length */
                                                iStringLength = iIntegerValue >> 1;
                                                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
                                                traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, -1,
                                                            ett_amf_traits, &traits_ti, "Traits for class %s (%u member names)", iStringValue, iTraitCount);
                                                name_tree = proto_tree_add_subtree_format(traits_tree, tvb,
                                                                              iValueOffset,
                                                                              iValueLength+iStringLength,
                                                                              ett_amf_string, NULL, "Class name: %s",
                                                                              iStringValue);
                                                proto_tree_add_uint(name_tree, hf_amf_classnamelength, tvb, iValueOffset, iValueLength, iStringLength);
                                                iValueOffset += iValueLength;
                                                proto_tree_add_string(name_tree, hf_amf_classname, tvb, iValueOffset, iStringLength, iStringValue);
                                                iValueOffset += iStringLength;
                                        } else {
                                                /* the upper 28 bits of the integer value are a string reference index */
                                                traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength,
                                                    ett_amf_traits, &traits_ti, "Traits for class (reference %u for name)", iIntegerValue >> 1);
                                                proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                                                iValueOffset += iValueLength;
                                        }
                                        for (i = 0; i < iTraitCount; i++) {
                                                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                                                if (iIntegerValue & 0x00000001) {
                                                        /* the upper 28 bits of the integer value is a string length */
                                                        iStringLength = iIntegerValue >> 1;
                                                        iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
                                                        member_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength+iStringLength,
                                                                                            ett_amf_trait_member, NULL, "Member '%s'", iStringValue);
                                                        proto_tree_add_uint(member_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength);
                                                        iValueOffset += iValueLength;
                                                        proto_tree_add_string(member_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue);
                                                        iValueOffset += iStringLength;
                                                } else {
                                                        /* the upper 28 bits of the integer value are a string reference index */
                                                        proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                                                        iValueOffset += iValueLength;
                                                }
                                        }
                                        for (i = 0; i < iTraitCount; i++)
                                                iValueOffset = dissect_amf3_value_type(tvb, iValueOffset, traits_tree, NULL);
                                        if (iTypeIsDynamic) {
                                                for (;;) {
                                                        /* Fetch the name */
                                                        iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                                                        if (iIntegerValue & 0x00000001) {
                                                                /* the upper 28 bits of the integer value is a string length */
                                                                iStringLength = iIntegerValue >> 1;
                                                                if (iStringLength == 0) {
                                                                        /* null name marks the end of the associative part */
                                                                        proto_tree_add_item(traits_tree, hf_amf_end_of_dynamic_members, tvb, iValueOffset, iValueLength, ENC_NA);
                                                                        iValueOffset += iValueLength;
                                                                        break;
                                                                }
                                                                iStringValue = tvb_get_string_enc(wmem_packet_scope(), tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
                                                                subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, -1,
                                                                            ett_amf_array_element, &subval_ti, "%s:", iStringValue);
                                                                name_tree = proto_tree_add_subtree_format(subval_tree, tvb,
                                                                                              iValueOffset,
                                                                                              iValueLength+iStringLength,
                                                                                              ett_amf_string, NULL, "Member name: %s",
                                                                                              iStringValue);
                                                                proto_tree_add_uint(name_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength);
                                                                iValueOffset += iValueLength;
                                                                proto_tree_add_string(name_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue);
                                                                iValueOffset += iStringLength;
                                                        } else {
                                                                /* the upper 28 bits of the integer value are a string reference index */
                                                                subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength,
                                                                                    ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1);
                                                                proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                                                                iValueOffset += iValueLength;
                                                        }

                                                        /* Fetch the value */
                                                        iValueOffset = dissect_amf3_value_type(tvb, iValueOffset, subval_tree, subval_ti);
                                                        proto_item_set_end(subval_ti, tvb, iValueOffset);
                                                }
                                        }
                                        proto_item_set_end(traits_ti, tvb, iValueOffset);
                                }
                        } else {
                                /*
                                 * U29O-traits-ref; the upper 27 bits of
                                 * the integer value are a traits reference
                                 * index.
                                 */
                                proto_tree_add_uint(val_tree, hf_amf_trait_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 2);
                                iValueOffset += iValueLength;
                        }
                } else {
                        /*
                         * U29O-ref; the upper 28 bits of the integer value
                         * are an object reference index.
                         */
                        proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
                }
                break;
        case AMF3_XML:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        /*
                         * The upper 28 bits of the integer value are
                         * a count of the number of bytes in the
                         * XML string.
                         */
                        iStringLength = iIntegerValue >> 1;
                        proto_tree_add_uint(val_tree, hf_amf_xmllength, tvb, iValueOffset, iValueLength, iStringLength);
                        iValueOffset += iValueLength;
                        proto_tree_add_item(val_tree, hf_amf_xml, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
                } else {
                        /* the upper 28 bits of the integer value are a string reference index */
                        proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
                }
                break;
        case AMF3_BYTEARRAY:
                iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
                if (iIntegerValue & 0x00000001) {
                        /*
                         * The upper 28 bits of the integer value are
                         * a count of the number of bytes in the
                         * byte array.
                         */
                        iArrayLength = iIntegerValue >> 1;
                        proto_tree_add_uint(val_tree, hf_amf_bytearraylength, tvb, iValueOffset, iValueLength, iArrayLength);
                        iValueOffset += iValueLength;
                        iByteArrayValue = (guint8 *)tvb_memdup(wmem_packet_scope(), tvb, iValueOffset, iArrayLength);
                        proto_tree_add_bytes(val_tree, hf_amf_bytearray, tvb, iValueOffset, iArrayLength, iByteArrayValue);
                        proto_item_append_text(ti, " %s", bytes_to_str(wmem_packet_scope(), iByteArrayValue, iArrayLength));
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " %s", bytes_to_str(wmem_packet_scope(), iByteArrayValue, iArrayLength));
                } else {
                        /* the upper 28 bits of the integer value are a object reference index */
                        proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
                        proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
                        if (parent_ti != NULL)
                                proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
                }
                break;
        default:
                /*
                 * If we can't determine the length, don't carry on;
                 * just skip to the end of the tvbuff.
                 */
                iValueOffset = tvb_reported_length(tvb);
                break;
        }
        proto_item_set_end(ti, tvb, iValueOffset);
        return iValueOffset;
}

static gint
dissect_rtmpt_body_command(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree, gboolean amf3)
{
        gboolean    amf3_encoding = FALSE;

        if (amf3) {
                /* Looks like for the AMF3 variants we get a 0 byte here,
                 * followed by AMF0 encoding - I've never seen actual AMF3
                 * encoding used, which is completely different. I speculate
                 * that if the byte is AMF0_AMF3_MARKER then the rest
                 * will be in AMF3. For now, assume AMF0 only. */
                offset++;
        }

        while (tvb_reported_length_remaining(tvb, offset) > 0)
        {
                if (amf3_encoding)
                        offset = dissect_amf3_value_type(tvb, offset, rtmpt_tree, NULL);
                else
                        offset = dissect_amf0_value_type(tvb, offset, rtmpt_tree, &amf3_encoding, NULL);
        }
        return offset;
}

static void
dissect_rtmpt_body_audio(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree)
{
        guint8      iCtl;
        proto_item *ai;
        proto_tree *at;

        iCtl = tvb_get_guint8(tvb, offset);
        ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_control, tvb, offset, 1, iCtl,
                                        "Control: 0x%02x (%s %s %s %s)", iCtl,
                                        val_to_str_const((iCtl & 0xf0)>>4, rtmpt_audio_codecs, "Unknown codec"),
                                        val_to_str_const((iCtl & 0x0c)>>2, rtmpt_audio_rates, "Unknown rate"),
                                        val_to_str_const((iCtl & 0x02)>>1, rtmpt_audio_sizes, "Unknown sample size"),
                                        val_to_str_const(iCtl & 0x01, rtmpt_audio_types, "Unknown channel count"));

        at = proto_item_add_subtree(ai, ett_rtmpt_audio_control);
        proto_tree_add_uint(at, hf_rtmpt_audio_format, tvb, offset, 1, iCtl);
        proto_tree_add_uint(at, hf_rtmpt_audio_rate, tvb, offset, 1, iCtl);
        proto_tree_add_uint(at, hf_rtmpt_audio_size, tvb, offset, 1, iCtl);
        proto_tree_add_uint(at, hf_rtmpt_audio_type, tvb, offset, 1, iCtl);
        proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_data, tvb, offset+1, -1, ENC_NA);
}

static void
dissect_rtmpt_body_video(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree)
{
        guint8      iCtl;
        proto_item *vi;
        proto_tree *vt;

        iCtl = tvb_get_guint8(tvb, offset);
        vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_control, tvb, offset, 1, iCtl,
                                        "Control: 0x%02x (%s %s)", iCtl,
                                        val_to_str_const((iCtl & 0xf0)>>4, rtmpt_video_types, "Unknown frame type"),
                                        val_to_str_const(iCtl & 0x0f, rtmpt_video_codecs, "Unknown codec"));

        vt = proto_item_add_subtree(vi, ett_rtmpt_video_control);
        proto_tree_add_uint(vt, hf_rtmpt_video_type, tvb, offset, 1, iCtl);
        proto_tree_add_uint(vt, hf_rtmpt_video_format, tvb, offset, 1, iCtl);
        proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_data, tvb, offset+1, -1, ENC_NA);
}

static void
dissect_rtmpt_body_aggregate(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree)
{
        proto_tree *tag_tree;

        proto_tree *data_tree;

        while (tvb_reported_length_remaining(tvb, offset) > 0) {
                guint8 iTagType;
                guint  iDataSize;

                iTagType  = tvb_get_guint8(tvb, offset + 0);
                iDataSize = tvb_get_ntoh24(tvb, offset + 1);

                tag_tree  = proto_tree_add_subtree(rtmpt_tree, tvb, offset, 11+iDataSize+4, ett_rtmpt_tag, NULL,
                                               val_to_str_const(iTagType, rtmpt_tag_vals, "Unknown Tag"));
                proto_tree_add_item(tag_tree, hf_rtmpt_tag_type, tvb, offset+0, 1, ENC_BIG_ENDIAN);
                proto_tree_add_item(tag_tree, hf_rtmpt_tag_datasize, tvb, offset+1, 3, ENC_BIG_ENDIAN);
                proto_tree_add_item(tag_tree, hf_rtmpt_tag_timestamp, tvb, offset+4, 3, ENC_BIG_ENDIAN);
                proto_tree_add_item(tag_tree, hf_rtmpt_tag_ets, tvb, offset+7, 1, ENC_BIG_ENDIAN);
                proto_tree_add_item(tag_tree, hf_rtmpt_tag_streamid, tvb, offset+8, 3, ENC_BIG_ENDIAN);

                data_tree = proto_tree_add_subtree(tag_tree, tvb, offset+11, iDataSize, ett_rtmpt_tag_data, NULL, "Data");

                switch (iTagType) {
                case 8:
                        dissect_rtmpt_body_audio(tvb, offset + 11, data_tree);
                        break;
                case 9:
                        dissect_rtmpt_body_video(tvb, offset + 11, data_tree);
                        break;
                case 18:
                        dissect_rtmpt_body_command(tvb, offset + 11, data_tree, FALSE);
                        break;
                default:
                        break;
                }

                proto_tree_add_item(tag_tree, hf_rtmpt_tag_tagsize, tvb, offset+11+iDataSize, 4, ENC_BIG_ENDIAN);
                offset += 11 + iDataSize + 4;
        }
}

/* The main dissector for unchunked packets */

static void
dissect_rtmpt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, rtmpt_conv_t *rconv, int cdir, rtmpt_packet_t *tp)
{
        gint        offset         = 0;

        gchar      *sDesc          = NULL;
        gint        deschasopcode  = FALSE;
        gboolean    haveETS        = FALSE;
        guint32     iBodyOffset    = 0;
        guint32     iBodyRemain    = 0;

        col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");

        RTMPT_DEBUG("Dissect: frame=%u visited=%d len=%d tree=%p\n",
                    pinfo->num, pinfo->fd->flags.visited,
                    tvb_reported_length_remaining(tvb, offset), tree);

        /* Clear any previous data in Info column (RTMP packets are protected by a "fence") */
        col_clear(pinfo->cinfo, COL_INFO);

        if (tvb_reported_length_remaining(tvb, offset) < 1) return;

        if (tp->id <= RTMPT_ID_MAX) {
                if (tp->fmt < 3
                    && tvb_reported_length_remaining(tvb, offset) >= tp->bhlen+3
                    && tvb_get_ntoh24(tvb, offset+tp->bhlen) == 0xffffff) {
                        haveETS = TRUE;
                }

                iBodyOffset = offset + tp->bhlen + tp->mhlen;
                iBodyRemain = tvb_reported_length_remaining(tvb, iBodyOffset);

                if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE && tp->len >= 4 && iBodyRemain >= 4) {
                        guint32 newchunksize = tvb_get_ntohl(tvb, iBodyOffset);
                        if (newchunksize < rtmpt_max_packet_size) {
                                wmem_tree_insert32(rconv->chunksize[cdir], tp->lastseq, GINT_TO_POINTER(newchunksize));
                        }
                }

                if (!PINFO_FD_VISITED(pinfo)) {
                        if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 ||
                            tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
                                guint32 soff = 0;
                                if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
                                        soff = 1;
                                }
                                tp->txid = rtmpt_get_amf_txid(tvb, iBodyOffset+soff);
                                if (tp->txid != 0) {
                                        RTMPT_DEBUG("got txid=%d\n", tp->txid);
                                        wmem_tree_insert32(rconv->txids[cdir], tp->txid, GINT_TO_POINTER(pinfo->num));
                                }
                        }
                }
        }

        if (tp->id <= RTMPT_ID_MAX)
        {
                sDesc = rtmpt_get_packet_desc(tvb, iBodyOffset, iBodyRemain, rconv, cdir, tp, &deschasopcode);
        }

        if (tp->id>RTMPT_ID_MAX) {
                col_append_sep_str(pinfo->cinfo, COL_INFO, "|",
                                val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
                col_set_fence(pinfo->cinfo, COL_INFO);
        } else if (sDesc) {
                col_append_sep_str(pinfo->cinfo, COL_INFO, "|", sDesc);
                col_set_fence(pinfo->cinfo, COL_INFO);
        } else {
                col_append_sep_str(pinfo->cinfo, COL_INFO, "|",
                                val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"));
                col_set_fence(pinfo->cinfo, COL_INFO);
        }

        if (tree)
        {
                proto_tree *rtmpt_tree     = NULL;
                proto_tree *rtmptroot_tree = NULL;
                proto_item *ti;
                ti = proto_tree_add_item(tree, proto_rtmpt, tvb, offset, -1, ENC_NA);

                if (tp->id > RTMPT_ID_MAX) {
                        /* Dissect handshake */
                        proto_item_append_text(ti, " (%s)",
                                               val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
                        rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt);
                        rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_handshake, NULL,
                                                 val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));

                        if (tp->id == RTMPT_TYPE_HANDSHAKE_1)
                        {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c0, tvb, 0, 1, ENC_NA);
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c1, tvb, 1, 1536, ENC_NA);
                        }
                        else if (tp->id == RTMPT_TYPE_HANDSHAKE_2)
                        {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s0, tvb, 0, 1, ENC_NA);
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s1, tvb, 1, 1536, ENC_NA);
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s2, tvb, 1537, 1536, ENC_NA);
                        }
                        else if (tp->id == RTMPT_TYPE_HANDSHAKE_3)
                        {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c2, tvb, 0, 1536, ENC_NA);
                        }

                        return;
                }

                if (sDesc && deschasopcode) {
                        proto_item_append_text(ti, " (%s)", sDesc);
                } else if (sDesc) {
                        proto_item_append_text(ti, " (%s %s)",
                                               val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"), sDesc);
                } else {
                        proto_item_append_text(ti, " (%s)",
                                               val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"));
                }
                rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt);

                /* Function call/response matching */
                if (tp->otherframe != 0) {
                        proto_tree_add_uint(rtmptroot_tree,
                                            tp->isresponse ? hf_rtmpt_function_response : hf_rtmpt_function_call,
                                            tvb, offset, tp->bhlen+tp->mhlen+tp->len,
                                            tp->otherframe);
                }

                /* Dissect header fields */
                rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, tp->bhlen+tp->mhlen, ett_rtmpt_header, NULL, RTMPT_TEXT_RTMP_HEADER);
/*                proto_item_append_text(ti, " (%s)", val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); */

                if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_format, tvb, offset + 0, 1, ENC_BIG_ENDIAN);
                if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_csid, tvb, offset + 0, tp->bhlen, ENC_BIG_ENDIAN);
                if (tp->fmt <= 2) {
                        if (tp->fmt > 0) {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp_delta, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN);
                        } else {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN);
                        }
                        if (haveETS) {
                                proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_ets, tvb, offset + tp->bhlen + tp->mhlen - 4, 4, ENC_BIG_ENDIAN);
                        }
                }
                if ((tp->fmt>0 && !haveETS) || tp->fmt == 3) {
                        proto_tree_add_uint_format_value(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 0, tp->ts, "%d (calculated)", tp->ts);
                }
                if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_body_size, tvb, offset + tp->bhlen + 3, 3, ENC_BIG_ENDIAN);
                if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_typeid, tvb, offset + tp->bhlen + 6, 1, ENC_BIG_ENDIAN);
                if (tp->fmt <= 0) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_streamid, tvb, offset + tp->bhlen + 7, 4, ENC_LITTLE_ENDIAN);

                /* Dissect body */
                if (tp->len == 0) return;
                offset = iBodyOffset;

                rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_body, NULL, RTMPT_TEXT_RTMP_BODY);

                switch (tp->cmd) {
                case RTMPT_TYPE_CHUNK_SIZE:
                case RTMPT_TYPE_ABORT_MESSAGE:
                case RTMPT_TYPE_ACKNOWLEDGEMENT:
                case RTMPT_TYPE_UCM:
                case RTMPT_TYPE_WINDOW:
                case RTMPT_TYPE_PEER_BANDWIDTH:
                        dissect_rtmpt_body_scm(tvb, offset, rtmpt_tree, tp->cmd);
                        break;
                case RTMPT_TYPE_COMMAND_AMF0:
                case RTMPT_TYPE_DATA_AMF0:
                        dissect_rtmpt_body_command(tvb, offset, rtmpt_tree, FALSE);
                        break;
                case RTMPT_TYPE_COMMAND_AMF3:
                case RTMPT_TYPE_DATA_AMF3:
                        dissect_rtmpt_body_command(tvb, offset, rtmpt_tree, TRUE);
                        break;
                case RTMPT_TYPE_AUDIO_DATA:
                        dissect_rtmpt_body_audio(tvb, offset, rtmpt_tree);
                        break;
                case RTMPT_TYPE_VIDEO_DATA:
                        dissect_rtmpt_body_video(tvb, offset, rtmpt_tree);
                        break;
                case RTMPT_TYPE_AGGREGATE:
                        dissect_rtmpt_body_aggregate(tvb, offset, rtmpt_tree);
                        break;
                }
        }
}

/* Unchunk a data stream into individual RTMP packets */

static void
dissect_rtmpt_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, rtmpt_conv_t *rconv, int cdir, guint32 seq, guint32 lastackseq)
{
        int     offset = 0;
        int     remain;
        int     want;

        guint8  header_type;
        int     basic_hlen;
        int     message_hlen;

        guint32 id;
        guint32 ts     = 0;
        guint32 tsd    = 0;
        int     body_len;
        guint8  cmd;
        guint32 src;
        int     chunk_size;
        guint32 save_seq = 0;

        rtmpt_frag_t   *tf;
        rtmpt_id_t     *ti;
        rtmpt_packet_t *tp;
        tvbuff_t       *pktbuf;

        remain = tvb_reported_length(tvb);
        if (!remain)
                return;

        RTMPT_DEBUG("Segment: cdir=%d seq=%d-%d\n", cdir, seq, seq+remain-1);

        if (pinfo->fd->flags.visited) {
                /* Already done the work, so just dump the existing state */
                wmem_stack_t *packets;

                /* List all RTMP packets terminating in this TCP segment, from end to beginning */

                packets = wmem_stack_new(wmem_packet_scope());
                wmem_stack_push(packets, 0);

                tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], seq+remain-1);
                while (tp && tp->lastseq >= seq && tp->lastseq >= save_seq) {
                        wmem_stack_push(packets, tp);
                        save_seq = tp->lastseq+1; /* Ensure sequence is increasing */
                        tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], tp->lastseq-1);
                }

                /* Dissect the generated list in reverse order (beginning to end) */

                while ((tp=(rtmpt_packet_t *)wmem_stack_pop(packets)) != NULL) {
                        if (tp->resident) {
                                pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
                                add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
                        } else {
                                pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have);
                        }
                        dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
                }

                return;
        }

        while (remain>0) {
                tf = NULL;
                ti = NULL;
                tp = NULL;

                /* Check for outstanding fragmented headers/chunks first */

                if (offset == 0) {
                        tf = (rtmpt_frag_t *)wmem_tree_lookup32_le(rconv->frags[cdir], seq+offset-1);

                        if (tf) {
                                /* May need to reassemble cross-TCP-segment fragments */
                                RTMPT_DEBUG("  tf seq=%d lseq=%d h=%d l=%d\n", tf->seq, tf->lastseq, tf->have, tf->len);
                                if (tf->have >= tf->len || seq+offset < tf->seq || seq+offset > tf->lastseq+tf->len-tf->have) {
                                        tf = NULL;
                                } else if (!tf->ishdr) {
                                        ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], tf->saved.id);
                                        if (ti) {
                                                tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1);
                                        }
                                        if (tp && tp->chunkwant) {
                                                goto unchunk;
                                        }
                                        tf = NULL;
                                        ti = NULL;
                                        tp = NULL;
                                }

                                if (tf) {
                                        /* The preceding segment contained an incomplete chunk header */

                                        want = tf->len - tf->have;
                                        if (remain<want)
                                                want = remain;

                                        tvb_memcpy(tvb, tf->saved.d+tf->have, offset, want);

                                        id = tf->saved.d[0];
                                        header_type = (id>>6) & 3;
                                        basic_hlen = rtmpt_basic_header_length(id);

                                        if ((header_type < 3) && (tf->have < (basic_hlen+3)) && (tf->have+want >= (basic_hlen+3))) {
                                                if (pntoh24(tf->saved.d+basic_hlen) == 0xffffff) {
                                                        tf->len += 4;
                                                }
                                        }

                                        tf->have += want;
                                        tf->lastseq = seq+want-1;
                                        remain -= want;
                                        offset += want;

                                        if (tf->have < tf->len) {
                                                return;
                                        }
                                }
                        }
                }

                if (!tf) {
                        /* No preceeding data, get header data starting at current position */
                        id = tvb_get_guint8(tvb, offset);

                        if (id == RTMPT_MAGIC && seq+offset == RTMPT_HANDSHAKE_OFFSET_1) {
                                header_type = 4;
                                basic_hlen = 1;
                                message_hlen = 0;
                                id = lastackseq == 1 ? RTMPT_TYPE_HANDSHAKE_1 : RTMPT_TYPE_HANDSHAKE_2;
                        } else if (seq+offset == RTMPT_HANDSHAKE_OFFSET_2) {
                                header_type = 4;
                                basic_hlen = 0;
                                message_hlen = 0;
                                id = RTMPT_TYPE_HANDSHAKE_3;
                        } else {
                                header_type = (id>>6) & 3;
                                basic_hlen = rtmpt_basic_header_length(id);
                                message_hlen = rtmpt_message_header_length(id);

                                if ((header_type < 3) && (remain >= (basic_hlen+3))) {
                                        if (tvb_get_ntoh24(tvb, offset+basic_hlen) == 0xffffff) {
                                                message_hlen += 4;
                                        }
                                }

                                if (remain < (basic_hlen+message_hlen)) {
                                        /* Ran out of packet mid-header, save and try again next time */
                                        tf = wmem_new(wmem_file_scope(), rtmpt_frag_t);
                                        tf->ishdr = 1;
                                        tf->seq = seq + offset;
                                        tf->lastseq = tf->seq + remain - 1;
                                        tf->len = basic_hlen + message_hlen;
                                        tvb_memcpy(tvb, tf->saved.d, offset, remain);
                                        tf->have = remain;
                                        wmem_tree_insert32(rconv->frags[cdir], seq+offset, tf);
                                        return;
                                }

                                id = id & 0x3f;
                                if (id == 0)
                                        id = tvb_get_guint8(tvb, offset+1) + 64;
                                else if (id == 1)
                                        id = tvb_get_letohs(tvb, offset+1) + 64;
                        }

                } else {
                        /* Use reassembled header data */
                        id = tf->saved.d[0];
                        header_type = (id>>6) & 3;
                        basic_hlen = rtmpt_basic_header_length(id);
                        message_hlen = tf->len - basic_hlen;

                        id = id & 0x3f;
                        if (id == 0)
                                id = tf->saved.d[1] + 64;
                        else if (id == 1)
                                id = pletoh16(tf->saved.d+1) + 64;
                }

                /* Calculate header values, defaulting from previous packets with same id */

                if (id <= RTMPT_ID_MAX)
                        ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], id);
                if (ti)
                        tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1);

                if (header_type == 0)
                        src = tf ? pntoh32(tf->saved.d+basic_hlen+7) : tvb_get_ntohl(tvb, offset+basic_hlen+7);
                else if (ti)
                        src = ti->src;
                else src = 0;

                if (header_type < 2)
                        cmd = tf ? tf->saved.d[basic_hlen+6] : tvb_get_guint8(tvb, offset+basic_hlen+6);
                else if (ti)
                        cmd = ti->cmd;
                else
                        cmd = 0;

                /* Calculate chunk_size now as a last-resort default payload length */
                if (id > RTMPT_ID_MAX) {
                        if (id == RTMPT_TYPE_HANDSHAKE_1)
                                chunk_size = body_len = 1536;
                        else if (id == RTMPT_TYPE_HANDSHAKE_2)
                                chunk_size = body_len = 3072;
                        else /* if (id == RTMPT_TYPE_HANDSHAKE_3) */
                                chunk_size = body_len = 1536;
                } else {
                        chunk_size = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->chunksize[cdir], seq+offset-1));
                        if (!chunk_size)
                                chunk_size = RTMPT_DEFAULT_CHUNK_SIZE;

                        if (header_type < 2)
                                body_len = tf ? pntoh24(tf->saved.d+basic_hlen+3) : tvb_get_ntoh24(tvb, offset+basic_hlen+3);
                        else if (ti)
                                body_len = ti->len;
                        else
                                body_len = chunk_size;

                        if (body_len > (gint)rtmpt_max_packet_size) {
                                return;
                        }
                }

                if (!ti || !tp || header_type<3 || tp->have == tp->want || tp->chunkhave != tp->chunkwant) {
                        /* Start a new packet if:
                         *   no previous packet with same id
                         *   not a short 1-byte header
                         *   previous packet with same id was complete
                         *   previous incomplete chunk not handled by fragment handler
                         */
                        RTMPT_DEBUG("New packet cdir=%d seq=%d ti=%p tp=%p header_type=%d header_len=%d id=%d tph=%d tpw=%d len=%d cs=%d\n",
                                    cdir, seq+offset,
                                    ti, tp, header_type, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size);

                        if (!ti) {
                                ti = wmem_new(wmem_file_scope(), rtmpt_id_t);
                                ti->packets = wmem_tree_new(wmem_file_scope());
                                ti->ts  = 0;
                                ti->tsd = 0;
                                wmem_tree_insert32(rconv->ids[cdir], id, ti);
                        }

                        if (header_type == 0) {
                                ts = tf ? pntoh24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen);
                                if (ts == 0xffffff) {
                                        ts = tf ? pntoh32(tf->saved.d+basic_hlen+11) : tvb_get_ntohl(tvb, offset+basic_hlen+11);
                                }
                                tsd = ts - ti->ts;
                        } else if (header_type < 3) {
                                tsd = tf ? pntoh24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen);
                                if (tsd == 0xffffff) {
                                        ts  = tf ? pntoh32(tf->saved.d+basic_hlen+message_hlen-4) : tvb_get_ntohl(tvb, offset+basic_hlen+message_hlen-4);
                                        tsd = ti->tsd; /* questionable */
                                } else {
                                        ts  = ti->ts + tsd;
                                }
                        } else {
                                ts  = ti->ts + ti->tsd;
                                tsd = ti->tsd;
                        }

                        /* create a new packet structure */
                        tp             = wmem_new(wmem_file_scope(), rtmpt_packet_t);
                        tp->seq        = tp->lastseq = tf ? tf->seq : seq+offset;
                        tp->have       = 0;
                        tp->want       = basic_hlen + message_hlen + body_len;
                        tp->chunkwant  = 0;
                        tp->chunkhave  = 0;
                        tp->bhlen      = basic_hlen;
                        tp->mhlen      = message_hlen;
                        tp->fmt        = header_type;
                        tp->id         = id;
                        tp->ts         = ts;
                        tp->len        = body_len;
                        if (id > RTMPT_ID_MAX)
                                tp->cmd = id;
                        else
                                tp->cmd = cmd & 0x7f;
                        tp->src        = src;
                        tp->txid       = 0;
                        tp->isresponse = FALSE;
                        tp->otherframe = 0;

                        /* Save the header information for future defaulting needs */
                        ti->ts  = ts;
                        ti->tsd = tsd;
                        ti->len = body_len;
                        ti->cmd = cmd;
                        ti->src = src;

                        /* store against the id only until unchunking is complete */
                        wmem_tree_insert32(ti->packets, tp->seq, tp);

                        if (!tf && body_len <= chunk_size && tp->want <= remain) {
                                /* The easy case - a whole packet contiguous and fully within this segment */
                                tp->resident    = FALSE;
                                tp->data.offset = offset;
                                tp->lastseq     = seq+offset+tp->want-1;
                                tp->have        = tp->want;

                                wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);

                                pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have);
                                dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);

                                offset += tp->want;
                                remain -= tp->want;
                                continue;

                        } else {
                                /* Some more reassembly required */
                                tp->resident = TRUE;
                                tp->data.p = (guint8 *)wmem_alloc(wmem_file_scope(), tp->bhlen+tp->mhlen+tp->len);

                                if (tf && tf->ishdr) {
                                        memcpy(tp->data.p, tf->saved.d, tf->len);
                                } else {
                                        tvb_memcpy(tvb, tp->data.p, offset, basic_hlen+message_hlen);
                                        offset += basic_hlen + message_hlen;
                                        remain -= basic_hlen + message_hlen;
                                }

                                tp->lastseq = seq+offset-1;
                                tp->have = basic_hlen + message_hlen;

                                if (tp->have == tp->want) {
                                        wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);

                                        pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
                                        add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
                                        dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
                                        continue;
                                }

                                tp->chunkwant = chunk_size;
                                if (tp->chunkwant > tp->want-tp->have)
                                        tp->chunkwant = tp->want - tp->have;
                        }
                } else {
                        RTMPT_DEBUG("Old packet cdir=%d seq=%d ti=%p tp=%p header_len=%d id=%d tph=%d tpw=%d len=%d cs=%d\n",
                                    cdir, seq+offset,
                                    ti, tp, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size);

                        tp->chunkwant = chunk_size;
                        if (tp->chunkwant > tp->want-tp->have)
                                tp->chunkwant = tp->want - tp->have;

                        offset += basic_hlen + message_hlen;
                        remain -= basic_hlen + message_hlen;
                }

                tf = NULL;

                /* Last case to deal with is unchunking the packet body */
        unchunk:
                want = tp->chunkwant - tp->chunkhave;
                if (want > remain)
                        want = remain;
                RTMPT_DEBUG("  cw=%d ch=%d r=%d w=%d\n", tp->chunkwant, tp->chunkhave, remain, want);

                tvb_memcpy(tvb, tp->data.p+tp->have, offset, want);

                if (tf) {
                        tf->have += want;
                        tf->lastseq = seq+offset+want-1;
                }
                tp->lastseq = seq+offset+want-1;
                tp->have += want;
                tp->chunkhave += want;

                offset += want;
                remain -= want;

                if (tp->chunkhave == tp->chunkwant) {
                        /* Chunk is complete - wait for next header */
                        tp->chunkhave = 0;
                        tp->chunkwant = 0;
                }

                if (tp->have == tp->want) {
                        /* Whole packet is complete */
                        wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);

                        pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
                        add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
                        dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
                } else if (tp->chunkhave < tp->chunkwant) {
                        /* Chunk is split across segment boundary */
                        rtmpt_frag_t *tf2 = wmem_new(wmem_file_scope(), rtmpt_frag_t);
                        tf2->ishdr    = 0;
                        tf2->seq      = seq + offset - want;
                        tf2->lastseq  = tf2->seq + remain - 1 + want;
                        tf2->have     = tp->chunkhave;
                        tf2->len      = tp->chunkwant;
                        tf2->saved.id = tp->id;
                        RTMPT_DEBUG("  inserting tf @ %d\n", seq+offset-want-1);
                        wmem_tree_insert32(rconv->frags[cdir], seq+offset-want-1, tf2);
                }
        }
}

static rtmpt_conv_t *
rtmpt_init_rconv(conversation_t *conv)
{
        rtmpt_conv_t *rconv = wmem_new(wmem_file_scope(), rtmpt_conv_t);
        conversation_add_proto_data(conv, proto_rtmpt, rconv);

        rconv->seqs[0]      = wmem_tree_new(wmem_file_scope());
        rconv->seqs[1]      = wmem_tree_new(wmem_file_scope());
        rconv->frags[0]     = wmem_tree_new(wmem_file_scope());
        rconv->frags[1]     = wmem_tree_new(wmem_file_scope());
        rconv->ids[0]       = wmem_tree_new(wmem_file_scope());
        rconv->ids[1]       = wmem_tree_new(wmem_file_scope());
        rconv->packets[0]   = wmem_tree_new(wmem_file_scope());
        rconv->packets[1]   = wmem_tree_new(wmem_file_scope());
        rconv->chunksize[0] = wmem_tree_new(wmem_file_scope());
        rconv->chunksize[1] = wmem_tree_new(wmem_file_scope());
        rconv->txids[0]     = wmem_tree_new(wmem_file_scope());
        rconv->txids[1]     = wmem_tree_new(wmem_file_scope());

        return rconv;
}

static int
dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
{
        conversation_t *conv;
        rtmpt_conv_t   *rconv;
        int             cdir;
        struct tcpinfo *tcpinfo;

        /* Reject the packet if data is NULL */
        if (data == NULL) {
                return 0;
        }
        tcpinfo = (struct tcpinfo*)data;

        conv = find_or_create_conversation(pinfo);

        rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
        if (!rconv) {
                rconv = rtmpt_init_rconv(conv);
        }

        cdir = (addresses_equal(conversation_key_addr1(conv->key_ptr), &pinfo->src) &&
                addresses_equal(conversation_key_addr2(conv->key_ptr), &pinfo->dst) &&
                conversation_key_port1(conv->key_ptr) == pinfo->srcport &&
                conversation_key_port2(conv->key_ptr) == pinfo->destport) ? 0 : 1;

        dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, tcpinfo->seq, tcpinfo->lastackseq);
        return tvb_reported_length(tvb);
}

static int
dissect_rtmpt_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
        conversation_t *conv;
        rtmpt_conv_t   *rconv;
        int             cdir;
        guint32         seq;
        guint32         lastackseq;
        guint32         offset;
        gint            remain;

        offset = 0;
        remain = tvb_reported_length_remaining(tvb, 0);

        /*
         * Request flow:
         *
         *  POST /open/1
         *    request body is a single non-RTMP byte
         *    response contains a client ID <cid> followed by NL
         *  POST /send/<cid>/<seq>
         *    <seq> starts at 0 after open and increments on each
         *    subsequent post
         *    request body is pure RTMP data
         *    response is a single non-RTMP byte followed by RTMP data
         *  POST /idle/<cid>/<seq>
         *    request contains a single non-RTMP byte
         *    response is a single non-RTMP byte followed by RTMP data
         *  POST /close/<cid>/<seq>
         *    request and response contain a single non-RTMP byte
         *
         * Ideally here we'd know:
         *
         *  1) Whether this is was a HTTP request or response
         *     (this gives us cdir directly)
         *  2) The requested URL (for both cases)
         *     (this tells us the type of framing bytes present,
         *     so whether there are any real bytes present). We
         *     could also use the client ID to identify the
         *     conversation, since each POST is likely to be on
         *     a different TCP connection, and there could be
         *     multiple simultaneous sessions from a single
         *     client (which we don't deal with here.)
         *
         *  As it is, we currently have to just guess, and are
         *  likely easily confused.
         */

        cdir = pinfo->srcport == pinfo->match_uint;

        if (cdir) {
                conv = find_conversation(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_endpoint_type(pinfo->ptype), 0, pinfo->srcport, 0);
                if (!conv) {
                        RTMPT_DEBUG("RTMPT new conversation\n");
                        conv = conversation_new(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_endpoint_type(pinfo->ptype), 0, pinfo->srcport, 0);
                }
        } else {
                conv = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_endpoint_type(pinfo->ptype), 0, pinfo->destport, 0);
                if (!conv) {
                        RTMPT_DEBUG("RTMPT new conversation\n");
                        conv = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_endpoint_type(pinfo->ptype), 0, pinfo->destport, 0);
                }
        }

        rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
        if (!rconv) {
                rconv = rtmpt_init_rconv(conv);
        }

        /* Work out a TCP-like sequence numbers for the tunneled data stream.
         * If we've seen the packet before we'll have stored the seq of our
         * last byte against the frame number - since we know how big we are
         * we can work out the seq of our first byte. If this is the first
         * time, we use the stored seq of the last byte of the previous frame
         * plus one. If there is no previous frame then we must be at seq=1!
         * (This is per-conversation and per-direction, of course.) */

        lastackseq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir ^ 1], pinfo->num))+1;

        if (cdir == 1 && lastackseq < 2 && remain == 17) {
                /* Session startup: the client makes an /open/ request and
                 * the server responds with a 16 bytes client
                 * identifier followed by a newline */
                offset += 17;
                remain -= 17;
        } else if (cdir || remain == 1) {
                /* All other server responses start with one byte which
                 * is not part of the RTMP stream. Client /idle/ requests
                 * contain a single byte also not part of the stream. We
                 * must discard these */
                offset++;
                remain--;
        }

        seq = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->seqs[cdir], pinfo->num));

        if (seq == 0) {
                seq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir], pinfo->num));
                seq += remain;
                wmem_tree_insert32(rconv->seqs[cdir], pinfo->num, GINT_TO_POINTER(seq));
        }

        seq -= remain-1;

        RTMPT_DEBUG("RTMPT f=%d cdir=%d seq=%d lastackseq=%d len=%d\n", pinfo->num, cdir, seq, lastackseq, remain);

        if (remain < 1)
                return offset;

        if (offset > 0) {
                tvbuff_t *tvbrtmp = tvb_new_subset_length(tvb, offset, remain);
                dissect_rtmpt_common(tvbrtmp, pinfo, tree, rconv, cdir, seq, lastackseq);
        } else {
                dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, seq, lastackseq);
        }
        return tvb_captured_length(tvb);
}

static gboolean
dissect_rtmpt_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
        conversation_t *conversation;
        if (tvb_reported_length(tvb) >= 12)
        {
                /* To avoid a too high rate of false positive, this heuristics only matches the protocol
                   from the first server response packet and not from the client request packets before.
                   Therefore it is necessary to a "Decode as" to properly decode the first packets */
                struct tcpinfo *tcpinfo = (struct tcpinfo *)data;
                if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2
                    && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1
                    && tvb_get_guint8(tvb, 0) == RTMPT_MAGIC)
                {
                    /* Register this dissector for this conversation */
                    conversation = find_or_create_conversation(pinfo);
                    conversation_set_dissector(conversation, rtmpt_tcp_handle);

                    /* Dissect the packet */
                    dissect_rtmpt_tcp(tvb, pinfo, tree, data);
                    return TRUE;
                }
        }
        return FALSE;
}

static int
dissect_amf(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, void* data _U_)
{
        proto_item *ti;
        proto_tree *amf_tree, *headers_tree, *messages_tree;
        int         offset;
        guint       header_count, message_count, i;
        guint       string_length;
        guint       header_length, message_length;
        gboolean    amf3_encoding = FALSE;

        /*
         * XXX - is "application/x-amf" just AMF3?
         */
        ti = proto_tree_add_item(tree, proto_amf, tvb, 0, -1, ENC_NA);
        amf_tree = proto_item_add_subtree(ti, ett_amf);
        offset = 0;
        proto_tree_add_item(amf_tree, hf_amf_version, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        header_count = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(amf_tree, hf_amf_header_count, tvb, offset, 2, header_count);
        offset += 2;
        if (header_count != 0) {
                headers_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_headers, NULL, "Headers");
                for (i = 0; i < header_count; i++) {
                        string_length = tvb_get_ntohs(tvb, offset);
                        proto_tree_add_item(headers_tree, hf_amf_header_name, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
                        offset += 2 + string_length;
                        proto_tree_add_item(headers_tree, hf_amf_header_must_understand, tvb, offset, 1, ENC_NA);
                        offset += 1;
                        header_length = tvb_get_ntohl(tvb, offset);
                        if (header_length == 0xFFFFFFFF)
                                proto_tree_add_uint_format_value(headers_tree, hf_amf_header_length, tvb, offset, 4, header_length, "Unknown");
                        else
                                proto_tree_add_uint(headers_tree, hf_amf_header_length, tvb, offset, 4, header_length);
                        offset += 4;
                        if (amf3_encoding)
                                offset = dissect_amf3_value_type(tvb, offset, headers_tree, NULL);
                        else
                                offset = dissect_amf0_value_type(tvb, offset, headers_tree, &amf3_encoding, NULL);
                }
        }
        message_count = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(amf_tree, hf_amf_message_count, tvb, offset, 2, message_count);
        offset += 2;
        if (message_count != 0) {
                messages_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_messages, NULL, "Messages");
                for (i = 0; i < message_count; i++) {
                        string_length = tvb_get_ntohs(tvb, offset);
                        proto_tree_add_item(messages_tree, hf_amf_message_target_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
                        offset += 2 + string_length;
                        string_length = tvb_get_ntohs(tvb, offset);
                        proto_tree_add_item(messages_tree, hf_amf_message_response_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
                        offset += 2 + string_length;
                        message_length = tvb_get_ntohl(tvb, offset);
                        if (message_length == 0xFFFFFFFF)
                                proto_tree_add_uint_format_value(messages_tree, hf_amf_message_length, tvb, offset, 4, message_length, "Unknown");
                        else
                                proto_tree_add_uint(messages_tree, hf_amf_message_length, tvb, offset, 4, message_length);
                        offset += 4;
                        offset = dissect_rtmpt_body_command(tvb, offset, messages_tree, FALSE);
                }
        }
        return tvb_captured_length(tvb);
}

void
proto_register_rtmpt(void)
{
        static hf_register_info hf[] = {
/* RTMP Handshake data */
                { &hf_rtmpt_handshake_c0,
                  { "Protocol version", "rtmpt.handshake.c0", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake C0", HFILL }},

                { &hf_rtmpt_handshake_s0,
                  { "Protocol version", "rtmpt.handshake.s0", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake S0", HFILL }},

                { &hf_rtmpt_handshake_c1,
                  { "Handshake data", "rtmpt.handshake.c1", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake C1", HFILL }},

                { &hf_rtmpt_handshake_s1,
                  { "Handshake data", "rtmpt.handshake.s1", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake S1", HFILL }},

                { &hf_rtmpt_handshake_c2,
                  { "Handshake data", "rtmpt.handshake.c2", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake C2", HFILL }},

                { &hf_rtmpt_handshake_s2,
                  { "Handshake data", "rtmpt.handshake.s2", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Handshake S2", HFILL }},

/* RTMP chunk/packet header */
                { &hf_rtmpt_header_format,
                  { "Format", "rtmpt.header.format", FT_UINT8, BASE_DEC,
                    NULL, 0xC0, "RTMPT Basic Header format", HFILL }},

                { &hf_rtmpt_header_csid,
                  { "Chunk Stream ID", "rtmpt.header.csid", FT_UINT8, BASE_DEC,
                    NULL, 0x3F, "RTMPT Basic Header chunk stream ID", HFILL }},

                { &hf_rtmpt_header_timestamp,
                  { "Timestamp", "rtmpt.header.timestamp", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Message Header timestamp", HFILL }},

                { &hf_rtmpt_header_timestamp_delta,
                  { "Timestamp delta", "rtmpt.header.timestampdelta", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Message Header timestamp delta", HFILL }},

                { &hf_rtmpt_header_body_size,
                  { "Body size", "rtmpt.header.bodysize", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Message Header body size", HFILL }},

                { &hf_rtmpt_header_typeid,
                  { "Type ID", "rtmpt.header.typeid", FT_UINT8, BASE_HEX,
                    VALS(rtmpt_opcode_vals), 0x0, "RTMPT Message Header type ID", HFILL }},

                { &hf_rtmpt_header_streamid,
                  { "Stream ID", "rtmpt.header.streamid", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT Header stream ID", HFILL }},

                { &hf_rtmpt_header_ets,
                  { "Extended timestamp", "rtmpt.header.ets", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Message Header extended timestamp", HFILL }},

/* Stream Control Messages */

                { &hf_rtmpt_scm_chunksize,
                  { "Chunk size", "rtmpt.scm.chunksize", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT SCM chunk size", HFILL }},

                { &hf_rtmpt_scm_csid,
                  { "Chunk stream ID", "rtmpt.scm.csid", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT SCM chunk stream ID", HFILL }},

                { &hf_rtmpt_scm_seq,
                  { "Sequence number", "rtmpt.scm.seq", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT SCM acknowledgement sequence number", HFILL }},

                { &hf_rtmpt_scm_was,
                  { "Window acknowledgement size", "rtmpt.scm.was", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT SCM window acknowledgement size", HFILL }},

                { &hf_rtmpt_scm_limittype,
                  { "Limit type", "rtmpt.scm.limittype", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_limit_vals), 0x0, "RTMPT SCM window acknowledgement size", HFILL }},

/* User Control Messages */
                { &hf_rtmpt_ucm_eventtype,
                  { "Event type", "rtmpt.ucm.eventtype", FT_UINT16, BASE_DEC,
                    VALS(rtmpt_ucm_vals), 0x0, "RTMPT UCM event type", HFILL }},

/* Frame links */

                { &hf_rtmpt_function_call,
                  { "Response to this call in frame", "rtmpt.function.call", FT_FRAMENUM, BASE_NONE,
                    NULL, 0x0, "RTMPT function call", HFILL }},

                { &hf_rtmpt_function_response,
                  { "Call for this response in frame", "rtmpt.function.response", FT_FRAMENUM, BASE_NONE,
                    NULL, 0x0, "RTMPT function response", HFILL }},

/* Audio packets */
                { &hf_rtmpt_audio_control,
                  { "Audio control", "rtmpt.audio.control", FT_UINT8, BASE_HEX,
                    NULL, 0x0, "RTMPT Audio control", HFILL }},

                { &hf_rtmpt_audio_format,
                  { "Format", "rtmpt.audio.format", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_audio_codecs), 0xf0, "RTMPT Audio format", HFILL }},

                { &hf_rtmpt_audio_rate,
                  { "Sample rate", "rtmpt.audio.rate", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_audio_rates), 0x0c, "RTMPT Audio sample rate", HFILL }},

                { &hf_rtmpt_audio_size,
                  { "Sample size", "rtmpt.audio.size", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_audio_sizes), 0x02, "RTMPT Audio sample size", HFILL }},

                { &hf_rtmpt_audio_type,
                  { "Channels", "rtmpt.audio.type", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_audio_types), 0x01, "RTMPT Audio channel count", HFILL }},

                { &hf_rtmpt_audio_data,
                  { "Audio data", "rtmpt.audio.data", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Audio data", HFILL }},

/* Video packets */
                { &hf_rtmpt_video_control,
                  { "Video control", "rtmpt.video.control", FT_UINT8, BASE_HEX,
                    NULL, 0x0, "RTMPT Video control", HFILL }},

                { &hf_rtmpt_video_type,
                  { "Type", "rtmpt.video.type", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_video_types), 0xf0, "RTMPT Video type", HFILL }},

                { &hf_rtmpt_video_format,
                  { "Format", "rtmpt.video.format", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_video_codecs), 0x0f, "RTMPT Video format", HFILL }},

                { &hf_rtmpt_video_data,
                  { "Video data", "rtmpt.video.data", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT Video data", HFILL }},

/* Aggregate packets */
                { &hf_rtmpt_tag_type,
                  { "Type", "rtmpt.tag.type", FT_UINT8, BASE_DEC,
                    VALS(rtmpt_tag_vals), 0x0, "RTMPT Aggregate tag type", HFILL }},

                { &hf_rtmpt_tag_datasize,
                  { "Data size", "rtmpt.tag.datasize", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Aggregate tag data size", HFILL }},

                { &hf_rtmpt_tag_timestamp,
                  { "Timestamp", "rtmpt.tag.timestamp", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Aggregate tag timestamp", HFILL }},

                { &hf_rtmpt_tag_ets,
                  { "Timestamp Extended", "rtmpt.tag.ets", FT_UINT8, BASE_DEC,
                    NULL, 0x0, "RTMPT Aggregate tag timestamp extended", HFILL }},

                { &hf_rtmpt_tag_streamid,
                  { "Stream ID", "rtmpt.tag.streamid", FT_UINT24, BASE_DEC,
                    NULL, 0x0, "RTMPT Aggregate tag stream ID", HFILL }},

                { &hf_rtmpt_tag_tagsize,
                  { "Previous tag size", "rtmpt.tag.tagsize", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT Aggregate previous tag size", HFILL }}
        };
        static gint *ett[] = {
                &ett_rtmpt,
                &ett_rtmpt_handshake,
                &ett_rtmpt_header,
                &ett_rtmpt_body,
                &ett_rtmpt_ucm,
                &ett_rtmpt_audio_control,
                &ett_rtmpt_video_control,
                &ett_rtmpt_tag,
                &ett_rtmpt_tag_data
        };

        module_t *rtmpt_module;

        proto_rtmpt = proto_register_protocol("Real Time Messaging Protocol", "RTMPT", "rtmpt");
        proto_register_field_array(proto_rtmpt, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));

        rtmpt_module = prefs_register_protocol(proto_rtmpt, NULL);
        prefs_register_bool_preference(rtmpt_module, "desegment",
                                       "Reassemble RTMPT messages spanning multiple TCP segments",
                                       "Whether the RTMPT dissector should reassemble messages spanning multiple TCP segments."
                                       " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\""
                                       " in the TCP protocol settings.",
                                       &rtmpt_desegment);

        prefs_register_uint_preference(rtmpt_module, "max_packet_size",
                                       "Maximum packet size",
                                       "The largest acceptable packet size for reassembly",
                                       10, &rtmpt_max_packet_size);

}

void
proto_register_amf(void)
{
        static hf_register_info hf[] = {
                { &hf_amf_version,
                  { "AMF version", "amf.version", FT_UINT16, BASE_DEC,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_header_count,
                  { "Header count", "amf.header_count", FT_UINT16, BASE_DEC,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_header_name,
                  { "Name", "amf.header.name", FT_UINT_STRING, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_header_must_understand,
                  { "Must understand", "amf.header.must_understand", FT_BOOLEAN, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_header_length,
                  { "Length", "amf.header.length", FT_UINT32, BASE_DEC,
                    NULL, 0x0, NULL, HFILL }},

#if 0
                { &hf_amf_header_value_type,
                  { "Value type", "amf.header.value_type", FT_UINT32, BASE_HEX,
                    VALS(rtmpt_type_vals), 0x0, NULL, HFILL }},
#endif

                { &hf_amf_message_count,
                  { "Message count", "amf.message_count", FT_UINT16, BASE_DEC,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_message_target_uri,
                  { "Target URI", "amf.message.target_uri", FT_UINT_STRING, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_message_response_uri,
                  { "Response URI", "amf.message.response_uri", FT_UINT_STRING, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_message_length,
                  { "Length", "amf.message.length", FT_UINT32, BASE_DEC,
                    NULL, 0x0, NULL, HFILL }},


/* AMF basic types */
                { &hf_amf_amf0_type,
                  { "AMF0 type", "amf.amf0_type", FT_UINT8, BASE_HEX,
                    VALS(amf0_type_vals), 0x0, NULL, HFILL }},

                { &hf_amf_amf3_type,
                  { "AMF3 type", "amf.amf3_type", FT_UINT8, BASE_HEX,
                    VALS(amf3_type_vals), 0x0, NULL, HFILL }},

                { &hf_amf_number,
                  { "Number", "amf.number", FT_DOUBLE, BASE_NONE,
                    NULL, 0x0, "AMF number", HFILL }},

                { &hf_amf_integer,
                  { "Integer", "amf.integer", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT AMF3 integer", HFILL }},

                { &hf_amf_boolean,
                  { "Boolean", "amf.boolean", FT_BOOLEAN, BASE_NONE,
                    NULL, 0x0, "AMF boolean", HFILL }},

                { &hf_amf_stringlength,
                  { "String length", "amf.stringlength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF string length", HFILL }},

                { &hf_amf_string,
                  { "String", "amf.string", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF string", HFILL }},

                { &hf_amf_string_reference,
                  { "String reference", "amf.string_reference", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT AMF3 string reference", HFILL }},

                { &hf_amf_object_reference,
                  { "Object reference", "amf.object_reference", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF object reference", HFILL }},

                { &hf_amf_date,
                  { "Date", "amf.date", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL,
                    NULL, 0x0, "AMF date", HFILL }},

#if 0
                { &hf_amf_longstringlength,
                  { "String length", "amf.longstringlength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF long string length", HFILL }},
#endif

                { &hf_amf_longstring,
                  { "Long string", "amf.longstring", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF long string", HFILL }},

                { &hf_amf_xml_doc,
                  { "XML document", "amf.xml_doc", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF XML document", HFILL }},

                { &hf_amf_xmllength,
                  { "XML text length", "amf.xmllength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF E4X XML length", HFILL }},

                { &hf_amf_xml,
                  { "XML", "amf.xml", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF E4X XML", HFILL }},

                { &hf_amf_int64,
                  { "Int64", "amf.int64", FT_INT64, BASE_DEC,
                    NULL, 0x0, "AMF int64", HFILL }},

                { &hf_amf_bytearraylength,
                  { "ByteArray length", "amf.bytearraylength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "RTMPT AMF3 ByteArray length", HFILL }},

                { &hf_amf_bytearray,
                  { "ByteArray", "amf.bytearray", FT_BYTES, BASE_NONE,
                    NULL, 0x0, "RTMPT AMF3 ByteArray", HFILL }},

/* AMF object types and subfields of the object types */
                { &hf_amf_object,
                  { "Object", "amf.object", FT_NONE, BASE_NONE,
                    NULL, 0x0, "AMF object", HFILL }},

                { &hf_amf_traitcount,
                  { "Trait count", "amf.traitcount", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF count of traits for an object", HFILL }},

                { &hf_amf_classnamelength,
                  { "Class name length", "amf.classnamelength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF class name length", HFILL }},

                { &hf_amf_classname,
                  { "Class name", "amf.classname", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF class name", HFILL }},

                { &hf_amf_membernamelength,
                  { "Member name length", "amf.membernamelength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF member name length", HFILL }},

                { &hf_amf_membername,
                  { "Member name", "amf.membername", FT_STRING, BASE_NONE,
                    NULL, 0x0, "AMF member name", HFILL }},

                { &hf_amf_trait_reference,
                  { "Trait reference", "amf.trait_reference", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF trait reference", HFILL }},

                { &hf_amf_ecmaarray,
                  { "ECMA array", "amf.ecmaarray", FT_NONE, BASE_NONE,
                    NULL, 0x0, "AMF ECMA array", HFILL }},

                { &hf_amf_strictarray,
                  { "Strict array", "amf.strictarray", FT_NONE, BASE_NONE,
                    NULL, 0x0, "AMF strict array", HFILL }},

                { &hf_amf_array,
                  { "Array", "amf.array", FT_NONE, BASE_NONE,
                    NULL, 0x0, "RTMPT AMF3 array", HFILL }},

                { &hf_amf_arraylength,
                  { "Array length", "amf.arraylength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF array length", HFILL }},

                { &hf_amf_arraydenselength,
                  { "Length of dense portion", "amf.arraydenselength", FT_UINT32, BASE_DEC,
                    NULL, 0x0, "AMF length of dense portion of array", HFILL }},

                { &hf_amf_end_of_object_marker,
                  { "End Of Object Marker", "amf.end_of_object_marker", FT_NONE, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_end_of_associative_part,
                  { "End of associative part", "amf.end_of_associative_part", FT_NONE, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},

                { &hf_amf_end_of_dynamic_members,
                  { "End Of dynamic members", "amf.end_of_dynamic_members", FT_NONE, BASE_NONE,
                    NULL, 0x0, NULL, HFILL }},
        };
        static gint *ett[] = {
                &ett_amf,
                &ett_amf_headers,
                &ett_amf_messages,
                &ett_amf_value,
                &ett_amf_property,
                &ett_amf_string,
                &ett_amf_array_element,
                &ett_amf_traits,
                &ett_amf_trait_member,
        };

        proto_amf = proto_register_protocol("Action Message Format", "AMF", "amf");
        proto_register_field_array(proto_amf, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
}

void
proto_reg_handoff_rtmpt(void)
{
        dissector_handle_t amf_handle;

        heur_dissector_add("tcp", dissect_rtmpt_heur, "RTMPT over TCP", "rtmpt_tcp", proto_rtmpt, HEURISTIC_DISABLE);
        rtmpt_tcp_handle = create_dissector_handle(dissect_rtmpt_tcp, proto_rtmpt);
        dissector_add_uint_with_preference("tcp.port", RTMP_PORT, rtmpt_tcp_handle);

        rtmpt_http_handle = create_dissector_handle(dissect_rtmpt_http, proto_rtmpt);
        dissector_add_string("media_type", "application/x-fcs", rtmpt_http_handle);

        amf_handle = create_dissector_handle(dissect_amf, proto_amf);
        dissector_add_string("media_type", "application/x-amf", amf_handle);
}

/*
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
