aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-rtmpt.c
diff options
context:
space:
mode:
authorJaap Keuter <jaap.keuter@xs4all.nl>2010-10-30 16:40:46 +0000
committerJaap Keuter <jaap.keuter@xs4all.nl>2010-10-30 16:40:46 +0000
commit31e26fdc74217297f5fb082576452ee116b7dc68 (patch)
tree31b61c6237f1f50efdec4cedc5478b0ebbc833c3 /epan/dissectors/packet-rtmpt.c
parenta4b536a2c0e4f1174a215c42025e4d601342c7bc (diff)
From John Sullivan:
Improve RTMP dissection: * Handle multi-byte csids * Handle extended timestamps * Better timestamp defaulting/calculations * Dechunking should now cope with any clean high quality capture without error (still loses sync on missing/out of order packets, but that's very hard to deal with) * Dissect tunnelled RTMPT * Dissect more packet types * Dissect more AMF0 data types * Dissect audio/video control information * Use official packet/type names * Several new fields * Function call/response frame linking * Push several useful bits of data up the dissection tree or into the info column for ease of use svn path=/trunk/; revision=34720
Diffstat (limited to 'epan/dissectors/packet-rtmpt.c')
-rw-r--r--epan/dissectors/packet-rtmpt.c2467
1 files changed, 1863 insertions, 604 deletions
diff --git a/epan/dissectors/packet-rtmpt.c b/epan/dissectors/packet-rtmpt.c
index 198c482630..66c79ab347 100644
--- a/epan/dissectors/packet-rtmpt.c
+++ b/epan/dissectors/packet-rtmpt.c
@@ -1,6 +1,5 @@
/* packet-rtmpt.c
* Routines for Real Time Messaging Protocol packet dissection
- *
* metatech <metatech@flashmail.com>
*
* $Id$
@@ -44,754 +43,1997 @@
# include "config.h"
#endif
+#include <stdio.h>
+#include <string.h>
+
#include <glib.h>
#include <epan/packet.h>
+#include <epan/emem.h>
#include <epan/conversation.h>
+#include <epan/strutil.h>
#include <epan/prefs.h>
#include "packet-tcp.h"
+/* #define DEBUG_RTMPT 1 */
+
static int proto_rtmpt = -1;
-static int hf_rtmpt_header_objid = -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_function = -1;
-static int hf_rtmpt_header_source = -1;
-static int hf_rtmpt_handshake_data = -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_amf_type = -1;
static int hf_rtmpt_amf_number = -1;
static int hf_rtmpt_amf_boolean = -1;
+static int hf_rtmpt_amf_stringlength = -1;
static int hf_rtmpt_amf_string = -1;
+static int hf_rtmpt_amf_reference = -1;
+static int hf_rtmpt_amf_date = -1;
+static int hf_rtmpt_amf_longstringlength = -1;
+static int hf_rtmpt_amf_longstring = -1;
+static int hf_rtmpt_amf_xml = -1;
+static int hf_rtmpt_amf_int64 = -1;
+
+static int hf_rtmpt_amf_object = -1;
+static int hf_rtmpt_amf_ecmaarray = -1;
+static int hf_rtmpt_amf_strictarray = -1;
+static int hf_rtmpt_amf_arraylength = -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_object = -1;
+static gint ett_rtmpt_ucm = -1;
+static gint ett_rtmpt_value = -1;
static gint ett_rtmpt_property = -1;
+static gint ett_rtmpt_string = -1;
+static gint ett_rtmpt_object = -1;
+static gint ett_rtmpt_mixed_array = -1;
+static gint ett_rtmpt_array = -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;
-typedef struct
-{
- guint8 amf_num;
- guint32 frame_modified;
- guint32 length_remaining;
- guint32 last_length;
- guint8 data_type;
- tvbuff_t* dechunk_buffer;
-} rtmpt_chunk_data_t;
-
-
-/* current_chunks is used to keep track of the chunk data on the initial pass through the packets */
-typedef struct
-{
- GHashTable *current_chunks; /* ONLY USE ON THE FIRST PASS THROUGH THE DATA! */
- guint previous_frame_number;
- guint current_chunk_size;
- guint is_rtmpe;
-} rtmpt_conversation_data_t;
-
-
-/* initial_chunk_data is starting state of the index values used to decode the packet */
-typedef struct
-{
- GHashTable *initial_chunks;
- guint initial_chunk_size;
-} rtmpt_packet_data_t;
-
-
-#define RTMP_PORT 1935
-
-#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_TYPE_NUMBER 0x00
-#define RTMPT_TYPE_BOOLEAN 0x01
-#define RTMPT_TYPE_STRING 0x02
-#define RTMPT_TYPE_OBJECT 0x03
-#define RTMPT_TYPE_MOVIECLIP 0x04
-#define RTMPT_TYPE_NULL 0x05
-#define RTMPT_TYPE_UNDEFINED 0x06
-#define RTMPT_TYPE_REFERENCE 0x07
-#define RTMPT_TYPE_MIXED_ARRAY 0x08
-#define RTMPT_TYPE_END_OF_OBJECT 0x09
-#define RTMPT_TYPE_ARRAY 0x0A
-#define RTMPT_TYPE_DATE 0x0B
-#define RTMPT_TYPE_LONG_STRING 0x0C
-#define RTMPT_TYPE_UNSUPPORTED 0x0D
-#define RTMPT_TYPE_RECORDSET 0x0E
-#define RTMPT_TYPE_XML 0x0F
-#define RTMPT_TYPE_CLASS_OBJECT 0x10
-#define RTMPT_TYPE_AMF3_OBJECT 0x11
-
-#define RTMPT_TEXT_RTMP_HEADER "RTMP Header"
-#define RTMPT_TEXT_RTMP_BODY "RTMP Body"
-#define RTMPT_TEXT_AMF_OBJECT "AMF Object"
-#define RTMPT_TEXT_AMF_PROPERTY "AMF Object Property"
+#define RTMP_PORT 1935
+
+#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
+
+/* 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. */
+/* #define RTMPT_MAX_PACKET_SIZE 131072 */
+/* #define RTMPT_MAX_PACKET_SIZE 262144 */
+#define RTMPT_MAX_PACKET_SIZE 524288
+
+#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_BYTES_READ 0x03
-#define RTMPT_TYPE_PING 0x04
-#define RTMPT_TYPE_SERVER_BANDWIDTH 0x05
-#define RTMPT_TYPE_CLIENT_BANDWIDTH 0x06
+#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_FLEX_STREAM_SEND 0x0F
-#define RTMPT_TYPE_FLEX_SHARED_OBJECT 0x10
-#define RTMPT_TYPE_FLEX_MESSAGE 0x11
-#define RTMPT_TYPE_NOTIFY 0x12
-#define RTMPT_TYPE_SHARED_OBJECT 0x13
-#define RTMPT_TYPE_INVOKE 0x14
-#define RTMPT_TYPE_FLV 0x16
-
-#define RTMPT_TYPE_HANDSHAKE_1 0xFA
-#define RTMPT_TYPE_HANDSHAKE_2 0xFB
-#define RTMPT_TYPE_HANDSHAKE_3 0xFC
+#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_AMF_NUMBER 0x00
+#define RTMPT_AMF_BOOLEAN 0x01
+#define RTMPT_AMF_STRING 0x02
+#define RTMPT_AMF_OBJECT 0x03
+#define RTMPT_AMF_MOVIECLIP 0x04
+#define RTMPT_AMF_NULL 0x05
+#define RTMPT_AMF_UNDEFINED 0x06
+#define RTMPT_AMF_REFERENCE 0x07
+#define RTMPT_AMF_ECMA_ARRAY 0x08
+#define RTMPT_AMF_END_OF_OBJECT 0x09
+#define RTMPT_AMF_STRICT_ARRAY 0x0A
+#define RTMPT_AMF_DATE 0x0B
+#define RTMPT_AMF_LONG_STRING 0x0C
+#define RTMPT_AMF_UNSUPPORTED 0x0D
+#define RTMPT_AMF_RECORDSET 0x0E
+#define RTMPT_AMF_XML 0x0F
+#define RTMPT_AMF_TYPED_OBJECT 0x10
+#define RTMPT_AMF_AMF3_MARKER 0x11
+#define RTMPT_AMF_INT64 0x22
+
+#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, "Chunk size" },
- { RTMPT_TYPE_BYTES_READ, "Bytes Read" },
- { RTMPT_TYPE_PING, "Ping" },
- { RTMPT_TYPE_SERVER_BANDWIDTH, "Server BW" },
- { RTMPT_TYPE_CLIENT_BANDWIDTH, "Client BW" },
- { RTMPT_TYPE_AUDIO_DATA, "Audio Data" },
- { RTMPT_TYPE_VIDEO_DATA, "Video Data" },
- { RTMPT_TYPE_FLEX_STREAM_SEND, "Flex Stream" },
- { RTMPT_TYPE_FLEX_SHARED_OBJECT, "Flex Shared Object" },
- { RTMPT_TYPE_FLEX_MESSAGE, "Flex Message" },
- { RTMPT_TYPE_NOTIFY, "Notify" },
- { RTMPT_TYPE_SHARED_OBJECT, "Shared Object" },
- { RTMPT_TYPE_INVOKE, "Invoke" },
- { RTMPT_TYPE_HANDSHAKE_1, "Handshake part 1" },
- { RTMPT_TYPE_HANDSHAKE_2, "Handshake part 2" },
- { RTMPT_TYPE_HANDSHAKE_3, "Handshake part 3" },
- { RTMPT_TYPE_FLV, "FLV Data" },
- { 0, NULL }
+ { 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_type_vals[] = {
- { RTMPT_TYPE_NUMBER, "Number" },
- { RTMPT_TYPE_BOOLEAN, "Boolean" },
- { RTMPT_TYPE_STRING, "String" },
- { RTMPT_TYPE_OBJECT, "Object" },
- { RTMPT_TYPE_MOVIECLIP, "Movie clip" },
- { RTMPT_TYPE_NULL, "Null" },
- { RTMPT_TYPE_UNDEFINED, "Undefined" },
- { RTMPT_TYPE_REFERENCE, "Reference" },
- { RTMPT_TYPE_MIXED_ARRAY, "Mixed array" },
- { RTMPT_TYPE_END_OF_OBJECT, "End of object" },
- { RTMPT_TYPE_ARRAY, "Array" },
- { RTMPT_TYPE_LONG_STRING, "Long string" },
- { RTMPT_TYPE_UNSUPPORTED, "Unsupported" },
- { RTMPT_TYPE_RECORDSET, "Record set" },
- { RTMPT_TYPE_XML, "XML" },
- { RTMPT_TYPE_CLASS_OBJECT, "Class object" },
- { RTMPT_TYPE_AMF3_OBJECT, "AMF3 object" },
- { 0, NULL }
+ { RTMPT_AMF_NUMBER, "Number" },
+ { RTMPT_AMF_BOOLEAN, "Boolean" },
+ { RTMPT_AMF_STRING, "String" },
+ { RTMPT_AMF_OBJECT, "Object" },
+ { RTMPT_AMF_MOVIECLIP, "Movie clip" },
+ { RTMPT_AMF_NULL, "Null" },
+ { RTMPT_AMF_UNDEFINED, "Undefined" },
+ { RTMPT_AMF_REFERENCE, "Reference" },
+ { RTMPT_AMF_ECMA_ARRAY, "ECMA array" },
+ { RTMPT_AMF_END_OF_OBJECT, "End of object" },
+ { RTMPT_AMF_STRICT_ARRAY, "Strict array" },
+ { RTMPT_AMF_DATE, "Date" },
+ { RTMPT_AMF_LONG_STRING, "Long string" },
+ { RTMPT_AMF_UNSUPPORTED, "Unsupported" },
+ { RTMPT_AMF_RECORDSET, "Record set" },
+ { RTMPT_AMF_XML, "XML" },
+ { RTMPT_AMF_TYPED_OBJECT, "Typed object" },
+ { RTMPT_AMF_AMF3_MARKER, "Switch to AMF3" },
+ { RTMPT_AMF_INT64, "Int64" },
+ { 0, NULL }
+};
+
+static const value_string rtmpt_object_vals[] = {
+ { RTMPT_AMF_OBJECT, "Object" },
+ { RTMPT_AMF_ECMA_ARRAY, "ECMA Array" },
+ { RTMPT_AMF_STRICT_ARRAY, "Strict Array" },
+ { 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 }
+};
+
+static const value_string rtmpt_audio_codecs[] = {
+ { 0, "Uncompressed" },
+ { 1, "ADPCM" },
+ { 2, "MP3" },
+ { 5, "Nellymoser 8kHz Mono" },
+ { 6, "Nellymoser 8kHz Stereo" },
+ { 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 gint rtmpt_header_length_from_type(gint iHeaderType)
+static const value_string rtmpt_audio_types[] = {
+ { 0, "mono" },
+ { 1, "stereo" },
+ { 0, NULL }
+};
+
+static const value_string rtmpt_video_types[] = {
+ { 1, "keyframe" },
+ { 2, "inter-frame" },
+ { 3, "disposable inter-frame" },
+ { 0, NULL }
+};
+
+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 }
+};
+
+/* 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 */
+
+ emem_tree_t *packets;
+} rtmpt_id_t;
+
+/* Historical view of a whole TCP connection
+ */
+typedef struct rtmpt_conv {
+ emem_tree_t *seqs[2];
+ emem_tree_t *frags[2];
+ emem_tree_t *ids[2];
+ emem_tree_t *packets[2];
+ emem_tree_t *chunksize[2];
+ emem_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);
+}
+#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)
{
- gint iHeaderLength = 0;
- switch (iHeaderType) {
- case 0: iHeaderLength = 12; break;
- case 1: iHeaderLength = 8; break;
- case 2: iHeaderLength = 4; break;
- case 3: iHeaderLength = 1; break;
- case 4: iHeaderLength = 1; break; /* Handshake */
+ 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;
}
- return iHeaderLength;
}
+/* 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_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==RTMPT_AMF_END_OF_OBJECT) {
+ rv++;
+ depth--;
+ continue;
+ }
+
+ switch (iObjType) {
+ case RTMPT_AMF_NUMBER:
+ itemlen = 9;
+ break;
+ case RTMPT_AMF_BOOLEAN:
+ itemlen = 2;
+ break;
+ case RTMPT_AMF_STRING:
+ if (remain-rv<3) return remain;
+ itemlen = tvb_get_ntohs(tvb, offset+rv+1) + 3;
+ break;
+ case RTMPT_AMF_NULL:
+ case RTMPT_AMF_UNDEFINED:
+ case RTMPT_AMF_UNSUPPORTED:
+ itemlen= 1;
+ break;
+ case RTMPT_AMF_DATE:
+ itemlen = 11;
+ break;
+ case RTMPT_AMF_LONG_STRING:
+ case RTMPT_AMF_XML:
+ if (remain-rv<5) return remain;
+ itemlen = tvb_get_ntohl(tvb, offset+rv+1) + 5;
+ break;
+ case RTMPT_AMF_INT64:
+ itemlen = 9;
+ break;
+ case RTMPT_AMF_OBJECT:
+ itemlen = 1;
+ depth++;
+ break;
+ case RTMPT_AMF_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_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==RTMPT_AMF_STRING && remain>=3) {
+ iStringLength = tvb_get_ntohs(tvb, offset+1);
+ if (remain>=iStringLength+3) {
+ return tvb_get_ephemeral_string(tvb, offset+3, iStringLength);
+ }
+ }
+
+ if (prop && iObjType==RTMPT_AMF_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)!=RTMPT_AMF_STRING) break;
+
+ iStringLength = tvb_get_ntohs(tvb, offset+2+iPropLength+1);
+ if (remain<2+iPropLength+3+iStringLength) break;
+
+ return tvb_get_ephemeral_string(tvb, offset+2+iPropLength+3, iStringLength);
+ }
+
+ 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_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==RTMPT_AMF_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 ep_strdup_printf("%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 ep_strdup_printf("%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 = NULL;
+ const gchar *sParam = "";
+
+ if (tp->len<2 || remain<2) return NULL;
+
+ iUCM = tvb_get_ntohs(tvb, offset);
+ sFunc = match_strval(iUCM, rtmpt_ucm_vals);
+ if (sFunc==NULL) {
+ *deschasopcode = TRUE;
+ sFunc = ep_strdup_printf("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 = ep_strdup_printf(" %d", tvb_get_ntohl(tvb, offset+2));
+ }
+ } else if (iUCM==RTMPT_UCM_SET_BUFFER) {
+ if (tp->len>=10 && remain>=10) {
+ sParam = ep_strdup_printf(" %d,%dms",
+ tvb_get_ntohl(tvb, offset+2),
+ tvb_get_ntohl(tvb, offset+6));
+ }
+ }
+
+ return ep_strdup_printf("%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_ephemeral_string(tvb, offset+3+soff, slen);
+ 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(se_tree_lookup32(rconv->txids[cdir^1], tp->txid));
+ if (tp->otherframe) {
+ RTMPT_DEBUG("got otherframe=%d\n", tp->otherframe);
+ }
+ }
+ }
+
+ if (sFunc) {
+ if (sParam) {
+ return ep_strdup_printf("%s('%s')", sFunc, sParam);
+ } else {
+ return ep_strdup_printf("%s()", sFunc);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/* Tree dissection helpers for various packet body forms */
static void
-dissect_rtmpt_amf(tvbuff_t *tvb, proto_tree *rtmpt_tree)
+dissect_rtmpt_body_scm(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree, guint scm)
{
- guint offset = 0;
- proto_item *ti = NULL;
+ switch (scm) {
+ case RTMPT_TYPE_CHUNK_SIZE:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_chunksize, tvb, offset, 4, FALSE);
+ break;
+ case RTMPT_TYPE_ABORT_MESSAGE:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_csid, tvb, offset, 4, FALSE);
+ break;
+ case RTMPT_TYPE_ACKNOWLEDGEMENT:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_seq, tvb, offset, 4, FALSE);
+ break;
+ case RTMPT_TYPE_UCM:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_ucm_eventtype, tvb, offset, 2, FALSE);
+ break;
+ case RTMPT_TYPE_WINDOW:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, FALSE);
+ break;
+ case RTMPT_TYPE_PEER_BANDWIDTH:
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, FALSE);
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_limittype, tvb, offset+4, 1, FALSE);
+ break;
+ }
+}
- while (tvb_length_remaining(tvb, offset) > 0)
- {
- guint8 iObjType = 0;
- guint16 iStringLength = 0;
- gint iObjectLength = 0;
- proto_tree *rtmpt_tree_object = NULL;
- proto_item *ti_object = NULL;
+static void
+dissect_rtmpt_body_command(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree, guint amf3)
+{
+ ep_stack_t amftrs;
+ ep_stack_t amftis;
+ ep_stack_t amfols;
+ ep_stack_t amfots;
+ ep_stack_t amfpcs;
+ int depth = 0;
+ int ot = 0;
+ int pc = 0;
+ proto_item *ti_object = NULL;
+ gint iObjectLength = 0;
+
+ amftrs = ep_stack_new();
+ amftis = ep_stack_new();
+ amfols = ep_stack_new();
+ amfots = ep_stack_new();
+ amfpcs = ep_stack_new();
+
+ 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 RTMPT_AMF_AMF3_MARKER then the rest
+ * will be in AMF3. For now, assume AMF0 only. */
+ offset++;
+ }
+
+ while (tvb_length_remaining(tvb, offset) > 0)
+ {
+ guint8 iObjType = 0;
+ gint iPropertyOffset = 0;
+ gint iPropertyLength = 0;
+ guint iStringLength = 0;
+ gint iValueOffset = 0;
+ gint iValueLength = 0; /* signed so we can use CLAMP() */
+ guint iValueExtra = 0;
+ gchar *sValue = "";
+ int hfvalue = -1;
+ guint iPush = 0;
+ proto_tree *rtmpt_tree_prop = NULL;
+ proto_item *ti = NULL;
+
+ rtmpt_tree_prop = rtmpt_tree;
+
+ iValueOffset = offset;
+
+ if (depth>0 && !ot) {
+ iStringLength = tvb_get_ntohs(tvb, offset);
+
+ if (iStringLength==0 && tvb_get_guint8(tvb, offset + 2)==RTMPT_AMF_END_OF_OBJECT)
+ {
+ iObjType = RTMPT_AMF_END_OF_OBJECT;
+ goto popamf;
+ }
+
+ iPropertyOffset = offset;
+ iPropertyLength = 2 + iStringLength;
+ iValueOffset += iPropertyLength;
+ }
+
+ iObjType = tvb_get_guint8(tvb, iValueOffset);
+ iValueOffset++;
+ iValueExtra = 1;
+
+ switch (iObjType) {
+ case RTMPT_AMF_NUMBER:
+ iValueLength = 8;
+ hfvalue = hf_rtmpt_amf_number;
+ sValue = ep_strdup_printf(" %." STRINGIFY(DBL_DIG) "g", tvb_get_ntohieee_double(tvb, iValueOffset));
+ break;
+ case RTMPT_AMF_BOOLEAN:
+ iValueLength = 1;
+ hfvalue = hf_rtmpt_amf_boolean;
+ sValue = tvb_get_guint8(tvb, iValueOffset) ? " true" : " false";
+ break;
+ case RTMPT_AMF_STRING:
+ iValueLength = tvb_get_ntohs(tvb, iValueOffset);
+ iValueOffset += 2;
+ iValueExtra = 3;
+ hfvalue = hf_rtmpt_amf_string;
+ sValue = ep_strdup_printf(" '%s'", tvb_get_ephemeral_string(tvb, iValueOffset, CLAMP(iValueLength, 0, ITEM_LABEL_LENGTH+1)));
+ break;
+ case RTMPT_AMF_OBJECT:
+ /* Uncounted list type, with end marker */
+ iValueLength = 0;
+ hfvalue = hf_rtmpt_amf_object;
+ iPush = 1;
+ break;
+ case RTMPT_AMF_NULL:
+ case RTMPT_AMF_UNDEFINED:
+ iValueLength = 0;
+ break;
+ case RTMPT_AMF_REFERENCE:
+ iValueLength = 2;
+ hfvalue = hf_rtmpt_amf_reference;
+ sValue = ep_strdup_printf(" %d", tvb_get_ntohs(tvb, iValueOffset));
+ break;
+ case RTMPT_AMF_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.
+ */
+ iValueLength = 0;
+ iValueOffset += 4;
+ iValueExtra = 5;
+ hfvalue = hf_rtmpt_amf_ecmaarray;
+ iPush = 1;
+ break;
+ case RTMPT_AMF_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 */
+ iValueLength = 4;
+ hfvalue = hf_rtmpt_amf_strictarray;
+ iPush = 1;
+ break;
+ case RTMPT_AMF_DATE:
+ iValueLength = 10;
+ hfvalue = hf_rtmpt_amf_date;
+ break;
+ case RTMPT_AMF_LONG_STRING:
+ case RTMPT_AMF_XML: /* same representation */
+ iValueLength = tvb_get_ntohl(tvb, iValueOffset);
+ iValueOffset += 4;
+ iValueExtra = 5;
+ hfvalue = (iObjType==RTMPT_AMF_XML) ? hf_rtmpt_amf_xml : hf_rtmpt_amf_longstring;
+ sValue = ep_strdup_printf(" '%s'", tvb_get_ephemeral_string(tvb, iValueOffset, CLAMP(iValueLength, 0, ITEM_LABEL_LENGTH+1)));
+ break;
+ case RTMPT_AMF_UNSUPPORTED:
+ iValueLength = 0;
+ break;
+ case RTMPT_AMF_INT64:
+ iValueLength = 8;
+ hfvalue = hf_rtmpt_amf_int64;
+ sValue = ep_strdup_printf(" %" G_GINT64_MODIFIER "d", tvb_get_ntoh64(tvb, iValueOffset));
+ break;
+ default:
+ /* If we can't determine the length, don't carry on */
+ iValueLength = tvb_length_remaining(tvb, iValueOffset);
+ sValue = "";
+ break;
+ }
+
+ offset += iPropertyLength + iValueExtra + iValueLength;
+ iObjectLength += iPropertyLength + iValueExtra + iValueLength;
+ pc++;
+
+ if (iPropertyLength>0) {
+ proto_tree *name_tree = NULL;
+ gchar *sProperty = tvb_get_ephemeral_string(tvb, iPropertyOffset+2, iPropertyLength-2);
+
+ ti = proto_tree_add_text(rtmpt_tree, tvb,
+ iPropertyOffset,
+ iPropertyLength+iValueExtra+iValueLength,
+ "Property '%s' %s%s",
+ sProperty, val_to_str(iObjType, rtmpt_type_vals, "Unknown"), sValue);
+ rtmpt_tree_prop = proto_item_add_subtree(ti, ett_rtmpt_property);
+
+ ti = proto_tree_add_text(rtmpt_tree_prop, tvb,
+ iPropertyOffset, iPropertyLength,
+ "Name: %s", sProperty);
+ name_tree = proto_item_add_subtree(ti, ett_rtmpt_string);
+
+ proto_tree_add_item(name_tree, hf_rtmpt_amf_stringlength, tvb, iPropertyOffset, 2, FALSE);
+ proto_tree_add_item(name_tree, hf_rtmpt_amf_string, tvb, iPropertyOffset+2, iPropertyLength-2, FALSE);
+ }
+
+ if (!iPush) {
+ proto_tree *val_tree = NULL;
+
+ ti = proto_tree_add_text(rtmpt_tree_prop, tvb,
+ iValueOffset-iValueExtra, iValueExtra+iValueLength,
+ "%s%s",
+ val_to_str(iObjType, rtmpt_type_vals, "Unknown"), sValue);
+ val_tree = proto_item_add_subtree(ti, ett_rtmpt_value);
+
+ proto_tree_add_item(val_tree, hf_rtmpt_amf_type, tvb, iValueOffset-iValueExtra, 1, FALSE);
+ if (iObjType==RTMPT_AMF_STRING) {
+ proto_tree_add_item(val_tree, hf_rtmpt_amf_stringlength, tvb, iValueOffset-iValueExtra+1, 2, FALSE);
+ } else if (iObjType==RTMPT_AMF_LONG_STRING || iObjType==RTMPT_AMF_XML) {
+ proto_tree_add_item(val_tree, hf_rtmpt_amf_longstringlength, tvb, iValueOffset-iValueExtra+1, 4, FALSE);
+ }
+ if (iValueLength>0 && hfvalue!=-1) {
+ ti = proto_tree_add_item(val_tree, hfvalue, tvb, iValueOffset, iValueLength, FALSE);
+ }
+ }
+
+ if (iPush) {
+ depth++;
+ ep_stack_push(amfols, GINT_TO_POINTER(iObjectLength));
+ iObjectLength = iValueExtra;
+ ep_stack_push(amfots, GINT_TO_POINTER(ot));
+ ot = iValueLength>0 ? tvb_get_ntohl(tvb, iValueOffset)+1 : 0;
+ ep_stack_push(amfpcs, GINT_TO_POINTER(pc));
+ pc = 0;
+ ep_stack_push(amftis, ti_object);
+ ti_object = proto_tree_add_item(rtmpt_tree_prop, hfvalue, tvb, iValueOffset+iValueLength, 1, FALSE);
+ ep_stack_push(amftrs, rtmpt_tree);
+ rtmpt_tree = proto_item_add_subtree(ti_object, ett_rtmpt_array);
+
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_type, tvb, iValueOffset-iValueExtra, 1, FALSE);
+ if (iValueExtra>1 || iValueLength>0) {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_arraylength, tvb, iValueOffset-iValueExtra+1, 4, FALSE);
+ }
+ }
+
+ if (!ot || --ot>0) continue;
+
+popamf:
+ /* reached end of amf container */
+
+ if (iObjType==RTMPT_AMF_END_OF_OBJECT) {
+ proto_tree_add_text(rtmpt_tree_prop, tvb, offset, 3, "End Of Object Marker");
+ iObjectLength += 3;
+ }
+
+ proto_item_set_len(ti_object, iObjectLength);
+ proto_item_append_text(ti_object, " (%d items)", pc);
+ depth--;
+ rtmpt_tree = ep_stack_pop(amftrs);
+ ti_object = ep_stack_pop(amftis);
+ ot = GPOINTER_TO_INT(ep_stack_pop(amfots));
+ pc = GPOINTER_TO_INT(ep_stack_pop(amfpcs));
+ iObjectLength += GPOINTER_TO_INT(ep_stack_pop(amfols));
+ if (iObjType==RTMPT_AMF_END_OF_OBJECT) {
+ offset += 3;
+ }
+
+ if (depth>0 && ot>0 && --ot==0) {
+ iObjType = 0;
+ goto popamf;
+ }
+ }
+}
- iObjType = tvb_get_guint8(tvb, offset + 0);
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_type, tvb, offset + 0, 1, FALSE);
- offset += 1;
+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((iCtl & 0xf0)>>4, rtmpt_audio_codecs, "Unknown codec"),
+ val_to_str((iCtl & 0x0c)>>2, rtmpt_audio_rates, "Unknown rate"),
+ val_to_str((iCtl & 0x02)>>1, rtmpt_audio_sizes, "Unknown sample size"),
+ val_to_str(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, FALSE);
+}
- switch (iObjType)
- {
- case RTMPT_TYPE_NUMBER:
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_number, tvb, offset + 0, 8, FALSE);
- offset += 8;
- break;
- case RTMPT_TYPE_BOOLEAN:
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_boolean, tvb, offset + 0, 1, FALSE);
- offset += 1;
- break;
- case RTMPT_TYPE_STRING:
- iStringLength = tvb_get_ntohs(tvb, offset + 0);
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_amf_string, tvb, offset + 2, iStringLength, FALSE);
- offset += 2 + iStringLength;
- break;
- case RTMPT_TYPE_OBJECT:
- ti_object = proto_tree_add_text(rtmpt_tree, tvb, offset, 1, RTMPT_TEXT_AMF_OBJECT);
- rtmpt_tree_object = proto_item_add_subtree(ti_object, ett_rtmpt_object);
- for (;;)
- {
- gint iPropertyLength = 0;
- proto_tree *rtmpt_tree_prop = NULL;
- if (tvb_length_remaining(tvb, offset) <= 0) break;
- iObjType = tvb_get_guint8(tvb, offset + 0);
- if (iObjType != 0x00) break;
- if (tvb_get_guint8(tvb, offset + 1) == 0 && tvb_get_guint8(tvb, offset + 2) == RTMPT_TYPE_END_OF_OBJECT)
- {
- /* End of object marker */
- offset += 2;
- break;
- }
- ti = proto_tree_add_text(rtmpt_tree_object, tvb, offset, 1, RTMPT_TEXT_AMF_PROPERTY);
- rtmpt_tree_prop = proto_item_add_subtree(ti, ett_rtmpt_property);
-
- /* Property name */
- iStringLength = tvb_get_guint8(tvb, offset + 1);
- proto_tree_add_item(rtmpt_tree_prop, hf_rtmpt_amf_string, tvb, offset + 2, iStringLength, FALSE);
- offset += 2 + iStringLength;
- iPropertyLength = 2 + iStringLength;
-
- /* Property value */
- iObjType = tvb_get_guint8(tvb, offset + 0);
- switch (iObjType)
- {
- case RTMPT_TYPE_NUMBER:
- proto_tree_add_item(rtmpt_tree_prop, hf_rtmpt_amf_number, tvb, offset + 1, 8, FALSE);
- offset += 9;
- iPropertyLength += 9;
- break;
- case RTMPT_TYPE_BOOLEAN:
- proto_tree_add_item(rtmpt_tree_prop, hf_rtmpt_amf_boolean, tvb, offset + 1, 1, FALSE);
- offset += 2;
- iPropertyLength += 2;
- break;
- case RTMPT_TYPE_STRING:
- iStringLength = tvb_get_ntohs(tvb, offset + 1);
- proto_tree_add_item(rtmpt_tree_prop, hf_rtmpt_amf_string, tvb, offset + 3, iStringLength, FALSE);
- offset += 3 + iStringLength;
- iPropertyLength += 3 + iStringLength;
- break;
- }
- proto_item_set_len(ti, iPropertyLength);
- iObjectLength += 1 + iPropertyLength;
- }
- proto_item_set_len(ti_object, iObjectLength);
- break;
+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((iCtl & 0xf0)>>4, rtmpt_video_types, "Unknown frame type"),
+ val_to_str(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, FALSE);
+}
- }
- }
+static void
+dissect_rtmpt_body_aggregate(tvbuff_t *tvb, gint offset, proto_tree *rtmpt_tree)
+{
+ proto_item *tag_item = NULL;
+ proto_tree *tag_tree = NULL;
+
+ proto_item *data_item = NULL;
+ proto_tree *data_tree = NULL;
+
+ while (tvb_length_remaining(tvb, offset) > 0) {
+ guint8 iTagType = 0;
+ guint iDataSize = 0;
+ guint iTimestamp = 0;
+ guint8 iETS = 0;
+ guint iStreamID = 0;
+
+ iTagType = tvb_get_guint8(tvb, offset + 0);
+ iDataSize = tvb_get_ntoh24(tvb, offset + 1);
+ iTimestamp = tvb_get_ntoh24(tvb, offset + 4);
+ iETS = tvb_get_guint8(tvb, offset + 7);
+ iStreamID = tvb_get_ntoh24(tvb, offset + 8);
+
+ tag_item = proto_tree_add_text(rtmpt_tree, tvb, offset, 11+iDataSize+4, "%s", val_to_str(iTagType, rtmpt_tag_vals, "Unknown Tag"));
+ tag_tree = proto_item_add_subtree(tag_item, ett_rtmpt_tag);
+ proto_tree_add_item(tag_tree, hf_rtmpt_tag_type, tvb, offset+0, 1, FALSE);
+ proto_tree_add_item(tag_tree, hf_rtmpt_tag_datasize, tvb, offset+1, 3, FALSE);
+ proto_tree_add_item(tag_tree, hf_rtmpt_tag_timestamp, tvb, offset+4, 3, FALSE);
+ proto_tree_add_item(tag_tree, hf_rtmpt_tag_ets, tvb, offset+7, 1, FALSE);
+ proto_tree_add_item(tag_tree, hf_rtmpt_tag_streamid, tvb, offset+8, 3, FALSE);
+
+ data_item = proto_tree_add_text(tag_tree, tvb, offset+11, iDataSize, "Data");
+ data_tree = proto_item_add_subtree(data_item, ett_rtmpt_tag_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, FALSE);
+ offset += 11 + iDataSize + 4;
+ }
}
+/* The main dissector for unchunked packets */
+
static void
-dissect_rtmpt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+dissect_rtmpt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, rtmpt_conv_t *rconv, int cdir, rtmpt_packet_t *tp)
{
proto_tree *rtmpt_tree = NULL;
proto_tree *rtmptroot_tree = NULL;
proto_item *ti = NULL;
- gint offset = 0;
+ gint offset = 0;
+ static gint iPreviousFrameNumber = -1;
- struct tcpinfo* tcpinfo = pinfo->private_data;
+ gchar *sDesc = NULL;
+ gint deschasopcode = FALSE;
+ gboolean haveETS = FALSE;
+ guint32 iBodyOffset = 0;
+ guint32 iBodyRemain = 0;
- guint8 iCommand = -1;
- guint32 iLength = 1;
- guint16 iHeaderType = 4;
- guint16 iHeaderLength;
- guint8 iID;
- guint rtmp_index;
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");
- conversation_t * current_conversation;
- rtmpt_conversation_data_t * conversation_data;
- rtmpt_packet_data_t * packet_data;
+ RTMPT_DEBUG("Dissect: frame=%d prev=%d visited=%d len=%d col=%d tree=%p\n", pinfo->fd->num, iPreviousFrameNumber, pinfo->fd->flags.visited, tvb_length_remaining(tvb, offset), check_col(pinfo->cinfo, COL_INFO), tree);
- rtmpt_chunk_data_t *current_chunk_data = NULL;
- rtmpt_chunk_data_t *initial_chunk_data = NULL;
+ /* This is a trick to know whether this is the first PDU in this packet or not */
+ if (iPreviousFrameNumber != (gint) PINFO_FD_NUM(pinfo))
+ col_clear(pinfo->cinfo, COL_INFO);
+ else
+ col_append_str(pinfo->cinfo, COL_INFO, " | ");
+ iPreviousFrameNumber = pinfo->fd->num;
+
+ if (tvb_length_remaining(tvb, offset) < 1) return;
+
+ if (tp->id<=RTMPT_ID_MAX) {
+ if (tp->fmt<3 && tvb_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_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) {
+ se_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);
+ se_tree_insert32(rconv->txids[cdir], tp->txid, GINT_TO_POINTER(pinfo->fd->num));
+ }
+ }
+ }
+ }
+
+ if ((check_col(pinfo->cinfo, COL_INFO) || tree) && tp->id<=RTMPT_ID_MAX)
+ {
+ sDesc = rtmpt_get_packet_desc(tvb, iBodyOffset, iBodyRemain, rconv, cdir, tp, &deschasopcode);
+ }
+
+ if (check_col(pinfo->cinfo, COL_INFO))
+ {
+ if (tp->id>RTMPT_ID_MAX) {
+ col_append_fstr(pinfo->cinfo, COL_INFO, "%s", val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
+ } else if (sDesc) {
+ col_append_fstr(pinfo->cinfo, COL_INFO, "%s", sDesc);
+ } else {
+ col_append_fstr(pinfo->cinfo, COL_INFO, "%s", val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"));
+ }
+ }
+
+ if (tree)
+ {
+ ti = proto_tree_add_item(tree, proto_rtmpt, tvb, offset, -1, FALSE);
+
+ 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);
+ ti = proto_tree_add_text(rtmptroot_tree, tvb, offset, -1, "%s", val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
+ rtmpt_tree = proto_item_add_subtree(ti, ett_rtmpt_handshake);
+
+ if (tp->id == RTMPT_TYPE_HANDSHAKE_1)
+ {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c0, tvb, 0, 1, FALSE);
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c1, tvb, 1, 1536, FALSE);
+ }
+ else if (tp->id == RTMPT_TYPE_HANDSHAKE_2)
+ {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s0, tvb, 0, 1, FALSE);
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s1, tvb, 1, 1536, FALSE);
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s2, tvb, 1537, 1536, FALSE);
+ }
+ else if (tp->id == RTMPT_TYPE_HANDSHAKE_3)
+ {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c2, tvb, 0, 1536, FALSE);
+ }
+
+ 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 */
+ ti = proto_tree_add_text(rtmptroot_tree, tvb, offset, tp->bhlen+tp->mhlen, RTMPT_TEXT_RTMP_HEADER);
+/* proto_item_append_text(ti, " (%s)", val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); */
+ rtmpt_tree = proto_item_add_subtree(ti, ett_rtmpt_header);
+
+ if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_format, tvb, offset + 0, 1, FALSE);
+ if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_csid, tvb, offset + 0, tp->bhlen, FALSE);
+ if (tp->fmt <= 2) {
+ if (tp->fmt>0) {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp_delta, tvb, offset + tp->bhlen, 3, FALSE);
+ } else {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 3, FALSE);
+ }
+ if (haveETS) {
+ proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_ets, tvb, offset + tp->bhlen + tp->mhlen - 4, 4, FALSE);
+ }
+ }
+ if ((tp->fmt>0 && !haveETS) || tp->fmt==3) {
+ proto_tree_add_text(rtmpt_tree, tvb, offset + tp->bhlen, 0, "Timestamp: %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, FALSE);
+ if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_typeid, tvb, offset + tp->bhlen + 6, 1, FALSE);
+ if (tp->fmt <= 0) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_streamid, tvb, offset + tp->bhlen + 7, 4, TRUE);
+
+ /* Dissect body */
+ if (tp->len==0) return;
+ offset = iBodyOffset;
+
+ ti = proto_tree_add_text(rtmptroot_tree, tvb, offset, -1, RTMPT_TEXT_RTMP_BODY);
+ rtmpt_tree = proto_item_add_subtree(ti, ett_rtmpt_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;
+ }
+ }
+}
- tvbuff_t* amf_tvb;
+/* Unchunk a data stream into individual RTMP packets */
- current_conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+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;
- if (NULL != current_conversation)
- {
- conversation_data = (rtmpt_conversation_data_t*)conversation_get_proto_data(current_conversation, proto_rtmpt);
- if (NULL == conversation_data)
- {
- conversation_data = se_alloc0(sizeof(rtmpt_conversation_data_t));
- conversation_add_proto_data(current_conversation, proto_rtmpt, conversation_data);
- conversation_data->current_chunks = g_hash_table_new(g_direct_hash, g_direct_equal);
- conversation_data->previous_frame_number = -1;
- conversation_data->current_chunk_size = RTMPT_DEFAULT_CHUNK_SIZE;
- conversation_data->is_rtmpe = 0;
- }
+ guint8 header_type;
+ int basic_hlen;
+ int message_hlen;
- packet_data = p_get_proto_data(pinfo->fd, proto_rtmpt);
- if (NULL == packet_data)
- {
- packet_data = se_alloc0(sizeof(rtmpt_packet_data_t));
- p_add_proto_data(pinfo->fd, proto_rtmpt, packet_data);
- packet_data->initial_chunks = g_hash_table_new(g_direct_hash, g_direct_equal);
- packet_data->initial_chunk_size = conversation_data->current_chunk_size;
- }
+ guint32 id;
+ guint32 ts = 0;
+ guint32 tsd = 0;
+ int body_len;
+ guint8 cmd;
+ guint32 src;
+ int chunk_size;
+ rtmpt_frag_t *tf;
+ rtmpt_id_t *ti;
+ rtmpt_packet_t *tp;
+ tvbuff_t *pktbuf;
- if (conversation_data->is_rtmpe == 1)
- {
- col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMPE");
- return;
- }
- else
- {
- col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");
- }
+ remain = tvb_length(tvb);
+ if (!remain) return;
- if (conversation_data->previous_frame_number != (guint) pinfo->fd->num)
- {
- conversation_data->current_chunk_size = packet_data->initial_chunk_size;
+ 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 */
+ ep_stack_t packets;
+
+ /* List all RTMP packets terminating in this TCP segment, from end to beginning */
+
+ packets = ep_stack_new();
+ ep_stack_push(packets, 0);
+
+ tp = se_tree_lookup32_le(rconv->packets[cdir], seq+remain-1);
+ while (tp && tp->lastseq>=seq) {
+ ep_stack_push(packets, tp);
+ tp = se_tree_lookup32_le(rconv->packets[cdir], tp->lastseq-1);
}
- col_set_writable(pinfo->cinfo, TRUE);
- col_clear(pinfo->cinfo, COL_INFO);
+ /* Dissect the generated list in reverse order (beginning to end) */
- conversation_data->previous_frame_number = pinfo->fd->num;
- if (tvb_length_remaining(tvb, offset) >= 1)
- {
- if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_1 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1)
- {
- iCommand = RTMPT_TYPE_HANDSHAKE_1;
+ while ((tp=ep_stack_pop(packets))!=NULL) {
+ if (tp->resident) {
+ pktbuf = tvb_new_real_data(tp->data.p, tp->have, tp->have);
+ add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
+ } else {
+ pktbuf = tvb_new_subset(tvb, tp->data.offset, tp->have, tp->have);
}
- else if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1) iCommand = RTMPT_TYPE_HANDSHAKE_2;
- else if (tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_2
- && tvb_length(tvb) == RTMPT_HANDSHAKE_LENGTH_3) iCommand = RTMPT_TYPE_HANDSHAKE_3;
- else
- {
- iID = tvb_get_guint8(tvb, offset + 0);
- iHeaderType = iID >> 6;
- rtmp_index = iID & 0x3F;
-
- current_chunk_data = g_hash_table_lookup(conversation_data->current_chunks, GUINT_TO_POINTER(rtmp_index));
- initial_chunk_data = g_hash_table_lookup(packet_data->initial_chunks, GUINT_TO_POINTER(rtmp_index));
-
- if (iHeaderType <= 2) iLength = tvb_get_ntoh24(tvb, offset + 4);
- if (iHeaderType <= 1)
- {
- iCommand = tvb_get_guint8(tvb, offset + 7);
- if (NULL == current_chunk_data)
- {
- current_chunk_data = se_alloc0(sizeof(rtmpt_chunk_data_t));
- g_hash_table_insert(conversation_data->current_chunks, GUINT_TO_POINTER(rtmp_index), current_chunk_data);
- }
+ dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
+ }
+
+ return;
+ }
- current_chunk_data->data_type = iCommand;
- current_chunk_data->last_length = iLength;
- current_chunk_data->frame_modified = pinfo->fd->num;
+ while (remain>0) {
+ tf = NULL;
+ ti = NULL;
+ tp = NULL;
+
+ /* Check for outstanding fragmented headers/chunks first */
+
+ if (offset==0) {
+ tf = se_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 = se_tree_lookup32(rconv->ids[cdir], tf->saved.id);
+ if (ti) tp = se_tree_lookup32_le(ti->packets, seq+offset-1);
+ if (tp && tp->chunkwant) {
+ goto unchunk;
+ }
+ tf = NULL;
+ ti = NULL;
+ tp = NULL;
}
- else
- {
- /* must get the command type from the previous entries in the hash table */
- /* try to use the current_chunk_data unless it is from a different frame */
- if (NULL != current_chunk_data && NULL != initial_chunk_data)
- {
- /* we have precedent data (we should)*/
- if (current_chunk_data->frame_modified != pinfo->fd->num)
- {
- iCommand = initial_chunk_data->data_type;
- iLength = initial_chunk_data->length_remaining;
- current_chunk_data->frame_modified = pinfo->fd->num;
- current_chunk_data->data_type = iCommand;
- current_chunk_data->last_length = iLength;
- current_chunk_data->dechunk_buffer = initial_chunk_data->dechunk_buffer;
- }
- else
- {
- iCommand = current_chunk_data->data_type;
- iLength = current_chunk_data->length_remaining;
- }
- if (iLength > conversation_data->current_chunk_size)
- {
- iLength = conversation_data->current_chunk_size;
+ 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);
+ message_hlen = rtmpt_message_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;
+ }
}
}
+ }
- iHeaderLength = rtmpt_header_length_from_type(iHeaderType);
+ 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 = se_alloc(sizeof(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;
+ se_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;
- if (check_col(pinfo->cinfo, COL_INFO))
- {
- col_append_sep_fstr(pinfo->cinfo, COL_INFO, " | ", "%s", val_to_str(iCommand, rtmpt_opcode_vals, "Unknown (0x%01x)"));
- col_set_fence(pinfo->cinfo, COL_INFO);
+ id = id & 0x3f;
+ if (id==0) id = tf->saved.d[1] + 64;
+ else if (id==1) id = pletohs(tf->saved.d+1) + 64;
+ }
+
+ /* Calculate header values, defaulting from previous packets with same id */
+
+ if (id<=RTMPT_ID_MAX) ti = se_tree_lookup32(rconv->ids[cdir], id);
+ if (ti) tp = se_tree_lookup32_le(ti->packets, seq+offset-1);
+
+ if (header_type==0) src = tf ? pntohl(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(se_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>RTMPT_MAX_PACKET_SIZE) {
+ return;
}
+ }
- if (tree)
- {
- ti = proto_tree_add_item(tree, proto_rtmpt, tvb, offset, -1, FALSE);
- proto_item_append_text(ti, " (%s)", val_to_str(iCommand, rtmpt_opcode_vals, "Unknown (0x%01x)"));
- rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt);
-
- ti = proto_tree_add_text(rtmptroot_tree, tvb, offset, iHeaderLength, RTMPT_TEXT_RTMP_HEADER);
- proto_item_append_text(ti, " (%s)", val_to_str(iCommand, rtmpt_opcode_vals, "Unknown (0x%01x)"));
- rtmpt_tree = proto_item_add_subtree(ti, ett_rtmpt_header);
-
- if (iHeaderType <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_objid, tvb, offset + 0, 1, FALSE);
- if (iHeaderType <= 2) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + 1, 3, FALSE);
- if (iHeaderType <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_body_size, tvb, offset + 4, 3, FALSE);
- if (iHeaderType <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_function, tvb, offset + 7, 1, FALSE);
- if (iHeaderType <= 0) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_source, tvb, offset + 8, 4, TRUE);
-
- if (iCommand == RTMPT_TYPE_HANDSHAKE_1)
- {
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_data, tvb, 1, 1536, FALSE);
- }
- else if (iCommand == RTMPT_TYPE_HANDSHAKE_2)
- {
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_data, tvb, 1, 1536, FALSE);
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_data, tvb, 1537, 1536, FALSE);
+ 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 = se_alloc(sizeof(rtmpt_id_t));
+ ti->packets = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_packets");
+ ti->ts = 0;
+ ti->tsd = 0;
+ se_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 ? pntohl(tf->saved.d+basic_hlen+11) : tvb_get_ntohl(tvb, offset+basic_hlen+11);
}
- else if (iCommand == RTMPT_TYPE_HANDSHAKE_3)
- {
- proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_data, tvb, 0, -1, FALSE);
+ 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 ? pntohl(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 if (iCommand == RTMPT_TYPE_CHUNK_SIZE)
- {
- conversation_data->current_chunk_size = tvb_get_ntohl (tvb, offset + iHeaderLength);
+ } else {
+ ts = ti->ts + ti->tsd;
+ tsd = ti->tsd;
+ }
+
+ /* create a new packet structure */
+ tp = se_alloc(sizeof(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 */
+ se_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;
+
+ se_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
+
+ pktbuf = tvb_new_subset(tvb, tp->data.offset, tp->have, 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 = se_alloc(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;
}
- offset = iHeaderLength;
- if (tvb_length_remaining(tvb, offset))
- {
- ti = proto_tree_add_text(rtmptroot_tree, tvb, offset, -1, RTMPT_TEXT_RTMP_BODY);
+ tp->lastseq = seq+offset-1;
+ tp->have = basic_hlen + message_hlen;
+
+ if (tp->have==tp->want) {
+ se_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
+
+ pktbuf = tvb_new_real_data(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);
- if (iCommand == RTMPT_TYPE_INVOKE || iCommand == RTMPT_TYPE_NOTIFY)
- {
- guint iChunkSize = tvb_length_remaining(tvb, iHeaderLength);
- /* we have data which will be AMF */
- /* we should add it to a new tvb */
- if (NULL != current_chunk_data)
- {
- if (NULL == current_chunk_data->dechunk_buffer)
- {
- /* we have to create a new tvbuffer */
- current_chunk_data->dechunk_buffer = tvb_new_composite();
- }
- if (!(current_chunk_data->dechunk_buffer->initialized))
- {
- /* add the existing data to the new buffer */
- tvb_composite_append(current_chunk_data->dechunk_buffer,
- tvb_new_real_data(tvb_memdup(tvb, iHeaderLength, iChunkSize), iChunkSize, iChunkSize));
+ tp->chunkwant = chunk_size;
+ if (tp->chunkwant>tp->want-tp->have) tp->chunkwant = tp->want - tp->have;
- if (current_chunk_data->length_remaining <= 0)
- {
- guint amf_length;
- guint8* amf_data;
+ offset += basic_hlen + message_hlen;
+ remain -= basic_hlen + message_hlen;
+ }
- tvb_composite_finalize(current_chunk_data->dechunk_buffer);
+ tf = NULL;
- amf_length = tvb_length(current_chunk_data->dechunk_buffer);
+ /* 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);
- if (amf_length == 0)
- {
- return;
- }
+ 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;
- amf_data = tvb_memdup(current_chunk_data->dechunk_buffer, 0, amf_length);
+ offset += want;
+ remain -= want;
- amf_tvb = tvb_new_real_data(amf_data, tvb_length_remaining(current_chunk_data->dechunk_buffer, 0), tvb_length_remaining(current_chunk_data->dechunk_buffer, 0));
+ if (tp->chunkhave==tp->chunkwant) {
+ /* Chunk is complete - wait for next header */
+ tp->chunkhave = 0;
+ tp->chunkwant = 0;
+ }
- add_new_data_source(pinfo, amf_tvb, "Dechunked AMF data");
- ti = proto_tree_add_item(tree, proto_rtmpt, amf_tvb, 0, -1, FALSE);
- rtmpt_tree = proto_item_add_subtree(ti, ett_rtmpt_body);
- proto_tree_set_appendix(rtmpt_tree, amf_tvb, 0, tvb_length_remaining(amf_tvb, 0));
- proto_item_append_text(rtmpt_tree, " (%s)", "AMF Data");
- dissect_rtmpt_amf(amf_tvb, rtmpt_tree);
- current_chunk_data->dechunk_buffer = NULL;
- }
- }
- }
- }
- }
+ if (tp->have==tp->want) {
+ /* Whole packet is complete */
+ se_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
+
+ pktbuf = tvb_new_real_data(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 = se_alloc(sizeof(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);
+ se_tree_insert32(rconv->frags[cdir], seq+offset-want-1, tf2);
}
}
}
+static rtmpt_conv_t*
+rtmpt_init_rconv(conversation_t *conv)
+{
+ rtmpt_conv_t *rconv = se_alloc(sizeof(rtmpt_conv_t));
+ conversation_add_proto_data(conv, proto_rtmpt, rconv);
+
+ rconv->seqs[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_seqs0");
+ rconv->seqs[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_seqs1");
+ rconv->frags[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_frags0");
+ rconv->frags[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_frags1");
+ rconv->ids[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_ids0");
+ rconv->ids[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_ids1");
+ rconv->packets[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_packets0");
+ rconv->packets[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_packets1");
+ rconv->chunksize[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_chunksize0");
+ rconv->chunksize[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_chunksize1");
+ rconv->txids[0] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_txids0");
+ rconv->txids[1] = se_tree_create_non_persistent(EMEM_TREE_TYPE_RED_BLACK, "rtmpt_txids1");
+
+ return rconv;
+}
-static guint
-get_rtmpt_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset)
+static void
+dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
- guint returned = 0;
- struct tcpinfo *tcpinfo = pinfo->private_data;
- static guint8 handshake2recvd = 0;
- static guint8 handshake3recvd = 0;
+ conversation_t *conv;
+ rtmpt_conv_t *rconv;
+ int cdir;
+ struct tcpinfo *tcpinfo;
+
+ conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ if (!conv) {
+ conv = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ }
+
+ rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
+ if (!rconv) {
+ rconv = rtmpt_init_rconv(conv);
+ }
- conversation_t * current_conversation;
- rtmpt_conversation_data_t * conversation_data;
- rtmpt_packet_data_t * packet_data;
- guint32 remaining_length = 0;
+ cdir = (ADDRESSES_EQUAL(&conv->key_ptr->addr1, &pinfo->src) &&
+ ADDRESSES_EQUAL(&conv->key_ptr->addr2, &pinfo->dst) &&
+ conv->key_ptr->port1==pinfo->srcport &&
+ conv->key_ptr->port2==pinfo->destport) ? 0 : 1;
- current_conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ tcpinfo = pinfo->private_data;
+ dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, tcpinfo->seq, tcpinfo->lastackseq);
+}
- remaining_length = tvb_length_remaining(tvb, offset + returned);
+static void
+dissect_rtmpt_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ conversation_t *conv;
+ rtmpt_conv_t *rconv;
+ int cdir;
+ guint32 seq;
+ guint32 lastackseq;
+ guint32 offset;
+ gint remain;
+
+ offset = 0;
+ remain = tvb_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_port;
+
+ if (cdir) {
+ conv = find_conversation(pinfo->fd->num, &pinfo->dst, &pinfo->src, pinfo->ptype, 0, pinfo->srcport, 0);
+ if (!conv) {
+ RTMPT_DEBUG("RTMPT new conversation\n");
+ conv = conversation_new(pinfo->fd->num, &pinfo->dst, &pinfo->src, pinfo->ptype, 0, pinfo->srcport, 0);
+ }
+ } else {
+ conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, 0, pinfo->destport, 0);
+ if (!conv) {
+ RTMPT_DEBUG("RTMPT new conversation\n");
+ conv = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, 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(se_tree_lookup32_le(rconv->seqs[cdir ^ 1], pinfo->fd->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(se_tree_lookup32(rconv->seqs[cdir], pinfo->fd->num));
+
+ if (seq==0) {
+ seq = GPOINTER_TO_INT(se_tree_lookup32_le(rconv->seqs[cdir], pinfo->fd->num));
+ seq += remain;
+ se_tree_insert32(rconv->seqs[cdir], pinfo->fd->num, GINT_TO_POINTER(seq));
+ }
+
+ seq -= remain-1;
+
+ RTMPT_DEBUG("RTMPT f=%d cdir=%d seq=%d lastackseq=%d len=%d\n", pinfo->fd->num, cdir, seq, lastackseq, remain);
+
+ if (remain<1) return;
+
+ if (offset>0) {
+ tvbuff_t *tvbrtmp = tvb_new_subset(tvb, offset, remain, remain);
+ dissect_rtmpt_common(tvbrtmp, pinfo, tree, rconv, cdir, seq, lastackseq);
+ } else {
+ dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, seq, lastackseq);
+ }
+}
- if (NULL != current_conversation)
+#if 0
+static gboolean
+dissect_rtmpt_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ conversation_t * conversation;
+ if (tvb_length(tvb) >= 12)
{
- conversation_data = (rtmpt_conversation_data_t*)conversation_get_proto_data(current_conversation, proto_rtmpt);
- if (NULL == conversation_data)
+ /* To avoid a too high rate of false positive, this heurisitics 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 = pinfo->private_data;
+ if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1 && tvb_get_guint8(tvb, 0) == RTMPT_MAGIC)
{
- conversation_data = se_alloc0(sizeof(rtmpt_conversation_data_t));
- conversation_add_proto_data(current_conversation, proto_rtmpt, conversation_data);
- conversation_data->current_chunks = g_hash_table_new(g_direct_hash, g_direct_equal);
- conversation_data->previous_frame_number = -1;
- conversation_data->current_chunk_size = RTMPT_DEFAULT_CHUNK_SIZE;
- conversation_data->is_rtmpe = 0;
- }
+ /* Register this dissector for this conversation */
+ conversation = NULL;
+ conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ if (conversation == NULL)
+ {
+ conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ }
+ conversation_set_dissector(conversation, rtmpt_tcp_handle);
- if (conversation_data->is_rtmpe == 1)
- {
- return remaining_length;
+ /* Dissect the packet */
+ dissect_rtmpt_tcp(tvb, pinfo, tree);
+ return TRUE;
}
+ }
+ return FALSE;
+}
+#endif
- packet_data = p_get_proto_data(pinfo->fd, proto_rtmpt);
- if (NULL == packet_data)
- {
- packet_data = se_alloc0(sizeof(rtmpt_packet_data_t));
- p_add_proto_data(pinfo->fd, proto_rtmpt, packet_data);
- packet_data->initial_chunks = g_hash_table_new(g_direct_hash, g_direct_equal);
- packet_data->initial_chunk_size = conversation_data->current_chunk_size;
- }
+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 }},
- if (conversation_data->previous_frame_number != (guint) pinfo->fd->num)
- {
- conversation_data->current_chunk_size = packet_data->initial_chunk_size;
- }
+ { &hf_rtmpt_handshake_s0,
+ { "Protocol version", "rtmpt.handshake.s0", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Handshake S0", HFILL }},
- if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_1 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1)
- {
- returned = RTMPT_HANDSHAKE_LENGTH_1;
- handshake2recvd = 0;
- if (tvb_get_guint8(tvb, offset) == 6)
- {
- conversation_data->is_rtmpe = 1;
- }
- }
- else if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1)
- {
- returned = RTMPT_HANDSHAKE_LENGTH_2;
- handshake2recvd = 1;
- handshake3recvd = 0;
- }
- else if (tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_2 && handshake2recvd && !handshake3recvd)
- {
- returned = RTMPT_HANDSHAKE_LENGTH_3;
- handshake2recvd = 0;
- handshake3recvd = 1;
- }
- else
- {
- guint32 segment_length = 0;
+ { &hf_rtmpt_handshake_c1,
+ { "Handshake data", "rtmpt.handshake.c1", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Handshake C1", HFILL }},
- remaining_length = tvb_length_remaining(tvb, offset + returned);
+ { &hf_rtmpt_handshake_s1,
+ { "Handshake data", "rtmpt.handshake.s1", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Handshake S1", HFILL }},
- if (remaining_length > 0)
- {
- guint16 iHeaderType;
- guint iHeaderLength;
- guint16 iID;
- guint rtmp_index;
-
- rtmpt_chunk_data_t *current_chunk_data = NULL;
- rtmpt_chunk_data_t *initial_chunk_data = NULL;
-
- iID = tvb_get_guint8(tvb, offset + returned);
- iHeaderType = iID >> 6;
- rtmp_index = iID & 0x3F;
-
- initial_chunk_data = g_hash_table_lookup(packet_data->initial_chunks, GUINT_TO_POINTER(rtmp_index));
- current_chunk_data = g_hash_table_lookup(conversation_data->current_chunks, GUINT_TO_POINTER(rtmp_index));
-
- if (iHeaderType <= 1)
- {
- if (remaining_length >=8)
- {
- if (NULL == current_chunk_data)
- {
- current_chunk_data = se_alloc0(sizeof(rtmpt_chunk_data_t));
- g_hash_table_insert(conversation_data->current_chunks, GUINT_TO_POINTER(rtmp_index), current_chunk_data);
- }
- else if (NULL == initial_chunk_data)
- {
- initial_chunk_data = se_alloc0(sizeof(rtmpt_chunk_data_t));
- g_hash_table_insert(packet_data->initial_chunks, GUINT_TO_POINTER(rtmp_index), initial_chunk_data);
- initial_chunk_data->amf_num = current_chunk_data->amf_num;
- initial_chunk_data->length_remaining = current_chunk_data->length_remaining;
- initial_chunk_data->last_length = current_chunk_data->last_length;
- initial_chunk_data->data_type = current_chunk_data->data_type;
- initial_chunk_data->dechunk_buffer = current_chunk_data->dechunk_buffer;
- }
+ { &hf_rtmpt_handshake_c2,
+ { "Handshake data", "rtmpt.handshake.c2", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Handshake C2", HFILL }},
- segment_length = tvb_get_ntoh24(tvb, offset + 4);
- current_chunk_data->last_length = segment_length;
+ { &hf_rtmpt_handshake_s2,
+ { "Handshake data", "rtmpt.handshake.s2", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Handshake S2", HFILL }},
- if (segment_length > conversation_data->current_chunk_size)
- {
- /* there will be additional headers of length 1 byte in the chunks */
- current_chunk_data->length_remaining = segment_length - conversation_data->current_chunk_size;
- segment_length = conversation_data->current_chunk_size;
- }
- else
- {
- current_chunk_data->length_remaining = 0;
- }
+/* RTMP chunk/packet header */
+ { &hf_rtmpt_header_format,
+ { "Format", "rtmpt.header.format", FT_UINT8, BASE_DEC, NULL, 0xC0, "RTMPT Basic Header format", HFILL }},
- if (remaining_length>=8)
- {
- /* we have the type as well */
- current_chunk_data->data_type = tvb_get_guint8(tvb, offset + returned + 7);
- }
- }
- else
- {
- segment_length = conversation_data->current_chunk_size;
- return segment_length;
- }
- }
- else
- {
- /* length info not given in header */
- /* check if there is a packet with the same amf number */
- if (NULL != current_chunk_data)
- {
- if (NULL == initial_chunk_data)
- {
- initial_chunk_data = se_alloc0(sizeof(rtmpt_chunk_data_t));
- g_hash_table_insert(packet_data->initial_chunks, GUINT_TO_POINTER(rtmp_index), initial_chunk_data);
- initial_chunk_data->amf_num = current_chunk_data->amf_num;
- initial_chunk_data->length_remaining = current_chunk_data->length_remaining;
- initial_chunk_data->last_length = current_chunk_data->last_length;
- initial_chunk_data->data_type = current_chunk_data->data_type;
- initial_chunk_data->dechunk_buffer = current_chunk_data->dechunk_buffer;
+ { &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 }},
- if (0 < current_chunk_data->length_remaining)
- {
- segment_length = current_chunk_data->length_remaining;
- }
- else
- {
- segment_length = current_chunk_data->last_length;
- }
+ { &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 }},
- if (segment_length > conversation_data->current_chunk_size)
- {
- segment_length = conversation_data->current_chunk_size;
- if (segment_length <= remaining_length)
- {
- current_chunk_data->length_remaining -= segment_length;
- }
- }
- else
- {
- if (segment_length <= remaining_length)
- {
- current_chunk_data->length_remaining = 0;
- }
- }
- }
- else
- {
- return tvb_length_remaining(tvb, offset);
- }
+ { &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 }},
- iHeaderLength = rtmpt_header_length_from_type(iHeaderType);
- segment_length += iHeaderLength;
+/* Stream Control Messages */
- returned = segment_length;
- }
- }
- }
- return returned;
-}
+ { &hf_rtmpt_scm_chunksize,
+ { "Chunk size", "rtmpt.scm.chunksize", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT SCM chunk size", HFILL }},
-static void
-dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
-{
-#if 0
- conversation_t * conversation;
+ { &hf_rtmpt_scm_csid,
+ { "Chunk stream ID", "rtmpt.scm.csid", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT SCM chunk stream ID", HFILL }},
- conversation = find_or_create_conversation(pinfo);
-#endif
+ { &hf_rtmpt_scm_seq,
+ { "Sequence number", "rtmpt.scm.seq", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT SCM acknowledgement sequence number", HFILL }},
- tcp_dissect_pdus(tvb, pinfo, tree, 1, 1, get_rtmpt_pdu_len, dissect_rtmpt);
-}
+ { &hf_rtmpt_scm_was,
+ { "Window acknowledgement size", "rtmpt.scm.seq", 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 }},
-void
-proto_register_rtmpt(void)
-{
- static hf_register_info hf[] = {
- { &hf_rtmpt_header_objid,
- { "ObjectID", "rtmpt.header.objid", FT_UINT8, BASE_DEC, NULL, 0x3F, "RTMPT Header object ID", 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 }},
- { &hf_rtmpt_header_timestamp,
- { "Timestamp", "rtmpt.header.timestamp", FT_UINT24, BASE_DEC, NULL, 0x0, "RTMPT Header timestamp", HFILL }},
+/* AMF basic types */
+ { &hf_rtmpt_amf_type,
+ { "AMF type", "rtmpt.amf.type", FT_UINT8, BASE_DEC, VALS(rtmpt_type_vals), 0x0, "RTMPT AMF type", HFILL }},
- { &hf_rtmpt_header_body_size,
- { "Body size", "rtmpt.header.bodysize", FT_UINT24, BASE_DEC, NULL, 0x0, "RTMPT Header body size", HFILL }},
+ { &hf_rtmpt_amf_number,
+ { "Number", "rtmpt.amf.number", FT_DOUBLE, BASE_NONE, NULL, 0x0, "RTMPT AMF number", HFILL }},
- { &hf_rtmpt_header_function,
- { "Function call", "rtmpt.header.function", FT_UINT8, BASE_HEX, VALS(rtmpt_opcode_vals), 0x0, "RTMPT Header function call", HFILL }},
+ { &hf_rtmpt_amf_boolean,
+ { "Boolean", "rtmpt.amf.boolean", FT_BOOLEAN, BASE_DEC, NULL, 0x0, "RTMPT AMF boolean", HFILL }},
- { &hf_rtmpt_header_source,
- { "Caller source", "rtmpt.header.caller", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT Header caller source", HFILL }},
+ { &hf_rtmpt_amf_stringlength,
+ { "String length", "rtmpt.amf.longstringlength", FT_UINT16, BASE_DEC, NULL, 0x0, "RTMPT AMF string length", HFILL }},
- { &hf_rtmpt_handshake_data,
- { "Handshake data", "rtmpt.header.handshake", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT Header handshake data", HFILL }},
+ { &hf_rtmpt_amf_string,
+ { "String", "rtmpt.amf.string", FT_STRINGZ, BASE_NONE, NULL, 0x0, "RTMPT AMF string", HFILL }},
- { &hf_rtmpt_amf_type,
- { "AMF type", "rtmpt.amf.type", FT_UINT8, BASE_DEC, VALS(rtmpt_type_vals), 0x0, "RTMPT AMF type", HFILL }},
+ { &hf_rtmpt_amf_reference,
+ { "Reference", "rtmpt.amf.reference", FT_UINT16, BASE_DEC, NULL, 0x0, "RTMPT AMF object reference", HFILL }},
+
+ { &hf_rtmpt_amf_date,
+ { "Date", "rtmpt.amf.date", FT_BYTES, BASE_NONE, NULL, 0x0, "RTMPT AMF date", HFILL }},
+
+ { &hf_rtmpt_amf_longstringlength,
+ { "String length", "rtmpt.amf.longstringlength", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT AMF long string length", HFILL }},
+
+ { &hf_rtmpt_amf_longstring,
+ { "Long string", "rtmpt.amf.longstring", FT_STRINGZ, BASE_NONE, NULL, 0x0, "RTMPT AMF long string", HFILL }},
+
+ { &hf_rtmpt_amf_xml,
+ { "XML document", "rtmpt.amf.xml", FT_STRINGZ, BASE_NONE, NULL, 0x0, "RTMPT AMF XML document", HFILL }},
+
+ { &hf_rtmpt_amf_int64,
+ { "Int64", "rtmpt.amf.int64", FT_INT64, BASE_DEC, NULL, 0x0, "RTMPT AMF int64", HFILL }},
+
+/* AMF object types */
+ { &hf_rtmpt_amf_object,
+ { "Object", "rtmpt.amf.object", FT_NONE, BASE_NONE, NULL, 0x0, "RTMPT AMF object", HFILL }},
+
+ { &hf_rtmpt_amf_ecmaarray,
+ { "ECMA array", "rtmpt.amf.ecmaarray", FT_NONE, BASE_NONE, NULL, 0x0, "RTMPT AMF ECMA array", HFILL }},
+
+ { &hf_rtmpt_amf_strictarray,
+ { "Strict array", "rtmpt.amf.strictarray", FT_NONE, BASE_NONE, NULL, 0x0, "RTMPT AMF strict array", HFILL }},
+
+ { &hf_rtmpt_amf_arraylength,
+ { "Array length", "rtmpt.amf.arraylength", FT_UINT32, BASE_DEC, NULL, 0x0, "RTMPT AMF array length", 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_amf_number,
- { "AMF number", "rtmpt.amf.number", FT_DOUBLE, BASE_NONE, NULL, 0x0, "RTMPT AMF number", 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_amf_boolean,
- { "AMF boolean", "rtmpt.amf.boolean", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "RTMPT AMF boolean", 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_amf_string,
- { "AMF string", "rtmpt.amf.string", FT_STRINGZ, BASE_NONE, NULL, 0x0, "RTMPT AMF string", 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_value,
+ &ett_rtmpt_property,
+ &ett_rtmpt_string,
&ett_rtmpt_object,
- &ett_rtmpt_property
+ &ett_rtmpt_mixed_array,
+ &ett_rtmpt_array,
+ &ett_rtmpt_audio_control,
+ &ett_rtmpt_video_control,
+ &ett_rtmpt_tag,
+ &ett_rtmpt_tag_data
};
module_t *rtmpt_module;
@@ -806,12 +2048,29 @@ proto_register_rtmpt(void)
"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);
-
}
void
proto_reg_handoff_rtmpt(void)
{
+/* heur_dissector_add("tcp", dissect_rtmpt_heur, proto_rtmpt); */
rtmpt_tcp_handle = create_dissector_handle(dissect_rtmpt_tcp, proto_rtmpt);
+/* dissector_add_handle("tcp.port", rtmpt_tcp_handle); */
dissector_add("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);
}
+
+/*
+ * 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:
+ */