Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-rtmpt.c
blob3f70cb5796f0499d726a56d7f96e3d006fe0829f
1 /* packet-rtmpt.c
2 * Routines for Real Time Messaging Protocol packet dissection
3 * metatech <metatech@flashmail.com>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 /* This dissector is called RTMPT to avoid a conflict with
13 * the other RTMP protocol (Routing Table Maintenance Protocol) implemented in packet-atalk.c
14 * (RTMPT normally stands for RTMP-Tunnel via http)
16 * RTMP in a nutshell
18 * The protocol has very few "magic words" to facilitate detection,
19 * but rather has "magic lengths".
20 * This protocol has plenty of special cases and few general rules,
21 * especially regarding the lengths and the structures.
23 * Documentation:
24 * RTMP protocol description on Wiki of Red5 Open Source Flash Server at
26 * http://trac.red5.org/wiki/Codecs/RTMPSpecification
28 * and the pages to which it links:
30 * http://osflash.org/documentation/rtmp
31 * http://wiki.gnashdev.org/RTMP
32 * http://wiki.gnashdev.org/RTMP_Messages_Decoded
33 * http://www.acmewebworks.com/Downloads/openCS/TheAMF.pdf
34 * https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf
36 * It's also available from Adobe at
38 * https://www.adobe.com/devnet/rtmp.html
40 * For AMF, see:
42 * http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf
44 * for AMF0 and
46 * http://amf3cplusplus.googlecode.com/svn-history/r4/trunk/doc/amf3_spec_05_05_08.pdf
48 * for AMF3.
50 * For FLV, see:
52 * https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf
54 * For Enhanced RTMP, see:
56 * https://veovera.org/docs/enhanced/enhanced-rtmp-v2.pdf
58 * Default TCP port is 1935
61 #include "config.h"
62 #define WS_LOG_DOMAIN "RTMPT"
64 #include <epan/packet.h>
65 #include <wsutil/pint.h>
67 #include <epan/prefs.h>
68 #include <epan/to_str.h>
69 #include <epan/expert.h>
70 #include "packet-tcp.h"
72 #define MAX_AMF_ITERATIONS 1000
74 void proto_register_rtmpt(void);
75 void proto_reg_handoff_rtmpt(void);
77 void proto_register_amf(void);
79 static int proto_rtmpt;
81 static int hf_rtmpt_handshake_c0;
82 static int hf_rtmpt_handshake_s0;
83 static int hf_rtmpt_handshake_c1;
84 static int hf_rtmpt_handshake_s1;
85 static int hf_rtmpt_handshake_c2;
86 static int hf_rtmpt_handshake_s2;
88 static int hf_rtmpt_header_format;
89 static int hf_rtmpt_header_csid;
90 static int hf_rtmpt_header_timestamp;
91 static int hf_rtmpt_header_timestamp_delta;
92 static int hf_rtmpt_header_body_size;
93 static int hf_rtmpt_header_typeid;
94 static int hf_rtmpt_header_streamid;
95 static int hf_rtmpt_header_ets;
97 static int hf_rtmpt_scm_chunksize;
98 static int hf_rtmpt_scm_csid;
99 static int hf_rtmpt_scm_seq;
100 static int hf_rtmpt_scm_was;
101 static int hf_rtmpt_scm_limittype;
103 static int hf_rtmpt_ucm_eventtype;
105 static int hf_rtmpt_function_call;
106 static int hf_rtmpt_function_response;
108 static int hf_rtmpt_audio_control;
109 static int hf_rtmpt_audio_multitrack_control;
110 static int hf_rtmpt_audio_is_ex_header;
111 static int hf_rtmpt_audio_format;
112 static int hf_rtmpt_audio_rate;
113 static int hf_rtmpt_audio_size;
114 static int hf_rtmpt_audio_type;
115 static int hf_rtmpt_audio_packet_type;
116 static int hf_rtmpt_audio_multitrack_type;
117 static int hf_rtmpt_audio_multitrack_packet_type;
118 static int hf_rtmpt_audio_fourcc;
119 static int hf_rtmpt_audio_track_id;
120 static int hf_rtmpt_audio_track_length;
121 static int hf_rtmpt_audio_data;
123 static int hf_rtmpt_video_control;
124 static int hf_rtmpt_video_multitrack_control;
125 static int hf_rtmpt_video_is_ex_header;
126 static int hf_rtmpt_video_type;
127 static int hf_rtmpt_video_command;
128 static int hf_rtmpt_video_format;
129 static int hf_rtmpt_video_packet_type;
130 static int hf_rtmpt_video_multitrack_type;
131 static int hf_rtmpt_video_multitrack_packet_type;
132 static int hf_rtmpt_video_fourcc;
133 static int hf_rtmpt_video_track_id;
134 static int hf_rtmpt_video_track_length;
135 static int hf_rtmpt_video_data;
137 static int hf_rtmpt_tag_type;
138 static int hf_rtmpt_tag_datasize;
139 static int hf_rtmpt_tag_timestamp;
140 static int hf_rtmpt_tag_ets;
141 static int hf_rtmpt_tag_streamid;
142 static int hf_rtmpt_tag_tagsize;
144 static expert_field ei_amf_loop;
146 static int ett_rtmpt;
147 static int ett_rtmpt_handshake;
148 static int ett_rtmpt_header;
149 static int ett_rtmpt_body;
150 static int ett_rtmpt_ucm;
151 static int ett_rtmpt_audio_control;
152 static int ett_rtmpt_video_control;
153 static int ett_rtmpt_audio_multitrack_control;
154 static int ett_rtmpt_audio_multitrack_track;
155 static int ett_rtmpt_video_multitrack_control;
156 static int ett_rtmpt_video_multitrack_track;
157 static int ett_rtmpt_tag;
158 static int ett_rtmpt_tag_data;
160 static dissector_handle_t amf_handle;
161 static dissector_handle_t rtmpt_tcp_handle;
162 static dissector_handle_t rtmpt_http_handle;
164 static bool rtmpt_desegment = true;
166 /* Native Bandwidth Detection (using the checkBandwidth(), onBWCheck(),
167 * onBWDone() calls) transmits a series of increasing size packets over
168 * the course of 2 seconds. On a fast link the largest packet can just
169 * exceed 256KB, but setting the limit there can cause massive memory
170 * usage, especially with fuzzed packets where the length value is bogus.
171 * Limit the initial allocation size and realloc if needed, i.e., in
172 * future frames if the bytes are actually there.
173 * This initial max allocation size can be reduced further if need be,
174 * but keep it at least 18 so that the headers fit without checking,
175 * See https://gitlab.com/wireshark/wireshark/-/issues/6898
177 #define RTMPT_INIT_ALLOC_SIZE 32768
179 #define RTMP_PORT 1935 /* Not IANA registered */
181 #define RTMPT_MAGIC 0x03
182 #define RTMPT_HANDSHAKE_OFFSET_1 1
183 #define RTMPT_HANDSHAKE_OFFSET_2 1538
184 #define RTMPT_HANDSHAKE_OFFSET_3 3074
185 #define RTMPT_HANDSHAKE_LENGTH_1 1537
186 #define RTMPT_HANDSHAKE_LENGTH_2 3073
187 #define RTMPT_HANDSHAKE_LENGTH_3 1536
188 #define RTMPT_INITIAL_CHUNK_SIZE 128
190 static unsigned rtmpt_default_chunk_size = 128;
192 #define RTMPT_ID_MAX 65599
193 #define RTMPT_TYPE_HANDSHAKE_1 0x100001
194 #define RTMPT_TYPE_HANDSHAKE_2 0x100002
195 #define RTMPT_TYPE_HANDSHAKE_3 0x100003
197 #define RTMPT_TYPE_CHUNK_SIZE 0x01
198 #define RTMPT_TYPE_ABORT_MESSAGE 0x02
199 #define RTMPT_TYPE_ACKNOWLEDGEMENT 0x03
200 #define RTMPT_TYPE_UCM 0x04
201 #define RTMPT_TYPE_WINDOW 0x05
202 #define RTMPT_TYPE_PEER_BANDWIDTH 0x06
203 #define RTMPT_TYPE_AUDIO_DATA 0x08
204 #define RTMPT_TYPE_VIDEO_DATA 0x09
205 #define RTMPT_TYPE_DATA_AMF3 0x0F
206 #define RTMPT_TYPE_SHARED_AMF3 0x10
207 #define RTMPT_TYPE_COMMAND_AMF3 0x11
208 #define RTMPT_TYPE_DATA_AMF0 0x12
209 #define RTMPT_TYPE_SHARED_AMF0 0x13
210 #define RTMPT_TYPE_COMMAND_AMF0 0x14
211 #define RTMPT_TYPE_AGGREGATE 0x16
213 #define RTMPT_UCM_STREAM_BEGIN 0x00
214 #define RTMPT_UCM_STREAM_EOF 0x01
215 #define RTMPT_UCM_STREAM_DRY 0x02
216 #define RTMPT_UCM_SET_BUFFER 0x03
217 #define RTMPT_UCM_STREAM_ISRECORDED 0x04
218 #define RTMPT_UCM_PING_REQUEST 0x06
219 #define RTMPT_UCM_PING_RESPONSE 0x07
221 #define RTMPT_IS_EX_AUDIO_HEADER 0x90
222 #define RTMPT_IS_EX_VIDEO_HEADER 0x80
223 #define RTMPT_IS_PACKET_TYPE_METADATA 0x04
224 #define RTMPT_IS_FRAME_TYPE_COMMAND 0x05
225 #define RTMPT_IS_AUDIO_MULTITRACK 0x05
226 #define RTMPT_IS_VIDEO_MULTITRACK 0x06
227 #define RTMPT_IS_ONETRACK 0x00
228 #define RTMPT_IS_MANYTRACKSMANYCODECS 0x02
230 #define RTMPT_TEXT_RTMP_HEADER "RTMP Header"
231 #define RTMPT_TEXT_RTMP_BODY "RTMP Body"
233 static const value_string rtmpt_handshake_vals[] = {
234 { RTMPT_TYPE_HANDSHAKE_1, "Handshake C0+C1" },
235 { RTMPT_TYPE_HANDSHAKE_2, "Handshake S0+S1+S2" },
236 { RTMPT_TYPE_HANDSHAKE_3, "Handshake C2" },
237 { 0, NULL }
240 static const value_string rtmpt_opcode_vals[] = {
241 { RTMPT_TYPE_CHUNK_SIZE, "Set Chunk Size" },
242 { RTMPT_TYPE_ABORT_MESSAGE, "Abort Message" },
243 { RTMPT_TYPE_ACKNOWLEDGEMENT, "Acknowledgement" },
244 { RTMPT_TYPE_UCM, "User Control Message" },
245 { RTMPT_TYPE_WINDOW, "Window Acknowledgement Size" },
246 { RTMPT_TYPE_PEER_BANDWIDTH, "Set Peer Bandwidth" },
247 { RTMPT_TYPE_AUDIO_DATA, "Audio Data" },
248 { RTMPT_TYPE_VIDEO_DATA, "Video Data" },
249 { RTMPT_TYPE_DATA_AMF3, "AMF3 Data" },
250 { RTMPT_TYPE_SHARED_AMF3, "AMF3 Shared Object" },
251 { RTMPT_TYPE_COMMAND_AMF3, "AMF3 Command" },
252 { RTMPT_TYPE_DATA_AMF0, "AMF0 Data" },
253 { RTMPT_TYPE_SHARED_AMF0, "AMF0 Shared Object" },
254 { RTMPT_TYPE_COMMAND_AMF0, "AMF0 Command" },
255 { RTMPT_TYPE_AGGREGATE, "Aggregate" },
256 { 0, NULL }
259 static const value_string rtmpt_limit_vals[] = {
260 /* These are a complete guess, from the order of the documented
261 * options - the values aren't actually specified */
262 { 0, "Hard" },
263 { 1, "Soft" },
264 { 2, "Dynamic" },
265 { 0, NULL }
268 static const value_string rtmpt_ucm_vals[] = {
269 { RTMPT_UCM_STREAM_BEGIN, "Stream Begin" },
270 { RTMPT_UCM_STREAM_EOF, "Stream EOF" },
271 { RTMPT_UCM_STREAM_DRY, "Stream Dry" },
272 { RTMPT_UCM_SET_BUFFER, "Set Buffer Length" },
273 { RTMPT_UCM_STREAM_ISRECORDED, "Stream Is Recorded" },
274 { RTMPT_UCM_PING_REQUEST, "Ping Request" },
275 { RTMPT_UCM_PING_RESPONSE, "Ping Response" },
276 { 0, NULL }
279 static const value_string rtmpt_tag_vals[] = {
280 { RTMPT_TYPE_AUDIO_DATA, "Audio Tag" },
281 { RTMPT_TYPE_VIDEO_DATA, "Video Tag" },
282 { RTMPT_TYPE_DATA_AMF0, "Script Tag" },
283 { 0, NULL }
286 /* [Spec] https://github.com/runner365/read_book/blob/master/rtmp/rtmp_specification_1.0.pdf */
287 /* [DevG] http://help.adobe.com/en_US/flashmediaserver/devguide/index.html "working with Live Video" => Adding metadata to a live stream */
288 /* [SWF] https://github.com/blackears/raven/blob/master/proj/SWFParser/doc/swf_file_format_spec_v10.pdf */
289 static const value_string rtmpt_audio_codecs[] = {
290 { 0, "Uncompressed" }, /* [DevG] */
291 { 1, "ADPCM" }, /* [DevG] */
292 { 2, "MP3" }, /* [DevG] */
293 { 3, "Uncompressed, little-endian"}, /* [SWF] */
294 { 4, "Nellymoser 16kHz" }, /* [SWF] */
295 { 5, "Nellymoser 8kHz" }, /* [DevG] [SWF]*/
296 { 6, "Nellymoser" }, /* [DevG] [SWF]*/
297 { 7, "G711A" }, /* [Spec] */
298 { 8, "G711U" }, /* [Spec] */
299 { 9, "Nellymoser 16kHz" }, /* [Spec] */
300 { 10, "HE-AAC" }, /* [DevG] */
301 { 11, "SPEEX" }, /* [DevG] */
302 { 0, NULL }
305 static const value_string rtmpt_audio_packet_types[] = {
306 { 0, "PacketTypeSequenceStart" },
307 { 1, "PacketTypeCodedFrames" },
308 { 4, "PacketTypeMultichannelConfig" },
309 { 5, "PacketTypeMultitrack" },
310 { 0, NULL }
313 static const value_string rtmpt_av_multitrack_types[] = {
314 { 0, "AvMultitrackTypeOneTrack" },
315 { 1, "AvMultitrackTypeManyTracks" },
316 { 2, "AvMultitrackTypeManyTracksManyCodecs" },
317 { 0, NULL }
320 static const value_string rtmpt_audio_rates[] = {
321 { 0, "5.5 kHz" },
322 { 1, "11 kHz" },
323 { 2, "22 kHz" },
324 { 3, "44 kHz" },
325 { 0, NULL }
328 static const value_string rtmpt_audio_sizes[] = {
329 { 0, "8 bit" },
330 { 1, "16 bit" },
331 { 0, NULL }
334 static const value_string rtmpt_audio_types[] = {
335 { 0, "mono" },
336 { 1, "stereo" },
337 { 0, NULL }
340 /* from FLV v10.1 section E.4.3.1 */
341 static const value_string rtmpt_video_types[] = {
342 { 1, "keyframe" },
343 { 2, "inter-frame" },
344 { 3, "disposable inter-frame" },
345 { 4, "generated key frame" },
346 { 5, "video info/command frame" },
347 { 0, NULL }
350 /* From Enhanced RTMP */
351 static const value_string rtmpt_video_commands[] = {
352 { 0, "StartSeek" },
353 { 1, "EndSeek" },
354 { 0, NULL }
357 /* from FLV v10.1 section E.4.3.1 */
358 static const value_string rtmpt_video_codecs[] = {
359 { 2, "Sorensen H.263" },
360 { 3, "Screen video" },
361 { 4, "On2 VP6" },
362 { 5, "On2 VP6+alpha" },
363 { 6, "Screen video version 2" },
364 { 7, "H.264" },
365 { 12, "H.265" },
366 { 0, NULL }
370 * https://raw.githubusercontent.com/veovera/enhanced-rtmp/main/enhanced-rtmp.pdf
372 static const value_string rtmpt_video_packet_types[] = {
373 { 0, "PacketTypeSequenceStart" },
374 { 1, "PacketTypeCodedFrames" },
375 { 2, "PacketTypeSequenceEnd" },
376 { 3, "PacketTypeCodedFramesX" },
377 { 4, "PacketTypeMetadata" },
378 { 5, "PacketTypeMPEG2TSSequenceStart" },
379 { 6, "PacketTypeMultitrack" },
380 { 0, NULL }
383 static int proto_amf;
385 static int hf_amf_version;
386 static int hf_amf_header_count;
387 static int hf_amf_header_name;
388 static int hf_amf_header_must_understand;
389 static int hf_amf_header_length;
390 /* static int hf_amf_header_value_type; */
391 static int hf_amf_message_count;
392 static int hf_amf_message_target_uri;
393 static int hf_amf_message_response_uri;
394 static int hf_amf_message_length;
396 static int hf_amf_amf0_type;
397 static int hf_amf_amf3_type;
398 static int hf_amf_number;
399 static int hf_amf_integer;
400 static int hf_amf_boolean;
401 static int hf_amf_stringlength;
402 static int hf_amf_string;
403 static int hf_amf_string_reference;
404 static int hf_amf_object_reference;
405 static int hf_amf_date;
406 /* static int hf_amf_longstringlength; */
407 static int hf_amf_longstring;
408 static int hf_amf_xml_doc;
409 static int hf_amf_xmllength;
410 static int hf_amf_xml;
411 static int hf_amf_int64;
412 static int hf_amf_bytearraylength;
413 static int hf_amf_bytearray;
415 static int hf_amf_object;
416 static int hf_amf_traitcount;
417 static int hf_amf_classnamelength;
418 static int hf_amf_classname;
419 static int hf_amf_membernamelength;
420 static int hf_amf_membername;
421 static int hf_amf_trait_reference;
422 static int hf_amf_ecmaarray;
423 static int hf_amf_strictarray;
424 static int hf_amf_array;
425 static int hf_amf_arraylength;
426 static int hf_amf_arraydenselength;
428 static int hf_amf_end_of_object_marker;
429 static int hf_amf_end_of_associative_part;
430 static int hf_amf_end_of_dynamic_members;
432 static int ett_amf;
433 static int ett_amf_headers;
434 static int ett_amf_messages;
435 static int ett_amf_value;
436 static int ett_amf_property;
437 static int ett_amf_string;
438 static int ett_amf_array_element;
439 static int ett_amf_traits;
440 static int ett_amf_trait_member;
442 /* AMF0 type markers */
443 #define AMF0_NUMBER 0x00
444 #define AMF0_BOOLEAN 0x01
445 #define AMF0_STRING 0x02
446 #define AMF0_OBJECT 0x03
447 #define AMF0_MOVIECLIP 0x04
448 #define AMF0_NULL 0x05
449 #define AMF0_UNDEFINED 0x06
450 #define AMF0_REFERENCE 0x07
451 #define AMF0_ECMA_ARRAY 0x08
452 #define AMF0_END_OF_OBJECT 0x09
453 #define AMF0_STRICT_ARRAY 0x0A
454 #define AMF0_DATE 0x0B
455 #define AMF0_LONG_STRING 0x0C
456 #define AMF0_UNSUPPORTED 0x0D
457 #define AMF0_RECORDSET 0x0E
458 #define AMF0_XML 0x0F
459 #define AMF0_TYPED_OBJECT 0x10
460 #define AMF0_AMF3_MARKER 0x11
461 #define AMF0_INT64 0x22
463 /* AMF3 type markers */
464 #define AMF3_UNDEFINED 0x00
465 #define AMF3_NULL 0x01
466 #define AMF3_FALSE 0x02
467 #define AMF3_TRUE 0x03
468 #define AMF3_INTEGER 0x04
469 #define AMF3_DOUBLE 0x05
470 #define AMF3_STRING 0x06
471 #define AMF3_XML_DOC 0x07
472 #define AMF3_DATE 0x08
473 #define AMF3_ARRAY 0x09
474 #define AMF3_OBJECT 0x0A
475 #define AMF3_XML 0x0B
476 #define AMF3_BYTEARRAY 0x0C
478 static const value_string amf0_type_vals[] = {
479 { AMF0_NUMBER, "Number" },
480 { AMF0_BOOLEAN, "Boolean" },
481 { AMF0_STRING, "String" },
482 { AMF0_OBJECT, "Object" },
483 { AMF0_MOVIECLIP, "Movie clip" },
484 { AMF0_NULL, "Null" },
485 { AMF0_UNDEFINED, "Undefined" },
486 { AMF0_REFERENCE, "Reference" },
487 { AMF0_ECMA_ARRAY, "ECMA array" },
488 { AMF0_END_OF_OBJECT, "End of object" },
489 { AMF0_STRICT_ARRAY, "Strict array" },
490 { AMF0_DATE, "Date" },
491 { AMF0_LONG_STRING, "Long string" },
492 { AMF0_UNSUPPORTED, "Unsupported" },
493 { AMF0_RECORDSET, "Record set" },
494 { AMF0_XML, "XML" },
495 { AMF0_TYPED_OBJECT, "Typed object" },
496 { AMF0_AMF3_MARKER, "Switch to AMF3" },
497 { AMF0_INT64, "Int64" },
498 { 0, NULL }
501 static const value_string amf3_type_vals[] = {
502 { AMF3_UNDEFINED, "Undefined" },
503 { AMF3_NULL, "Null" },
504 { AMF3_FALSE, "False" },
505 { AMF3_TRUE, "True" },
506 { AMF3_INTEGER, "Integer" },
507 { AMF3_DOUBLE, "Double" },
508 { AMF3_STRING, "String" },
509 { AMF3_XML_DOC, "XML document" },
510 { AMF3_DATE, "Date" },
511 { AMF3_ARRAY, "Array" },
512 { AMF3_OBJECT, "Object" },
513 { AMF3_XML, "XML" },
514 { AMF3_BYTEARRAY, "ByteArray" },
515 { 0, NULL }
518 /* Holds the reassembled data for a packet during un-chunking
520 /* XXX: Because we don't use the TCP dissector's built-in desegmentation,
521 * or the standard reassembly API, we don't get FT_FRAMENUM links for
522 * chunk fragments, and we don't mark depended upon frames for export.
523 * Ideally we'd use the standard API (mark pinfo->desegment_offset and
524 * pinfo->desegment_len for TCP, or use the reassembly API), or call
525 * mark_frame_as_depended_upon and add the FT_FRAMENUM fields ourselves.
526 * To do the latter, we should have a data structure indicating which
527 * frames contributed to the packet.
529 typedef struct rtmpt_packet {
530 uint32_t seq;
531 uint32_t lastseq;
533 int resident;
534 union {
535 uint8_t *p;
536 uint32_t offset;
537 } data;
539 wmem_list_t *frames;
541 /* used during unchunking */
542 int alloc;
543 int want;
544 int have;
545 int chunkwant;
546 int chunkhave;
548 uint8_t bhlen;
549 uint8_t mhlen;
551 /* Chunk Basic Header */
552 uint8_t fmt; /* byte 0 */
553 uint32_t id; /* byte 0 */
555 /* Chunk Message Header (offsets assume bhlen == 1) */
556 uint32_t ts; /* bytes 1-3, or from ETS @ mhlen-4 if -1 */
557 uint32_t len; /* bytes 4-6 */
558 uint8_t cmd; /* byte 7 */
559 uint32_t src; /* bytes 8-11 */
561 uint32_t txid;
562 int isresponse;
563 int otherframe;
565 } rtmpt_packet_t;
567 /* Represents a header or a chunk that is split over two TCP
568 * segments
570 typedef struct rtmpt_frag {
571 int ishdr;
572 uint32_t seq;
573 uint32_t lastseq;
574 int have;
575 int len;
577 union {
578 uint8_t d[18]; /* enough for a complete header (3 + 11 + 4) */
579 uint32_t id;
580 } saved;
581 } rtmpt_frag_t;
583 /* The full message header information for the last packet on a particular
584 * ID - used for defaulting short headers
586 typedef struct rtmpt_id {
587 uint32_t ts; /* bytes 1-3 */
588 uint32_t tsd;
589 uint32_t len; /* bytes 4-6 */
590 uint32_t src; /* bytes 8-11 */
591 uint8_t cmd; /* byte 7 */
593 wmem_tree_t *packets;
594 } rtmpt_id_t;
596 /* Historical view of a whole TCP connection
598 typedef struct rtmpt_conv {
599 wmem_tree_t *seqs[2];
600 wmem_tree_t *frags[2];
601 wmem_tree_t *ids[2];
602 wmem_tree_t *packets[2];
603 wmem_tree_t *chunksize[2];
604 wmem_tree_t *txids[2];
605 } rtmpt_conv_t;
607 static void
608 rtmpt_packet_mark_depended(void *data, void *user_data)
610 frame_data *fd = (frame_data *)user_data;
611 uint32_t frame_num = GPOINTER_TO_UINT(data);
612 mark_frame_as_depended_upon(fd, frame_num);
615 /* Header length helpers */
617 static int rtmpt_basic_header_length(int id)
619 switch (id & 0x3f) {
620 case 0: return 2;
621 case 1: return 3;
622 default: return 1;
626 static int rtmpt_message_header_length(int id)
628 switch ((id>>6) & 3) {
629 case 0: return 11;
630 case 1: return 7;
631 case 2: return 3;
632 default: return 0;
636 /* Lightweight access to AMF0 blobs - more complete dissection is done
637 * in dissect_rtmpt_body_command */
639 static uint32_t
640 rtmpt_get_amf_length(tvbuff_t *tvb, int offset, proto_item* pi)
642 uint8_t iObjType;
643 int remain = tvb_reported_length_remaining(tvb, offset);
644 uint32_t depth = 0;
645 uint32_t itemlen = 0;
646 uint32_t rv = 0;
647 unsigned iterations = MAX_AMF_ITERATIONS;
649 while (rv == 0 || depth > 0) {
651 if (--iterations == 0) {
652 expert_add_info(NULL, pi, &ei_amf_loop);
653 return 0;
656 if (depth > 0) {
657 if (remain-rv < 2)
658 return remain;
659 itemlen = tvb_get_ntohs(tvb, offset+rv) + 2;
660 if (remain-rv<itemlen+1)
661 return remain;
662 rv += itemlen;
665 if (remain-rv < 1)
666 return remain;
667 iObjType = tvb_get_uint8(tvb, offset+rv);
669 if (depth > 0 && itemlen == 2 && iObjType == AMF0_END_OF_OBJECT) {
670 rv++;
671 depth--;
672 continue;
675 switch (iObjType) {
676 case AMF0_NUMBER:
677 itemlen = 9;
678 break;
679 case AMF0_BOOLEAN:
680 itemlen = 2;
681 break;
682 case AMF0_STRING:
683 if (remain-rv < 3)
684 return remain;
685 itemlen = tvb_get_ntohs(tvb, offset+rv+1) + 3;
686 break;
687 case AMF0_NULL:
688 case AMF0_UNDEFINED:
689 case AMF0_UNSUPPORTED:
690 itemlen = 1;
691 break;
692 case AMF0_DATE:
693 itemlen = 11;
694 break;
695 case AMF0_LONG_STRING:
696 case AMF0_XML:
697 if (remain-rv < 5)
698 return remain;
699 itemlen = tvb_get_ntohl(tvb, offset+rv+1) + 5;
700 break;
701 case AMF0_INT64:
702 itemlen = 9;
703 break;
704 case AMF0_OBJECT:
705 itemlen = 1;
706 depth++;
707 break;
708 case AMF0_ECMA_ARRAY:
709 itemlen = 5;
710 depth++;
711 break;
712 default:
713 return remain;
716 if (remain-rv < itemlen)
717 return remain;
718 rv += itemlen;
722 return rv;
725 static char *
726 rtmpt_get_amf_param(tvbuff_t *tvb, int offset, proto_item* pi, int param, const char *prop)
728 uint32_t remain = tvb_reported_length_remaining(tvb, offset);
729 uint32_t itemlen;
730 uint32_t iStringLength;
732 while (remain > 0 && param > 0) {
733 itemlen = rtmpt_get_amf_length(tvb, offset, pi);
734 if (itemlen == 0)
735 break;
736 offset += itemlen;
737 remain -= itemlen;
738 param--;
741 if (remain > 0 && param == 0) {
742 uint8_t iObjType = tvb_get_uint8(tvb, offset);
744 if (!prop && iObjType == AMF0_STRING && remain >= 3) {
745 iStringLength = tvb_get_ntohs(tvb, offset+1);
746 if (remain >= iStringLength+3) {
747 return tvb_get_string_enc(wmem_packet_scope(), tvb, offset+3, iStringLength, ENC_ASCII);
751 if (prop && iObjType == AMF0_OBJECT) {
752 offset++;
753 remain--;
755 while (remain > 2) {
756 uint32_t iPropLength = tvb_get_ntohs(tvb, offset);
757 if (remain < 2+iPropLength+3)
758 break;
760 if (tvb_strneql(tvb, offset+2, prop, strlen(prop)) == 0) {
761 if (tvb_get_uint8(tvb, offset+2+iPropLength) != AMF0_STRING)
762 break;
764 iStringLength = tvb_get_ntohs(tvb, offset+2+iPropLength+1);
765 if (remain < 2+iPropLength+3+iStringLength)
766 break;
768 return tvb_get_string_enc(wmem_packet_scope(), tvb, offset+2+iPropLength+3, iStringLength, ENC_ASCII);
771 itemlen = rtmpt_get_amf_length(tvb, offset+2+iPropLength, pi);
772 if (itemlen == 0)
773 break;
774 offset += 2+iPropLength+itemlen;
775 remain -= 2+iPropLength+itemlen;
780 return NULL;
783 static uint32_t
784 rtmpt_get_amf_txid(tvbuff_t *tvb, int offset, proto_item* pi)
786 uint32_t remain = tvb_reported_length_remaining(tvb, offset);
788 if (remain > 0) {
789 uint32_t itemlen = rtmpt_get_amf_length(tvb, offset, pi);
790 if (itemlen == 0 || remain < itemlen)
791 return 0;
792 offset += itemlen;
793 remain -= itemlen;
795 if (remain >= 9) {
796 uint8_t iObjType = tvb_get_uint8(tvb, offset);
797 if (iObjType == AMF0_NUMBER) {
798 return (uint32_t)tvb_get_ntohieee_double(tvb, offset+1);
802 return 0;
806 /* Generate a useful description for various packet types */
808 static char *
809 rtmpt_get_packet_desc(tvbuff_t *tvb, uint32_t offset, proto_item* pi, uint32_t remain, rtmpt_conv_t *rconv, int cdir,
810 rtmpt_packet_t *tp, bool *deschasopcode)
812 if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE || tp->cmd == RTMPT_TYPE_ABORT_MESSAGE ||
813 tp->cmd == RTMPT_TYPE_ACKNOWLEDGEMENT || tp->cmd == RTMPT_TYPE_WINDOW) {
814 if (tp->len >= 4 && remain >= 4) {
815 *deschasopcode = true;
816 return wmem_strdup_printf(wmem_packet_scope(), "%s %d",
817 val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"),
818 tvb_get_ntohl(tvb, offset));
821 } else if (tp->cmd == RTMPT_TYPE_PEER_BANDWIDTH) {
822 if (tp->len >= 5 && remain >= 5) {
823 *deschasopcode = true;
824 return wmem_strdup_printf(wmem_packet_scope(), "%s %d,%s",
825 val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"),
826 tvb_get_ntohl(tvb, offset),
827 val_to_str(tvb_get_uint8(tvb, offset+4), rtmpt_limit_vals, "Unknown (%d)"));
830 } else if (tp->cmd == RTMPT_TYPE_UCM) {
831 uint16_t iUCM = -1;
832 const char *sFunc;
833 const char *sParam = "";
835 if (tp->len < 2 || remain < 2)
836 return NULL;
838 iUCM = tvb_get_ntohs(tvb, offset);
839 sFunc = try_val_to_str(iUCM, rtmpt_ucm_vals);
840 if (sFunc == NULL) {
841 *deschasopcode = true;
842 sFunc = wmem_strdup_printf(wmem_packet_scope(), "User Control Message 0x%01x", iUCM);
845 if (iUCM == RTMPT_UCM_STREAM_BEGIN || iUCM == RTMPT_UCM_STREAM_EOF ||
846 iUCM == RTMPT_UCM_STREAM_DRY || iUCM == RTMPT_UCM_STREAM_ISRECORDED) {
847 if (tp->len >= 6 && remain >= 6) {
848 sParam = wmem_strdup_printf(wmem_packet_scope(), " %d", tvb_get_ntohl(tvb, offset+2));
850 } else if (iUCM == RTMPT_UCM_SET_BUFFER) {
851 if (tp->len >= 10 && remain >= 10) {
852 sParam = wmem_strdup_printf(wmem_packet_scope(), " %d,%dms",
853 tvb_get_ntohl(tvb, offset+2),
854 tvb_get_ntohl(tvb, offset+6));
858 return wmem_strdup_printf(wmem_packet_scope(), "%s%s", sFunc, sParam);
860 } else if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 ||
861 tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
862 uint32_t slen = 0;
863 uint32_t soff = 0;
864 char *sFunc = NULL;
865 char *sParam = NULL;
867 if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
868 soff = 1;
870 if (tp->len >= 3+soff && remain >= 3+soff) {
871 slen = tvb_get_ntohs(tvb, offset+1+soff);
873 if (slen > 0) {
874 sFunc = tvb_get_string_enc(wmem_packet_scope(), tvb, offset+3+soff, slen, ENC_ASCII);
875 ws_debug("got function call '%s'", sFunc);
877 if (strcmp(sFunc, "connect") == 0) {
878 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 2, "app");
879 } else if (strcmp(sFunc, "play") == 0) {
880 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, NULL);
881 } else if (strcmp(sFunc, "play2") == 0) {
882 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, "streamName");
883 } else if (strcmp(sFunc, "releaseStream") == 0) {
884 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, NULL);
885 } else if (strcmp(sFunc, "FCPublish") == 0) {
886 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, NULL);
887 } else if (strcmp(sFunc, "publish") == 0) {
888 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, NULL);
889 } else if (strcmp(sFunc, "onStatus") == 0) {
890 if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3) {
891 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, "code");
892 } else {
893 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 1, "code");
895 } else if (strcmp(sFunc, "onPlayStatus") == 0) {
896 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 1, "code");
897 } else if (strcmp(sFunc, "_result") == 0) {
898 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, "code");
899 tp->isresponse = true;
900 } else if (strcmp(sFunc, "_error") == 0) {
901 sParam = rtmpt_get_amf_param(tvb, offset+soff, pi, 3, "code");
902 tp->isresponse = true;
905 if (tp->txid != 0 && tp->otherframe == 0) {
906 tp->otherframe = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->txids[cdir^1], tp->txid));
907 if (tp->otherframe) {
908 ws_debug("got otherframe=%d", tp->otherframe);
913 if (sFunc) {
914 if (sParam) {
915 return wmem_strdup_printf(wmem_packet_scope(), "%s('%s')", sFunc, sParam);
916 } else {
917 return wmem_strdup_printf(wmem_packet_scope(), "%s()", sFunc);
922 return NULL;
926 /* Tree dissection helpers for various packet body forms */
928 static void
929 dissect_rtmpt_body_scm(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree, unsigned scm)
931 switch (scm) {
932 case RTMPT_TYPE_CHUNK_SIZE:
933 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_chunksize, tvb, offset, 4, ENC_BIG_ENDIAN);
934 break;
935 case RTMPT_TYPE_ABORT_MESSAGE:
936 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_csid, tvb, offset, 4, ENC_BIG_ENDIAN);
937 break;
938 case RTMPT_TYPE_ACKNOWLEDGEMENT:
939 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_seq, tvb, offset, 4, ENC_BIG_ENDIAN);
940 break;
941 case RTMPT_TYPE_UCM:
942 proto_tree_add_item(rtmpt_tree, hf_rtmpt_ucm_eventtype, tvb, offset, 2, ENC_BIG_ENDIAN);
943 break;
944 case RTMPT_TYPE_WINDOW:
945 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN);
946 break;
947 case RTMPT_TYPE_PEER_BANDWIDTH:
948 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN);
949 proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_limittype, tvb, offset+4, 1, ENC_BIG_ENDIAN);
950 break;
954 static int
955 dissect_amf0_value_type(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, bool *amf3_encoding, proto_item *parent_ti);
958 * A "property list" is a sequence of name/value pairs, terminated by
959 * and "end of object" indicator. AMF0 "object"s and "ECMA array"s
960 * are encoded as property lists.
962 static int
963 // NOLINTNEXTLINE(misc-no-recursion)
964 dissect_amf0_property_list(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, unsigned *countp, bool *amf3_encoding)
966 proto_item *prop_ti;
967 proto_tree *prop_tree;
968 proto_tree *name_tree;
969 unsigned iStringLength;
970 char *iStringValue;
971 unsigned count = 0;
974 * XXX - at least as I read "3.1 AVM+ Type Marker" in the AMF0
975 * specification, the AVM+ Type Marker only affects "the following
976 * Object". For now, we have a single "AMF3 encoding" flag, and
977 * set it when we see the type marker, and never clear it.
979 for (;;) {
980 /* UTF-8: property name */
981 iStringLength = tvb_get_ntohs(tvb, offset);
982 if (iStringLength == 0 &&
983 tvb_get_uint8(tvb, offset + 2) == AMF0_END_OF_OBJECT)
984 break;
985 count++;
986 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, offset + 2, iStringLength, ENC_ASCII);
987 prop_tree = proto_tree_add_subtree_format(tree, tvb, offset, -1,
988 ett_amf_property, &prop_ti, "Property '%s'",
989 iStringValue);
991 name_tree = proto_tree_add_subtree_format(prop_tree, tvb,
992 offset, 2+iStringLength,
993 ett_amf_string, NULL, "Name: %s", iStringValue);
995 proto_tree_add_uint(name_tree, hf_amf_stringlength, tvb, offset, 2, iStringLength);
996 offset += 2;
997 proto_tree_add_item(name_tree, hf_amf_string, tvb, offset, iStringLength, ENC_UTF_8);
998 offset += iStringLength;
1000 /* value-type: property value */
1001 offset = dissect_amf0_value_type(tvb, pinfo, offset, prop_tree, amf3_encoding, prop_ti);
1002 proto_item_set_end(prop_ti, tvb, offset);
1004 proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, offset, 3, ENC_NA);
1005 offset += 3;
1007 *countp = count;
1009 return offset;
1012 static int
1013 // NOLINTNEXTLINE(misc-no-recursion)
1014 dissect_amf0_value_type(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, bool *amf3_encoding, proto_item *parent_ti)
1016 uint8_t iObjType;
1017 proto_item *ti;
1018 proto_tree *val_tree;
1019 int iValueOffset = offset;
1020 uint32_t iIntegerValue;
1021 double iDoubleValue;
1022 bool iBooleanValue;
1023 unsigned iStringLength;
1024 char *iStringValue;
1025 unsigned iArrayLength;
1026 unsigned i;
1027 nstime_t t;
1028 int64_t iInteger64Value;
1029 unsigned count;
1031 iObjType = tvb_get_uint8(tvb, offset);
1032 if (parent_ti != NULL)
1033 proto_item_append_text(parent_ti, " %s",
1034 val_to_str_const(iObjType, amf0_type_vals, "Unknown"));
1035 switch (iObjType) {
1037 case AMF0_OBJECT:
1039 * For object types, make the top-level protocol tree
1040 * item a field for that type.
1042 ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA);
1043 val_tree = proto_item_add_subtree(ti, ett_amf_value);
1044 break;
1046 case AMF0_ECMA_ARRAY:
1048 * For ECMA array types, make the top-level protocol tree
1049 * item a field for that type.
1051 ti = proto_tree_add_item(tree, hf_amf_ecmaarray, tvb, offset, -1, ENC_NA);
1052 val_tree = proto_item_add_subtree(ti, ett_amf_value);
1053 break;
1055 case AMF0_STRICT_ARRAY:
1057 * For strict array types, make the top-level protocol tree
1058 * item a field for that type.
1060 ti = proto_tree_add_item(tree, hf_amf_strictarray, tvb, offset, -1, ENC_NA);
1061 val_tree = proto_item_add_subtree(ti, ett_amf_value);
1062 break;
1064 default:
1066 * For all other types, make it just a text item; the
1067 * field for that type will be used for the value.
1069 val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti,
1070 val_to_str_const(iObjType, amf0_type_vals, "Unknown"));
1071 break;
1074 proto_tree_add_uint(val_tree, hf_amf_amf0_type, tvb, iValueOffset, 1, iObjType);
1075 iValueOffset++;
1077 increment_dissection_depth(pinfo);
1078 switch (iObjType) {
1079 case AMF0_NUMBER:
1080 iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
1081 proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue);
1082 iValueOffset += 8;
1083 proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
1084 if (parent_ti != NULL)
1085 proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
1086 break;
1087 case AMF0_BOOLEAN:
1088 iBooleanValue = tvb_get_uint8(tvb, iValueOffset);
1089 proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, iValueOffset, 1, iBooleanValue);
1090 iValueOffset += 1;
1091 proto_item_append_text(ti, iBooleanValue ? " true" : " false");
1092 if (parent_ti != NULL)
1093 proto_item_append_text(parent_ti, iBooleanValue ? " true" : " false");
1094 break;
1095 case AMF0_STRING:
1096 iStringLength = tvb_get_ntohs(tvb, iValueOffset);
1097 proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
1098 iValueOffset += 2;
1099 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
1100 if (iStringLength != 0)
1101 proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
1102 iValueOffset += iStringLength;
1103 proto_item_append_text(ti, " '%s'", iStringValue);
1104 if (parent_ti != NULL)
1105 proto_item_append_text(parent_ti, " '%s'", iStringValue);
1106 break;
1107 case AMF0_OBJECT:
1108 iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding);
1109 proto_item_append_text(ti, " (%u items)", count);
1110 break;
1111 case AMF0_NULL:
1112 case AMF0_UNDEFINED:
1113 break;
1114 case AMF0_REFERENCE:
1115 iIntegerValue = tvb_get_ntohs(tvb, iValueOffset);
1116 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, 2, iIntegerValue);
1117 iValueOffset += 2;
1118 proto_item_append_text(ti, " %d", iIntegerValue);
1119 break;
1120 case AMF0_ECMA_ARRAY:
1122 * Counted list type, with end marker. The count appears to be
1123 * more of a hint than a rule, and is sometimes sent as 0 or
1124 * invalid.
1126 * Basically the same as OBJECT but with the extra count field.
1127 * There being many strange encoders/metadata injectors out
1128 * there, sometimes you see a valid count and no end marker.
1129 * Figuring out which you've got for a deeply nested structure
1130 * is non-trivial.
1132 iArrayLength = tvb_get_ntohl(tvb, iValueOffset);
1133 proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength);
1134 iValueOffset += 4;
1135 iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding);
1136 proto_item_append_text(ti, " (%u items)", count);
1137 break;
1138 case AMF0_END_OF_OBJECT:
1139 proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, iValueOffset, 3, ENC_NA);
1140 iValueOffset += 3;
1141 break;
1142 case AMF0_STRICT_ARRAY:
1144 * Counted list type, without end marker. Number of values
1145 * is determined by count, values are assumed to form a
1146 * [0..N-1] numbered array and are presented as plain AMF
1147 * types, not OBJECT or ECMA_ARRAY style named properties.
1149 iArrayLength = tvb_get_ntohl(tvb, iValueOffset);
1150 proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength);
1151 iValueOffset += 4;
1152 for (i = 0; i < iArrayLength; i++)
1153 iValueOffset = dissect_amf0_value_type(tvb, pinfo, iValueOffset, val_tree, amf3_encoding, NULL);
1154 proto_item_append_text(ti, " (%u items)", iArrayLength);
1155 break;
1156 case AMF0_DATE:
1157 iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
1158 t.secs = (time_t)(iDoubleValue/1000);
1159 t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000);
1160 proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t);
1161 iValueOffset += 8;
1162 proto_item_append_text(ti, " %s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true));
1163 if (parent_ti != NULL)
1164 proto_item_append_text(parent_ti, " %s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true));
1165 /* time-zone */
1166 iValueOffset += 2;
1167 break;
1168 case AMF0_LONG_STRING:
1169 case AMF0_XML: /* same representation */
1170 iStringLength = tvb_get_ntohl(tvb, iValueOffset);
1171 proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
1172 iValueOffset += 4;
1173 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
1174 if (iStringLength != 0)
1175 proto_tree_add_string(val_tree, (iObjType == AMF0_XML) ? hf_amf_xml_doc : hf_amf_longstring, tvb, iValueOffset, iStringLength, iStringValue);
1176 iValueOffset += iStringLength;
1177 proto_item_append_text(ti, " '%s'", iStringValue);
1178 if (parent_ti != NULL)
1179 proto_item_append_text(parent_ti, " '%s'", iStringValue);
1180 break;
1181 case AMF0_UNSUPPORTED:
1182 break;
1183 case AMF0_TYPED_OBJECT:
1184 /* class-name */
1185 iStringLength = tvb_get_ntohs(tvb, iValueOffset);
1186 proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength);
1187 iValueOffset += 2;
1188 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
1189 proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
1190 iValueOffset += iStringLength;
1191 iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding);
1192 break;
1193 case AMF0_AMF3_MARKER:
1194 *amf3_encoding = true;
1195 break;
1196 case AMF0_INT64:
1197 iInteger64Value = tvb_get_ntoh64(tvb, iValueOffset);
1198 proto_tree_add_int64(val_tree, hf_amf_int64, tvb, iValueOffset, 8, iInteger64Value);
1199 iValueOffset += 8;
1200 proto_item_append_text(ti," %" PRId64, iInteger64Value);
1201 if (parent_ti != NULL)
1202 proto_item_append_text(parent_ti," %" PRId64, iInteger64Value);
1203 break;
1204 default:
1206 * If we can't determine the length, don't carry on;
1207 * just skip to the end of the tvbuff.
1209 iValueOffset = tvb_reported_length(tvb);
1210 break;
1212 decrement_dissection_depth(pinfo);
1213 proto_item_set_end(ti, tvb, iValueOffset);
1214 return iValueOffset;
1217 static uint32_t
1218 amf_get_u29(tvbuff_t *tvb, int offset, unsigned *lenp)
1220 unsigned len = 0;
1221 uint8_t iByte;
1222 uint32_t iValue;
1224 iByte = tvb_get_uint8(tvb, offset);
1225 iValue = (iByte & 0x7F);
1226 offset++;
1227 len++;
1228 if (!(iByte & 0x80)) {
1229 /* 1 byte value */
1230 *lenp = len;
1231 return iValue;
1233 iByte = tvb_get_uint8(tvb, offset);
1234 iValue = (iValue << 7) | (iByte & 0x7F);
1235 offset++;
1236 len++;
1237 if (!(iByte & 0x80)) {
1238 /* 2 byte value */
1239 *lenp = len;
1240 return iValue;
1242 iByte = tvb_get_uint8(tvb, offset);
1243 iValue = (iValue << 7) | (iByte & 0x7F);
1244 offset++;
1245 len++;
1246 if (!(iByte & 0x80)) {
1247 /* 3 byte value */
1248 *lenp = len;
1249 return iValue;
1251 iByte = tvb_get_uint8(tvb, offset);
1252 iValue = (iValue << 8) | iByte;
1253 len++;
1254 *lenp = len;
1255 return iValue;
1258 static int
1259 // NOLINTNEXTLINE(misc-no-recursion)
1260 dissect_amf3_value_type(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, proto_item *parent_ti)
1262 uint8_t iObjType;
1263 proto_item *ti;
1264 proto_tree *val_tree;
1265 int iValueOffset = offset;
1266 unsigned iValueLength;
1267 uint32_t iIntegerValue;
1268 double iDoubleValue;
1269 unsigned iStringLength;
1270 char *iStringValue;
1271 unsigned iArrayLength;
1272 proto_item *subval_ti;
1273 proto_tree *subval_tree;
1274 unsigned i;
1275 bool iTypeIsDynamic;
1276 unsigned iTraitCount;
1277 proto_item *traits_ti;
1278 proto_tree *traits_tree;
1279 proto_tree *name_tree;
1280 proto_tree *member_tree;
1281 uint8_t *iByteArrayValue;
1283 iObjType = tvb_get_uint8(tvb, offset);
1284 if (parent_ti != NULL)
1285 proto_item_append_text(parent_ti, " %s",
1286 val_to_str_const(iObjType, amf3_type_vals, "Unknown"));
1287 switch (iObjType) {
1289 case AMF3_ARRAY:
1291 * For array types, make the top-level protocol tree
1292 * item a field for that type.
1294 ti = proto_tree_add_item(tree, hf_amf_array, tvb, offset, -1, ENC_NA);
1295 val_tree = proto_item_add_subtree(ti, ett_amf_value);
1296 break;
1298 case AMF3_OBJECT:
1300 * For object types, make the top-level protocol tree
1301 * item a field for that type.
1303 ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA);
1304 val_tree = proto_item_add_subtree(ti, ett_amf_value);
1305 break;
1307 default:
1309 * For all other types, make it just a text item; the
1310 * field for that type will be used for the value.
1312 val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti,
1313 val_to_str_const(iObjType, amf3_type_vals, "Unknown"));
1314 break;
1317 proto_tree_add_uint(val_tree, hf_amf_amf3_type, tvb, iValueOffset, 1, iObjType);
1318 iValueOffset++;
1320 increment_dissection_depth(pinfo);
1321 switch (iObjType) {
1322 case AMF3_UNDEFINED:
1323 case AMF3_NULL:
1324 break;
1325 case AMF3_FALSE:
1326 proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, false);
1327 proto_item_append_text(ti, " false");
1328 break;
1329 case AMF3_TRUE:
1330 proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, true);
1331 proto_item_append_text(ti, " true");
1332 break;
1333 case AMF3_INTEGER:
1334 /* XXX - signed or unsigned? */
1335 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1336 proto_tree_add_uint(val_tree, hf_amf_integer, tvb, iValueOffset, iValueLength, iIntegerValue);
1337 proto_item_append_text(ti, " %u", iIntegerValue);
1338 if (parent_ti != NULL)
1339 proto_item_append_text(parent_ti, " %u", iIntegerValue);
1340 iValueOffset += iValueLength;
1341 break;
1342 case AMF3_DOUBLE:
1343 iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
1344 proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue);
1345 iValueOffset += 8;
1346 proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
1347 if (parent_ti != NULL)
1348 proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue);
1349 break;
1350 case AMF3_STRING:
1351 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1352 if (iIntegerValue & 0x00000001) {
1353 /* the upper 28 bits of the integer value is a string length */
1354 iStringLength = iIntegerValue >> 1;
1355 proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength);
1356 iValueOffset += iValueLength;
1357 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA);
1358 if (iStringLength != 0)
1359 proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
1360 iValueOffset += iStringLength;
1361 proto_item_append_text(ti, " '%s'", iStringValue);
1362 if (parent_ti != NULL)
1363 proto_item_append_text(parent_ti, " '%s'", iStringValue);
1364 } else {
1365 /* the upper 28 bits of the integer value are a string reference index */
1366 proto_tree_add_uint(val_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1367 iValueOffset += iValueLength;
1368 proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
1369 if (parent_ti != NULL)
1370 proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
1372 break;
1373 case AMF3_DATE:
1374 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1375 if (iIntegerValue & 0x00000001) {
1377 * The upper 28 bits of the integer value are
1378 * ignored; what follows is a double
1379 * containing milliseconds since the Epoch.
1381 nstime_t t;
1383 iValueOffset += iValueLength;
1384 iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset);
1385 t.secs = (time_t)(iDoubleValue/1000);
1386 t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000);
1387 proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t);
1388 iValueOffset += 8;
1389 proto_item_append_text(ti, "%s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true));
1390 if (parent_ti != NULL)
1391 proto_item_append_text(parent_ti, "%s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true));
1392 } else {
1393 /* the upper 28 bits of the integer value are an object reference index */
1394 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1395 iValueOffset += iValueLength;
1396 proto_item_append_text(ti, " object reference %u", iIntegerValue >> 1);
1397 if (parent_ti != NULL)
1398 proto_item_append_text(parent_ti, " object reference %u", iIntegerValue >> 1);
1400 break;
1401 case AMF3_ARRAY:
1402 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1403 if (iIntegerValue & 0x00000001) {
1405 * The upper 28 bits of the integer value are
1406 * a count of the number of elements in
1407 * the dense portion of the array.
1409 iArrayLength = iIntegerValue >> 1;
1410 proto_tree_add_uint(val_tree, hf_amf_arraydenselength, tvb, iValueOffset, iValueLength, iArrayLength);
1411 iValueOffset += iValueLength;
1414 * The AMF3 spec bit on the Array type is slightly
1415 * confusingly written, but seems to be saying that
1416 * the associative portion of the array follows the
1417 * size of the dense portion of the array, and the
1418 * dense portion of the array follows the associative
1419 * portion.
1421 * Dissect the associative portion.
1423 for (;;) {
1424 /* Fetch the name */
1425 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1426 if (iIntegerValue & 0x00000001) {
1427 /* the upper 28 bits of the integer value is a string length */
1428 iStringLength = iIntegerValue >> 1;
1429 if (iStringLength == 0) {
1430 /* null name marks the end of the associative part */
1431 proto_tree_add_item(val_tree, hf_amf_end_of_associative_part, tvb, iValueOffset, iValueLength, ENC_NA);
1432 iValueOffset += iValueLength;
1433 break;
1435 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
1436 subval_tree = proto_tree_add_subtree(val_tree, tvb, iValueOffset, iStringLength,
1437 ett_amf_array_element, &subval_ti, iStringValue);
1438 proto_tree_add_uint(subval_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength);
1439 iValueOffset += iValueLength;
1440 proto_tree_add_string(subval_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue);
1441 } else {
1442 /* the upper 28 bits of the integer value are a string reference index */
1443 subval_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength,
1444 ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1);
1445 proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1448 /* Fetch the value */
1449 iObjType = tvb_get_uint8(tvb, offset);
1450 proto_item_append_text(subval_ti, "%s",
1451 val_to_str_const(iObjType, amf3_type_vals, "Unknown"));
1453 iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, subval_tree, subval_ti);
1457 * Dissect the dense portion.
1459 for (i = 0; i < iArrayLength; i++)
1460 iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, val_tree, NULL);
1462 proto_item_set_end(ti, tvb, iValueOffset);
1463 } else {
1464 /* the upper 28 bits of the integer value are an object reference index */
1465 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1466 proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
1467 if (parent_ti != NULL)
1468 proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
1470 break;
1471 case AMF3_OBJECT:
1472 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1473 if (iIntegerValue & 0x00000001) {
1474 if (iIntegerValue & 0x00000002) {
1475 if (iIntegerValue & 0x00000004) {
1477 * U29O-traits-ext; the rest of
1478 * iIntegerValue is not significant,
1479 * and, worse, we have idea what
1480 * follows the class name, or even
1481 * how many bytes follow the class
1482 * name - that's by convention between
1483 * the client and server.
1485 iValueOffset += iValueLength;
1486 } else {
1488 * U29O-traits; the 0x00000008 bit
1489 * specifies whether the type is
1490 * dynamic.
1492 iTypeIsDynamic = (iIntegerValue & 0x00000008) ? true : false;
1493 iTraitCount = iIntegerValue >> 4;
1494 proto_tree_add_uint(val_tree, hf_amf_traitcount, tvb, iValueOffset, iValueLength, iTraitCount);
1495 iValueOffset += iValueLength;
1496 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1497 if (iIntegerValue & 0x00000001) {
1498 /* the upper 28 bits of the integer value is a string length */
1499 iStringLength = iIntegerValue >> 1;
1500 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
1501 traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, -1,
1502 ett_amf_traits, &traits_ti, "Traits for class %s (%u member names)", iStringValue, iTraitCount);
1503 name_tree = proto_tree_add_subtree_format(traits_tree, tvb,
1504 iValueOffset,
1505 iValueLength+iStringLength,
1506 ett_amf_string, NULL, "Class name: %s",
1507 iStringValue);
1508 proto_tree_add_uint(name_tree, hf_amf_classnamelength, tvb, iValueOffset, iValueLength, iStringLength);
1509 iValueOffset += iValueLength;
1510 proto_tree_add_string(name_tree, hf_amf_classname, tvb, iValueOffset, iStringLength, iStringValue);
1511 iValueOffset += iStringLength;
1512 } else {
1513 /* the upper 28 bits of the integer value are a string reference index */
1514 traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength,
1515 ett_amf_traits, &traits_ti, "Traits for class (reference %u for name)", iIntegerValue >> 1);
1516 proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1517 iValueOffset += iValueLength;
1519 for (i = 0; i < iTraitCount; i++) {
1520 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1521 if (iIntegerValue & 0x00000001) {
1522 /* the upper 28 bits of the integer value is a string length */
1523 iStringLength = iIntegerValue >> 1;
1524 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
1525 member_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength+iStringLength,
1526 ett_amf_trait_member, NULL, "Member '%s'", iStringValue);
1527 proto_tree_add_uint(member_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength);
1528 iValueOffset += iValueLength;
1529 proto_tree_add_string(member_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue);
1530 iValueOffset += iStringLength;
1531 } else {
1532 /* the upper 28 bits of the integer value are a string reference index */
1533 proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1534 iValueOffset += iValueLength;
1537 for (i = 0; i < iTraitCount; i++)
1538 iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, traits_tree, NULL);
1539 if (iTypeIsDynamic) {
1540 for (;;) {
1541 /* Fetch the name */
1542 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1543 if (iIntegerValue & 0x00000001) {
1544 /* the upper 28 bits of the integer value is a string length */
1545 iStringLength = iIntegerValue >> 1;
1546 if (iStringLength == 0) {
1547 /* null name marks the end of the associative part */
1548 proto_tree_add_item(traits_tree, hf_amf_end_of_dynamic_members, tvb, iValueOffset, iValueLength, ENC_NA);
1549 iValueOffset += iValueLength;
1550 break;
1552 iStringValue = tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA);
1553 subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, -1,
1554 ett_amf_array_element, &subval_ti, "%s:", iStringValue);
1555 name_tree = proto_tree_add_subtree_format(subval_tree, tvb,
1556 iValueOffset,
1557 iValueLength+iStringLength,
1558 ett_amf_string, NULL, "Member name: %s",
1559 iStringValue);
1560 proto_tree_add_uint(name_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength);
1561 iValueOffset += iValueLength;
1562 proto_tree_add_string(name_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue);
1563 iValueOffset += iStringLength;
1564 } else {
1565 /* the upper 28 bits of the integer value are a string reference index */
1566 subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength,
1567 ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1);
1568 proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1569 iValueOffset += iValueLength;
1572 /* Fetch the value */
1573 iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, subval_tree, subval_ti);
1574 proto_item_set_end(subval_ti, tvb, iValueOffset);
1577 proto_item_set_end(traits_ti, tvb, iValueOffset);
1579 } else {
1581 * U29O-traits-ref; the upper 27 bits of
1582 * the integer value are a traits reference
1583 * index.
1585 proto_tree_add_uint(val_tree, hf_amf_trait_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 2);
1586 iValueOffset += iValueLength;
1588 } else {
1590 * U29O-ref; the upper 28 bits of the integer value
1591 * are an object reference index.
1593 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1594 proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
1595 if (parent_ti != NULL)
1596 proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
1598 break;
1599 case AMF3_XML:
1600 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1601 if (iIntegerValue & 0x00000001) {
1603 * The upper 28 bits of the integer value are
1604 * a count of the number of bytes in the
1605 * XML string.
1607 iStringLength = iIntegerValue >> 1;
1608 proto_tree_add_uint(val_tree, hf_amf_xmllength, tvb, iValueOffset, iValueLength, iStringLength);
1609 iValueOffset += iValueLength;
1610 proto_tree_add_item(val_tree, hf_amf_xml, tvb, iValueOffset, iStringLength, ENC_UTF_8);
1611 } else {
1612 /* the upper 28 bits of the integer value are a string reference index */
1613 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1614 proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
1615 if (parent_ti != NULL)
1616 proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
1618 break;
1619 case AMF3_BYTEARRAY:
1620 iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength);
1621 if (iIntegerValue & 0x00000001) {
1623 * The upper 28 bits of the integer value are
1624 * a count of the number of bytes in the
1625 * byte array.
1627 iArrayLength = iIntegerValue >> 1;
1628 proto_tree_add_uint(val_tree, hf_amf_bytearraylength, tvb, iValueOffset, iValueLength, iArrayLength);
1629 iValueOffset += iValueLength;
1630 iByteArrayValue = (uint8_t *)tvb_memdup(pinfo->pool, tvb, iValueOffset, iArrayLength);
1631 proto_tree_add_bytes(val_tree, hf_amf_bytearray, tvb, iValueOffset, iArrayLength, iByteArrayValue);
1632 proto_item_append_text(ti, " %s", bytes_to_str(pinfo->pool, iByteArrayValue, iArrayLength));
1633 if (parent_ti != NULL)
1634 proto_item_append_text(parent_ti, " %s", bytes_to_str(pinfo->pool, iByteArrayValue, iArrayLength));
1635 } else {
1636 /* the upper 28 bits of the integer value are a object reference index */
1637 proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1);
1638 proto_item_append_text(ti, " reference %u", iIntegerValue >> 1);
1639 if (parent_ti != NULL)
1640 proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1);
1642 break;
1643 default:
1645 * If we can't determine the length, don't carry on;
1646 * just skip to the end of the tvbuff.
1648 iValueOffset = tvb_reported_length(tvb);
1649 break;
1651 decrement_dissection_depth(pinfo);
1652 proto_item_set_end(ti, tvb, iValueOffset);
1653 return iValueOffset;
1656 static int
1657 dissect_rtmpt_body_command(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *rtmpt_tree, bool amf3)
1659 bool amf3_encoding = false;
1661 if (amf3) {
1662 /* Looks like for the AMF3 variants we get a 0 byte here,
1663 * followed by AMF0 encoding - I've never seen actual AMF3
1664 * encoding used, which is completely different. I speculate
1665 * that if the byte is AMF0_AMF3_MARKER then the rest
1666 * will be in AMF3. For now, assume AMF0 only. */
1667 offset++;
1670 while (tvb_reported_length_remaining(tvb, offset) > 0)
1672 if (amf3_encoding)
1673 offset = dissect_amf3_value_type(tvb, pinfo, offset, rtmpt_tree, NULL);
1674 else
1675 offset = dissect_amf0_value_type(tvb, pinfo, offset, rtmpt_tree, &amf3_encoding, NULL);
1677 return offset;
1680 static void
1681 dissect_rtmpt_body_audio(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree)
1683 uint8_t iCtl;
1684 uint8_t iAudioMultitrackCtl;
1685 uint8_t iAudioTrackId;
1686 uint32_t iAudioTrackLength;
1687 proto_item *ai;
1688 proto_tree *at;
1689 bool isAudioMultitrack = false;
1690 bool isOneTrack = false;
1691 bool isManyTracksManyCodecs = false;
1692 bool processAudioBody = true;
1694 iCtl = tvb_get_uint8(tvb, offset);
1695 if ((iCtl & RTMPT_IS_EX_AUDIO_HEADER) == RTMPT_IS_EX_AUDIO_HEADER) {
1696 ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_packet_type, tvb, offset, 1, iCtl,
1697 "Control: 0x%02x (%s)", iCtl,
1698 val_to_str_const((iCtl & 0xf), rtmpt_audio_packet_types, "Reserved audio packet type"));
1699 at = proto_item_add_subtree(ai, ett_rtmpt_audio_control);
1701 proto_tree_add_uint(at, hf_rtmpt_audio_is_ex_header, tvb, offset, 1, iCtl);
1702 proto_tree_add_uint(at, hf_rtmpt_audio_packet_type, tvb, offset, 1, iCtl);
1703 offset += 1;
1705 isAudioMultitrack = (iCtl & 0xf) == RTMPT_IS_AUDIO_MULTITRACK;
1706 if (isAudioMultitrack) {
1707 iAudioMultitrackCtl = tvb_get_uint8(tvb, offset);
1708 ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_multitrack_control, tvb, offset, 1, iAudioMultitrackCtl,
1709 "Audio Multitrack Control: 0x%02x (%s %s)", iAudioMultitrackCtl,
1710 val_to_str_const((iAudioMultitrackCtl & 0x0f), rtmpt_av_multitrack_types, "Reserved av multitrack type"),
1711 val_to_str_const((iAudioMultitrackCtl & 0xf0) >> 4, rtmpt_audio_packet_types, "Reserved audio packet type"));
1712 at = proto_item_add_subtree(ai, ett_rtmpt_audio_multitrack_control);
1713 proto_tree_add_uint(at, hf_rtmpt_audio_multitrack_packet_type, tvb, offset, 1, iAudioMultitrackCtl);
1714 proto_tree_add_uint(at, hf_rtmpt_audio_multitrack_type, tvb, offset, 1, iAudioMultitrackCtl);
1715 offset += 1;
1717 isOneTrack = (iAudioMultitrackCtl & 0x0f) == RTMPT_IS_ONETRACK;
1718 isManyTracksManyCodecs = (iAudioMultitrackCtl & 0x0f) == RTMPT_IS_MANYTRACKSMANYCODECS;
1719 if (!isManyTracksManyCodecs) {
1720 proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII);
1721 offset += 4;
1723 } else {
1724 proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII);
1725 offset += 4;
1728 while(processAudioBody && tvb_reported_length_remaining(tvb, offset) > 0) {
1729 if (isAudioMultitrack) {
1730 iAudioTrackId = tvb_get_uint8(tvb, offset);
1731 ai = proto_tree_add_uint(rtmpt_tree, hf_rtmpt_audio_track_id, tvb, offset, 1, iAudioTrackId);
1732 at = proto_item_add_subtree(ai, ett_rtmpt_audio_multitrack_track);
1733 offset += 1;
1734 if (isManyTracksManyCodecs) {
1735 proto_tree_add_item(at, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII);
1736 offset += 4;
1738 if (!isOneTrack) {
1739 iAudioTrackLength = tvb_get_uint24(tvb, offset, ENC_BIG_ENDIAN);
1740 proto_tree_add_uint(at, hf_rtmpt_audio_track_length, tvb, offset, 3, iAudioTrackLength);
1741 offset += 3;
1742 proto_tree_add_item(at, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA);
1743 offset += iAudioTrackLength;
1744 } else {
1745 proto_tree_add_item(at, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA);
1746 processAudioBody = false;
1748 } else {
1749 proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA);
1750 processAudioBody = false;
1753 } else {
1754 ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_control, tvb, offset, 1, iCtl,
1755 "Control: 0x%02x (%s %s %s %s)", iCtl,
1756 val_to_str_const((iCtl & 0xf0) >> 4, rtmpt_audio_codecs, "Unknown codec"),
1757 val_to_str_const((iCtl & 0x0c) >> 2, rtmpt_audio_rates, "Unknown rate"),
1758 val_to_str_const((iCtl & 0x02) >> 1, rtmpt_audio_sizes, "Unknown sample size"),
1759 val_to_str_const(iCtl & 0x01, rtmpt_audio_types, "Unknown channel count"));
1761 at = proto_item_add_subtree(ai, ett_rtmpt_audio_control);
1762 proto_tree_add_uint(at, hf_rtmpt_audio_format, tvb, offset, 1, iCtl);
1763 proto_tree_add_uint(at, hf_rtmpt_audio_rate, tvb, offset, 1, iCtl);
1764 proto_tree_add_uint(at, hf_rtmpt_audio_size, tvb, offset, 1, iCtl);
1765 proto_tree_add_uint(at, hf_rtmpt_audio_type, tvb, offset, 1, iCtl);
1766 proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_data, tvb, offset + 1, -1, ENC_NA);
1770 static void
1771 dissect_rtmpt_body_video(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree)
1773 uint8_t iCtl;
1774 uint8_t iMultitrackCtl;
1775 uint8_t iVideoFrameType;
1776 uint8_t iVideoPacketType;
1777 uint8_t iAvMultitrackType;
1778 uint32_t iVideoTrackLength;
1779 proto_item *vi;
1780 proto_tree *vt;
1781 bool isVideoMultitrack = false;
1782 bool isOneTrack = false;
1783 bool isManyTracksManyCodecs = false;
1784 bool processVideoBody = true;
1786 iCtl = tvb_get_uint8(tvb, offset);
1787 iVideoFrameType = (iCtl & 0x70) >> 4;
1790 * https://veovera.org/docs/enhanced/enhanced-rtmp-v2.pdf
1792 if (iCtl & RTMPT_IS_EX_VIDEO_HEADER) {
1793 iVideoPacketType = iCtl & 0x0f;
1794 isVideoMultitrack = iVideoPacketType == RTMPT_IS_VIDEO_MULTITRACK;
1796 vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_control, tvb, offset, 1, iCtl,
1797 "Control: 0x%02x (%s %s)", iCtl,
1798 val_to_str_const(iVideoFrameType, rtmpt_video_types, "Reserved frame type"),
1799 val_to_str_const(iVideoPacketType, rtmpt_video_packet_types, "Reserved packet type"));
1800 vt = proto_item_add_subtree(vi, ett_rtmpt_video_control);
1801 proto_tree_add_item(vt, hf_rtmpt_video_is_ex_header, tvb, offset, 1, ENC_BIG_ENDIAN);
1802 proto_tree_add_item(vt, hf_rtmpt_video_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1803 proto_tree_add_item(vt, hf_rtmpt_video_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1804 offset += 1;
1806 if (iVideoPacketType != RTMPT_IS_PACKET_TYPE_METADATA && iVideoFrameType == RTMPT_IS_FRAME_TYPE_COMMAND) {
1807 proto_tree_add_item(vt, hf_rtmpt_video_command, tvb, offset, 1, ENC_BIG_ENDIAN);
1808 offset += 1;
1809 processVideoBody = false;
1810 } else if (isVideoMultitrack) {
1811 iMultitrackCtl = tvb_get_uint8(tvb, offset);
1812 iAvMultitrackType = (iMultitrackCtl >> 4) & 0xf;
1813 vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_multitrack_control, tvb, offset, 1, iMultitrackCtl,
1814 "Video Multitrack Control: 0x%02x (%s %s)", iMultitrackCtl,
1815 val_to_str_const(iAvMultitrackType, rtmpt_av_multitrack_types, "Reserved av multitrack type"),
1816 val_to_str_const(iMultitrackCtl & 0xf, rtmpt_video_packet_types, "Reserved video packet type"));
1817 vt = proto_item_add_subtree(vi, ett_rtmpt_video_multitrack_control);
1818 proto_tree_add_item(vt, hf_rtmpt_video_multitrack_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1819 proto_tree_add_item(vt, hf_rtmpt_video_multitrack_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1820 offset += 1;
1822 isOneTrack = (iAvMultitrackType & 0x0f) == RTMPT_IS_ONETRACK;
1823 isManyTracksManyCodecs = (iAvMultitrackType & 0x0f) == RTMPT_IS_MANYTRACKSMANYCODECS;
1824 if (!isManyTracksManyCodecs) {
1825 proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII);
1826 offset += 4;
1828 } else {
1829 proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII);
1830 offset += 4;
1833 while (processVideoBody && tvb_reported_length_remaining(tvb, offset) > 0){
1834 if (isVideoMultitrack) {
1835 vi = proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_track_id, tvb, offset, 1, ENC_BIG_ENDIAN);
1836 vt = proto_item_add_subtree(vi, ett_rtmpt_video_multitrack_track);
1837 if (isManyTracksManyCodecs) {
1838 proto_tree_add_item(vt, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII);
1839 offset += 4;
1841 offset += 1;
1842 if (!isOneTrack) {
1843 iVideoTrackLength = tvb_get_uint24(tvb, offset, ENC_BIG_ENDIAN);
1844 proto_tree_add_item(vt, hf_rtmpt_video_track_length, tvb, offset, 3, ENC_BIG_ENDIAN);
1845 offset += 3;
1846 proto_tree_add_item(vt, hf_rtmpt_video_data, tvb, offset, iVideoTrackLength, ENC_NA);
1847 offset += iVideoTrackLength;
1848 } else {
1849 proto_tree_add_item(vt, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA);
1850 processVideoBody = false;
1852 } else {
1853 proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA);
1854 processVideoBody = false;
1857 } else {
1858 vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_control, tvb, offset, 1, iCtl,
1859 "Control: 0x%02x (%s %s)", iCtl,
1860 val_to_str_const(iVideoFrameType, rtmpt_video_types, "Reserved frame type"),
1861 val_to_str_const(iCtl & 0x0f, rtmpt_video_codecs, "Unknown codec"));
1863 vt = proto_item_add_subtree(vi, ett_rtmpt_video_control);
1864 proto_tree_add_item(vt, hf_rtmpt_video_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1865 proto_tree_add_item(vt, hf_rtmpt_video_format, tvb, offset, 1, ENC_BIG_ENDIAN);
1866 offset += 1;
1868 if (iVideoFrameType == RTMPT_IS_FRAME_TYPE_COMMAND) {
1869 proto_tree_add_item(vt, hf_rtmpt_video_command, tvb, offset, 1, ENC_BIG_ENDIAN);
1870 } else {
1871 proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA);
1876 static void
1877 dissect_rtmpt_body_aggregate(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *rtmpt_tree)
1879 proto_tree *tag_tree;
1881 proto_tree *data_tree;
1883 while (tvb_reported_length_remaining(tvb, offset) > 0) {
1884 uint8_t iTagType;
1885 unsigned iDataSize;
1887 iTagType = tvb_get_uint8(tvb, offset + 0);
1888 iDataSize = tvb_get_ntoh24(tvb, offset + 1);
1890 tag_tree = proto_tree_add_subtree(rtmpt_tree, tvb, offset, 11+iDataSize+4, ett_rtmpt_tag, NULL,
1891 val_to_str_const(iTagType, rtmpt_tag_vals, "Unknown Tag"));
1892 proto_tree_add_item(tag_tree, hf_rtmpt_tag_type, tvb, offset+0, 1, ENC_BIG_ENDIAN);
1893 proto_tree_add_item(tag_tree, hf_rtmpt_tag_datasize, tvb, offset+1, 3, ENC_BIG_ENDIAN);
1894 proto_tree_add_item(tag_tree, hf_rtmpt_tag_timestamp, tvb, offset+4, 3, ENC_BIG_ENDIAN);
1895 proto_tree_add_item(tag_tree, hf_rtmpt_tag_ets, tvb, offset+7, 1, ENC_BIG_ENDIAN);
1896 proto_tree_add_item(tag_tree, hf_rtmpt_tag_streamid, tvb, offset+8, 3, ENC_BIG_ENDIAN);
1898 data_tree = proto_tree_add_subtree(tag_tree, tvb, offset+11, iDataSize, ett_rtmpt_tag_data, NULL, "Data");
1900 switch (iTagType) {
1901 case 8:
1902 dissect_rtmpt_body_audio(tvb, offset + 11, data_tree);
1903 break;
1904 case 9:
1905 dissect_rtmpt_body_video(tvb, offset + 11, data_tree);
1906 break;
1907 case 18:
1908 dissect_rtmpt_body_command(tvb, pinfo, offset + 11, data_tree, false);
1909 break;
1910 default:
1911 break;
1914 proto_tree_add_item(tag_tree, hf_rtmpt_tag_tagsize, tvb, offset+11+iDataSize, 4, ENC_BIG_ENDIAN);
1915 offset += 11 + iDataSize + 4;
1919 /* The main dissector for unchunked packets */
1921 static void
1922 dissect_rtmpt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, rtmpt_conv_t *rconv, int cdir, rtmpt_packet_t *tp)
1924 int offset = 0;
1926 char *sDesc = NULL;
1927 bool deschasopcode = false;
1928 bool haveETS = false;
1929 uint32_t iBodyOffset = 0;
1930 uint32_t iBodyRemain = 0;
1932 col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP");
1934 ws_debug("Dissect: frame=%u visited=%d len=%d tree=%p",
1935 pinfo->num, pinfo->fd->visited,
1936 tvb_reported_length_remaining(tvb, offset), tree);
1938 /* Clear any previous data in Info column (RTMP packets are protected by a "fence") */
1939 col_clear(pinfo->cinfo, COL_INFO);
1941 if (tvb_reported_length_remaining(tvb, offset) < 1) return;
1943 if (tp->id <= RTMPT_ID_MAX) {
1944 if (tp->fmt < 3
1945 && tvb_reported_length_remaining(tvb, offset) >= tp->bhlen+3
1946 && tvb_get_ntoh24(tvb, offset+tp->bhlen) == 0xffffff) {
1947 haveETS = true;
1950 iBodyOffset = offset + tp->bhlen + tp->mhlen;
1951 iBodyRemain = tvb_reported_length_remaining(tvb, iBodyOffset);
1953 if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE && tp->len >= 4 && iBodyRemain >= 4) {
1954 int32_t newchunksize = tvb_get_ntohl(tvb, iBodyOffset);
1955 if (newchunksize > 0) {
1956 wmem_tree_insert32(rconv->chunksize[cdir], tp->lastseq, GINT_TO_POINTER(newchunksize));
1960 if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 ||
1961 tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
1962 uint32_t soff = 0;
1963 if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) {
1964 soff = 1;
1966 tp->txid = rtmpt_get_amf_txid(tvb, iBodyOffset+soff, tree);
1967 if (tp->txid != 0 && !PINFO_FD_VISITED(pinfo)) {
1968 ws_debug("got txid=%d", tp->txid);
1969 wmem_tree_insert32(rconv->txids[cdir], tp->txid, GINT_TO_POINTER(pinfo->num));
1972 } else if (tp->id == RTMPT_TYPE_HANDSHAKE_2 || tp->id == RTMPT_TYPE_HANDSHAKE_3) {
1973 uint32_t newchunksize = RTMPT_INITIAL_CHUNK_SIZE;
1974 wmem_tree_insert32(rconv->chunksize[cdir], tp->lastseq, GINT_TO_POINTER(newchunksize));
1977 if (tp->id <= RTMPT_ID_MAX)
1979 sDesc = rtmpt_get_packet_desc(tvb, iBodyOffset, tree, iBodyRemain, rconv, cdir, tp, &deschasopcode);
1982 if (tp->id>RTMPT_ID_MAX) {
1983 col_append_sep_str(pinfo->cinfo, COL_INFO, "|",
1984 val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
1985 col_set_fence(pinfo->cinfo, COL_INFO);
1986 } else if (sDesc) {
1987 col_append_sep_str(pinfo->cinfo, COL_INFO, "|", sDesc);
1988 col_set_fence(pinfo->cinfo, COL_INFO);
1989 } else {
1990 col_append_sep_str(pinfo->cinfo, COL_INFO, "|",
1991 val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"));
1992 col_set_fence(pinfo->cinfo, COL_INFO);
1995 if (tree)
1997 proto_tree *rtmpt_tree = NULL;
1998 proto_tree *rtmptroot_tree = NULL;
1999 proto_item *ti;
2000 ti = proto_tree_add_item(tree, proto_rtmpt, tvb, offset, -1, ENC_NA);
2002 if (tp->id > RTMPT_ID_MAX) {
2003 /* Dissect handshake */
2004 proto_item_append_text(ti, " (%s)",
2005 val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
2006 rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt);
2007 rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_handshake, NULL,
2008 val_to_str(tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"));
2010 if (tp->id == RTMPT_TYPE_HANDSHAKE_1)
2012 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c0, tvb, 0, 1, ENC_NA);
2013 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c1, tvb, 1, 1536, ENC_NA);
2015 else if (tp->id == RTMPT_TYPE_HANDSHAKE_2)
2017 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s0, tvb, 0, 1, ENC_NA);
2018 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s1, tvb, 1, 1536, ENC_NA);
2019 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s2, tvb, 1537, 1536, ENC_NA);
2021 else if (tp->id == RTMPT_TYPE_HANDSHAKE_3)
2023 proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c2, tvb, 0, 1536, ENC_NA);
2026 return;
2029 if (sDesc && deschasopcode) {
2030 proto_item_append_text(ti, " (%s)", sDesc);
2031 } else if (sDesc) {
2032 proto_item_append_text(ti, " (%s %s)",
2033 val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"), sDesc);
2034 } else {
2035 proto_item_append_text(ti, " (%s)",
2036 val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"));
2038 rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt);
2040 /* Function call/response matching */
2041 if (tp->otherframe != 0) {
2042 proto_tree_add_uint(rtmptroot_tree,
2043 tp->isresponse ? hf_rtmpt_function_response : hf_rtmpt_function_call,
2044 tvb, offset, tp->bhlen+tp->mhlen+tp->len,
2045 tp->otherframe);
2048 /* Dissect header fields */
2049 rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, tp->bhlen+tp->mhlen, ett_rtmpt_header, NULL, RTMPT_TEXT_RTMP_HEADER);
2050 /* proto_item_append_text(ti, " (%s)", val_to_str(tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); */
2052 if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_format, tvb, offset + 0, 1, ENC_BIG_ENDIAN);
2053 if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_csid, tvb, offset + 0, tp->bhlen, ENC_BIG_ENDIAN);
2054 if (tp->fmt <= 2) {
2055 if (tp->fmt > 0) {
2056 proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp_delta, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN);
2057 } else {
2058 proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN);
2060 if (haveETS) {
2061 proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_ets, tvb, offset + tp->bhlen + tp->mhlen - 4, 4, ENC_BIG_ENDIAN);
2064 if ((tp->fmt>0 && !haveETS) || tp->fmt == 3) {
2065 proto_tree_add_uint_format_value(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 0, tp->ts, "%d (calculated)", tp->ts);
2067 if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_body_size, tvb, offset + tp->bhlen + 3, 3, ENC_BIG_ENDIAN);
2068 if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_typeid, tvb, offset + tp->bhlen + 6, 1, ENC_BIG_ENDIAN);
2069 if (tp->fmt <= 0) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_streamid, tvb, offset + tp->bhlen + 7, 4, ENC_LITTLE_ENDIAN);
2071 /* Dissect body */
2072 if (tp->len == 0) return;
2073 offset = iBodyOffset;
2075 rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_body, NULL, RTMPT_TEXT_RTMP_BODY);
2077 switch (tp->cmd) {
2078 case RTMPT_TYPE_CHUNK_SIZE:
2079 case RTMPT_TYPE_ABORT_MESSAGE:
2080 case RTMPT_TYPE_ACKNOWLEDGEMENT:
2081 case RTMPT_TYPE_UCM:
2082 case RTMPT_TYPE_WINDOW:
2083 case RTMPT_TYPE_PEER_BANDWIDTH:
2084 dissect_rtmpt_body_scm(tvb, offset, rtmpt_tree, tp->cmd);
2085 break;
2086 case RTMPT_TYPE_COMMAND_AMF0:
2087 case RTMPT_TYPE_DATA_AMF0:
2088 dissect_rtmpt_body_command(tvb, pinfo, offset, rtmpt_tree, false);
2089 break;
2090 case RTMPT_TYPE_COMMAND_AMF3:
2091 case RTMPT_TYPE_DATA_AMF3:
2092 dissect_rtmpt_body_command(tvb, pinfo, offset, rtmpt_tree, true);
2093 break;
2094 case RTMPT_TYPE_AUDIO_DATA:
2095 dissect_rtmpt_body_audio(tvb, offset, rtmpt_tree);
2096 break;
2097 case RTMPT_TYPE_VIDEO_DATA:
2098 dissect_rtmpt_body_video(tvb, offset, rtmpt_tree);
2099 break;
2100 case RTMPT_TYPE_AGGREGATE:
2101 dissect_rtmpt_body_aggregate(tvb, pinfo, offset, rtmpt_tree);
2102 break;
2107 /* Unchunk a data stream into individual RTMP packets */
2109 static void
2110 dissect_rtmpt_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, rtmpt_conv_t *rconv, int cdir, uint32_t seq, uint32_t lastackseq)
2112 int offset = 0;
2113 int remain;
2114 int want;
2116 uint8_t header_type;
2117 int basic_hlen;
2118 int message_hlen;
2120 uint32_t id;
2121 uint32_t ts = 0;
2122 uint32_t tsd = 0;
2123 int body_len;
2124 uint8_t cmd;
2125 uint32_t src;
2126 int chunk_size;
2128 rtmpt_frag_t *tf;
2129 rtmpt_id_t *ti;
2130 rtmpt_packet_t *tp;
2131 tvbuff_t *pktbuf;
2133 remain = tvb_reported_length(tvb);
2134 if (!remain)
2135 return;
2137 ws_debug("Segment: cdir=%d seq=%d-%d", cdir, seq, seq+remain-1);
2139 if (pinfo->fd->visited) {
2140 /* Already done the work, so just dump the existing state */
2141 /* XXX: If there's bogus sequence numbers and the
2142 * tcp.analyze_sequence_numbers pref is true, we can't actually
2143 * assume that we processed this frame the first time around,
2144 * since the TCP dissector might not have given it to us.
2146 wmem_stack_t *packets;
2148 /* List all RTMP packets terminating in this TCP segment, from end to beginning */
2150 packets = wmem_stack_new(pinfo->pool);
2151 wmem_stack_push(packets, 0);
2153 tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], seq+remain-1);
2154 while (tp && GE_SEQ(tp->lastseq, seq)) {
2155 /* Sequence numbers can wrap around (especially with
2156 * tcp.relative_sequence_numbers false), so use the
2157 * wrap around aware comparison from packet-tcp.h
2159 wmem_stack_push(packets, tp);
2160 if (tp->seq == 0) {
2161 // reached first segment.
2162 /* XXX: Assuming tcp.relative_sequence_numbers
2163 * is true, that is, since on TCP we just
2164 * reuse the sequence numbers from tcpinfo.
2166 break;
2168 if (tp->seq > tp->lastseq) {
2169 /* XXX: There are some problems with sequence
2170 * numbers that wraparound in the middle of
2171 * a segment and using wmem_tree_lookup32_le
2172 * below. Break out here to guarantee that there
2173 * is a limit to the tree lookups and we don't
2174 * have infinite loops. Really a lot of this
2175 * code should be rewritten to deal with
2176 * sequence numbers that wrap around (especially
2177 * (SYN packets with altered sequence numbers
2178 * and out of order packets.)
2180 break;
2182 tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], tp->seq-1);
2185 /* Dissect the generated list in reverse order (beginning to end) */
2187 while ((tp=(rtmpt_packet_t *)wmem_stack_pop(packets)) != NULL) {
2188 if (tp->resident) {
2189 pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
2190 add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
2191 } else {
2192 pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have);
2194 dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
2197 return;
2200 while (remain>0) {
2201 tf = NULL;
2202 ti = NULL;
2203 tp = NULL;
2205 /* Check for outstanding fragmented headers/chunks first */
2207 if (offset == 0) {
2208 tf = (rtmpt_frag_t *)wmem_tree_lookup32_le(rconv->frags[cdir], seq+offset-1);
2210 if (tf) {
2211 /* May need to reassemble cross-TCP-segment fragments */
2212 ws_noisy(" tf seq=%d lseq=%d h=%d l=%d", tf->seq, tf->lastseq, tf->have, tf->len);
2213 if (tf->have >= tf->len || seq+offset < tf->seq || seq+offset > tf->lastseq+tf->len-tf->have) {
2214 tf = NULL;
2215 } else if (!tf->ishdr) {
2216 ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], tf->saved.id);
2217 if (ti) {
2218 tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1);
2220 if (tp && tp->chunkwant) {
2221 goto unchunk;
2223 tf = NULL;
2224 ti = NULL;
2225 tp = NULL;
2228 if (tf) {
2229 /* The preceding segment contained an incomplete chunk header */
2231 want = tf->len - tf->have;
2232 if (remain<want)
2233 want = remain;
2235 tvb_memcpy(tvb, tf->saved.d+tf->have, offset, want);
2237 id = tf->saved.d[0];
2238 header_type = (id>>6) & 3;
2239 basic_hlen = rtmpt_basic_header_length(id);
2241 if ((header_type < 3) && (tf->have < (basic_hlen+3)) && (tf->have+want >= (basic_hlen+3))) {
2242 if (pntoh24(tf->saved.d+basic_hlen) == 0xffffff) {
2243 tf->len += 4;
2247 tf->have += want;
2248 tf->lastseq = seq+want-1;
2249 remain -= want;
2250 offset += want;
2252 if (tf->have < tf->len) {
2253 return;
2259 if (!tf) {
2260 /* No preceding data, get header data starting at current position */
2261 id = tvb_get_uint8(tvb, offset);
2263 if (id == RTMPT_MAGIC && seq+offset == RTMPT_HANDSHAKE_OFFSET_1) {
2264 header_type = 4;
2265 basic_hlen = 1;
2266 message_hlen = 0;
2267 id = lastackseq == 1 ? RTMPT_TYPE_HANDSHAKE_1 : RTMPT_TYPE_HANDSHAKE_2;
2268 } else if (seq+offset == RTMPT_HANDSHAKE_OFFSET_2) {
2269 header_type = 4;
2270 basic_hlen = 0;
2271 message_hlen = 0;
2272 id = RTMPT_TYPE_HANDSHAKE_3;
2273 } else {
2274 header_type = (id>>6) & 3;
2275 basic_hlen = rtmpt_basic_header_length(id);
2276 message_hlen = rtmpt_message_header_length(id);
2278 if ((header_type < 3) && (remain >= (basic_hlen+3))) {
2279 if (tvb_get_ntoh24(tvb, offset+basic_hlen) == 0xffffff) {
2280 message_hlen += 4;
2284 if (remain < (basic_hlen+message_hlen)) {
2285 /* Ran out of packet mid-header, save and try again next time */
2286 tf = wmem_new(wmem_file_scope(), rtmpt_frag_t);
2287 tf->ishdr = 1;
2288 tf->seq = seq + offset;
2289 tf->lastseq = tf->seq + remain - 1;
2290 tf->len = basic_hlen + message_hlen;
2291 tvb_memcpy(tvb, tf->saved.d, offset, remain);
2292 tf->have = remain;
2293 wmem_tree_insert32(rconv->frags[cdir], seq+offset, tf);
2294 return;
2297 id = id & 0x3f;
2298 if (id == 0)
2299 id = tvb_get_uint8(tvb, offset+1) + 64;
2300 else if (id == 1)
2301 id = tvb_get_letohs(tvb, offset+1) + 64;
2304 } else {
2305 /* Use reassembled header data */
2306 id = tf->saved.d[0];
2307 header_type = (id>>6) & 3;
2308 basic_hlen = rtmpt_basic_header_length(id);
2309 message_hlen = tf->len - basic_hlen;
2311 id = id & 0x3f;
2312 if (id == 0)
2313 id = tf->saved.d[1] + 64;
2314 else if (id == 1)
2315 id = pletoh16(tf->saved.d+1) + 64;
2318 /* Calculate header values, defaulting from previous packets with same id */
2320 if (id <= RTMPT_ID_MAX)
2321 ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], id);
2322 if (ti)
2323 tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1);
2325 if (header_type == 0)
2326 src = tf ? pntoh32(tf->saved.d+basic_hlen+7) : tvb_get_ntohl(tvb, offset+basic_hlen+7);
2327 else if (ti)
2328 src = ti->src;
2329 else src = 0;
2331 if (header_type < 2)
2332 cmd = tf ? tf->saved.d[basic_hlen+6] : tvb_get_uint8(tvb, offset+basic_hlen+6);
2333 else if (ti)
2334 cmd = ti->cmd;
2335 else
2336 cmd = 0;
2338 /* Calculate chunk_size now as a last-resort default payload length */
2339 if (id > RTMPT_ID_MAX) {
2340 if (id == RTMPT_TYPE_HANDSHAKE_1)
2341 chunk_size = body_len = 1536;
2342 else if (id == RTMPT_TYPE_HANDSHAKE_2)
2343 chunk_size = body_len = 3072;
2344 else /* if (id == RTMPT_TYPE_HANDSHAKE_3) */
2345 chunk_size = body_len = 1536;
2346 } else {
2347 chunk_size = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->chunksize[cdir], seq+offset-1));
2348 if (!chunk_size) {
2349 chunk_size = ((int)rtmpt_default_chunk_size > 0) ? rtmpt_default_chunk_size : INT_MAX;
2352 if (header_type < 2)
2353 body_len = tf ? pntoh24(tf->saved.d+basic_hlen+3) : tvb_get_ntoh24(tvb, offset+basic_hlen+3);
2354 else if (ti)
2355 body_len = ti->len;
2356 else
2357 body_len = chunk_size;
2360 if (!ti || !tp || header_type<3 || tp->have == tp->want || tp->chunkhave != tp->chunkwant) {
2361 /* Start a new packet if:
2362 * no previous packet with same id
2363 * not a short 1-byte header
2364 * previous packet with same id was complete
2365 * previous incomplete chunk not handled by fragment handler
2367 ws_noisy("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",
2368 cdir, seq+offset,
2369 ti, tp, header_type, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size);
2371 if (!ti) {
2372 ti = wmem_new(wmem_file_scope(), rtmpt_id_t);
2373 ti->packets = wmem_tree_new(wmem_file_scope());
2374 ti->ts = 0;
2375 ti->tsd = 0;
2376 wmem_tree_insert32(rconv->ids[cdir], id, ti);
2379 if (header_type == 0) {
2380 ts = tf ? pntoh24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen);
2381 if (ts == 0xffffff) {
2382 ts = tf ? pntoh32(tf->saved.d+basic_hlen+11) : tvb_get_ntohl(tvb, offset+basic_hlen+11);
2384 tsd = ts - ti->ts;
2385 } else if (header_type < 3) {
2386 tsd = tf ? pntoh24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen);
2387 if (tsd == 0xffffff) {
2388 ts = tf ? pntoh32(tf->saved.d+basic_hlen+message_hlen-4) : tvb_get_ntohl(tvb, offset+basic_hlen+message_hlen-4);
2389 tsd = ti->tsd; /* questionable */
2390 } else {
2391 ts = ti->ts + tsd;
2393 } else {
2394 ts = ti->ts + ti->tsd;
2395 tsd = ti->tsd;
2398 /* create a new packet structure */
2399 tp = wmem_new(wmem_file_scope(), rtmpt_packet_t);
2400 tp->seq = tp->lastseq = tf ? tf->seq : seq+offset;
2401 tp->have = 0;
2402 tp->want = basic_hlen + message_hlen + body_len;
2403 tp->chunkwant = 0;
2404 tp->chunkhave = 0;
2405 tp->bhlen = basic_hlen;
2406 tp->mhlen = message_hlen;
2407 tp->fmt = header_type;
2408 tp->id = id;
2409 tp->ts = ts;
2410 tp->len = body_len;
2411 if (id > RTMPT_ID_MAX)
2412 tp->cmd = id;
2413 else
2414 tp->cmd = cmd & 0x7f;
2415 tp->src = src;
2416 tp->txid = 0;
2417 tp->isresponse = false;
2418 tp->otherframe = 0;
2420 tp->frames = wmem_list_new(wmem_file_scope());
2421 wmem_list_prepend(tp->frames, GUINT_TO_POINTER(pinfo->num));
2423 /* Save the header information for future defaulting needs */
2424 ti->ts = ts;
2425 ti->tsd = tsd;
2426 ti->len = body_len;
2427 ti->cmd = cmd;
2428 ti->src = src;
2430 /* store against the id only until unchunking is complete */
2431 wmem_tree_insert32(ti->packets, tp->seq, tp);
2433 if (!tf && body_len <= chunk_size && tp->want <= remain) {
2434 /* The easy case - a whole packet contiguous and fully within this segment */
2435 tp->resident = false;
2436 tp->data.offset = offset;
2437 tp->lastseq = seq+offset+tp->want-1;
2438 tp->have = tp->want;
2440 wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
2442 pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have);
2443 dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
2445 offset += tp->want;
2446 remain -= tp->want;
2447 continue;
2449 } else {
2450 /* Some more reassembly required */
2451 tp->resident = true;
2452 /* tp->want is how much data we think we want.
2453 * If it's a really big number, we don't want
2454 * to allocate it all at once, due to memory
2455 * exhaustion on fuzzed data (#6898).
2456 * RTMPT_INIT_ALLOC_SIZE should always be larger
2457 * than basic_hlen + message_hlen.
2459 tp->alloc = MIN(tp->want, RTMPT_INIT_ALLOC_SIZE);
2460 tp->data.p = (uint8_t *)wmem_alloc(wmem_file_scope(), tp->alloc);
2462 if (tf && tf->ishdr) {
2463 memcpy(tp->data.p, tf->saved.d, tf->len);
2464 } else {
2465 tvb_memcpy(tvb, tp->data.p, offset, basic_hlen+message_hlen);
2466 offset += basic_hlen + message_hlen;
2467 remain -= basic_hlen + message_hlen;
2470 tp->lastseq = seq+offset-1;
2471 tp->have = basic_hlen + message_hlen;
2473 if (tp->have == tp->want) {
2474 wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
2476 pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
2477 add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
2478 dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
2479 continue;
2482 tp->chunkwant = chunk_size;
2483 if (tp->chunkwant > tp->want-tp->have)
2484 tp->chunkwant = tp->want - tp->have;
2486 } else {
2487 if (header_type == 3 && tp->resident && tp->have > tp->bhlen + 3
2488 && pntoh24(tp->data.p+tp->bhlen) == 0xffffff) {
2489 /* Header type 3 resends the extended time stamp if the last message on the chunk
2490 * stream had an extended timestamp.
2491 * See: https://gitlab.com/wireshark/wireshark/-/issues/15718
2493 message_hlen += 4;
2495 ws_noisy("Old packet cdir=%d seq=%d ti=%p tp=%p header_len=%d id=%d tph=%d tpw=%d len=%d cs=%d",
2496 cdir, seq+offset,
2497 ti, tp, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size);
2499 tp->chunkwant = chunk_size;
2500 if (tp->chunkwant > tp->want-tp->have)
2501 tp->chunkwant = tp->want - tp->have;
2503 offset += basic_hlen + message_hlen;
2504 remain -= basic_hlen + message_hlen;
2507 tf = NULL;
2509 /* Last case to deal with is unchunking the packet body */
2510 unchunk:
2511 want = tp->chunkwant - tp->chunkhave;
2512 if (want > remain)
2513 want = remain;
2514 ws_noisy(" cw=%d ch=%d r=%d w=%d", tp->chunkwant, tp->chunkhave, remain, want);
2516 /* message length is a 3 byte number, never overflows an int */
2517 if (tp->alloc < tp->have + want) {
2518 /* tp->want - how much data is supposedly in the entire
2519 * unchunked packet, according to the header. Up to a
2520 * 24-bit integer. Actually allocating this amount can
2521 * cause memory exhaustion on fuzzed data.
2522 * want - how much more data we are going to copy from the
2523 * current tvbuff. No more than necessary to finish the
2524 * current chunk, or what's actually in the tvbuff.
2525 * Allocating this amount shouldn't cause memory exhaustion
2526 * because it's present in the frame.
2528 * We should have calculated those values so that the
2529 * following assertion is true.
2531 DISSECTOR_ASSERT_CMPINT(tp->have + want, <=, tp->want);
2532 tp->alloc = MAX(tp->alloc*2, tp->have + want);
2533 tp->alloc = MIN(tp->alloc, tp->want);
2534 tp->data.p = wmem_realloc(wmem_file_scope(), tp->data.p, tp->alloc);
2536 tvb_memcpy(tvb, tp->data.p+tp->have, offset, want);
2537 wmem_list_frame_t *frame_head = wmem_list_head(tp->frames);
2538 if (wmem_list_frame_data(frame_head) != GUINT_TO_POINTER(pinfo->num)) {
2539 wmem_list_prepend(tp->frames, GUINT_TO_POINTER(pinfo->num));
2542 if (tf) {
2543 tf->have += want;
2544 tf->lastseq = seq+offset+want-1;
2546 tp->lastseq = seq+offset+want-1;
2547 tp->have += want;
2548 tp->chunkhave += want;
2550 offset += want;
2551 remain -= want;
2553 if (tp->chunkhave == tp->chunkwant) {
2554 /* Chunk is complete - wait for next header */
2555 tp->chunkhave = 0;
2556 tp->chunkwant = 0;
2559 if (tp->have == tp->want) {
2560 /* Whole packet is complete */
2561 wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp);
2562 wmem_list_foreach(tp->frames, rtmpt_packet_mark_depended, pinfo->fd);
2564 pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have);
2565 add_new_data_source(pinfo, pktbuf, "Unchunked RTMP");
2566 dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp);
2567 } else if (tp->chunkhave < tp->chunkwant) {
2568 /* Chunk is split across segment boundary */
2569 rtmpt_frag_t *tf2 = wmem_new(wmem_file_scope(), rtmpt_frag_t);
2570 tf2->ishdr = 0;
2571 tf2->seq = seq + offset - want;
2572 tf2->lastseq = tf2->seq + remain - 1 + want;
2573 tf2->have = tp->chunkhave;
2574 tf2->len = tp->chunkwant;
2575 tf2->saved.id = tp->id;
2576 ws_noisy(" inserting tf @ %d", seq+offset-want-1);
2577 wmem_tree_insert32(rconv->frags[cdir], seq+offset-want-1, tf2);
2582 static rtmpt_conv_t *
2583 rtmpt_init_rconv(conversation_t *conv)
2585 rtmpt_conv_t *rconv = wmem_new(wmem_file_scope(), rtmpt_conv_t);
2586 conversation_add_proto_data(conv, proto_rtmpt, rconv);
2588 rconv->seqs[0] = wmem_tree_new(wmem_file_scope());
2589 rconv->seqs[1] = wmem_tree_new(wmem_file_scope());
2590 rconv->frags[0] = wmem_tree_new(wmem_file_scope());
2591 rconv->frags[1] = wmem_tree_new(wmem_file_scope());
2592 rconv->ids[0] = wmem_tree_new(wmem_file_scope());
2593 rconv->ids[1] = wmem_tree_new(wmem_file_scope());
2594 rconv->packets[0] = wmem_tree_new(wmem_file_scope());
2595 rconv->packets[1] = wmem_tree_new(wmem_file_scope());
2596 rconv->chunksize[0] = wmem_tree_new(wmem_file_scope());
2597 rconv->chunksize[1] = wmem_tree_new(wmem_file_scope());
2598 rconv->txids[0] = wmem_tree_new(wmem_file_scope());
2599 rconv->txids[1] = wmem_tree_new(wmem_file_scope());
2601 return rconv;
2604 static int
2605 dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
2607 conversation_t *conv;
2608 rtmpt_conv_t *rconv;
2609 int cdir;
2610 struct tcpinfo *tcpinfo;
2612 /* Reject the packet if data is NULL */
2613 if (data == NULL) {
2614 return 0;
2616 tcpinfo = (struct tcpinfo*)data;
2618 conv = find_or_create_conversation(pinfo);
2620 rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
2621 if (!rconv) {
2622 rconv = rtmpt_init_rconv(conv);
2625 cdir = (addresses_equal(conversation_key_addr1(conv->key_ptr), &pinfo->src) &&
2626 addresses_equal(conversation_key_addr2(conv->key_ptr), &pinfo->dst) &&
2627 conversation_key_port1(conv->key_ptr) == pinfo->srcport &&
2628 conversation_key_port2(conv->key_ptr) == pinfo->destport) ? 0 : 1;
2630 dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, tcpinfo->seq, tcpinfo->lastackseq);
2631 return tvb_reported_length(tvb);
2634 static int
2635 dissect_rtmpt_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
2637 conversation_t *conv;
2638 rtmpt_conv_t *rconv;
2639 int cdir;
2640 uint32_t seq;
2641 uint32_t lastackseq;
2642 uint32_t offset;
2643 int remain;
2645 offset = 0;
2646 remain = tvb_reported_length_remaining(tvb, 0);
2649 * Request flow:
2651 * POST /open/1
2652 * request body is a single non-RTMP byte
2653 * response contains a client ID <cid> followed by NL
2654 * POST /send/<cid>/<seq>
2655 * <seq> starts at 0 after open and increments on each
2656 * subsequent post
2657 * request body is pure RTMP data
2658 * response is a single non-RTMP byte followed by RTMP data
2659 * POST /idle/<cid>/<seq>
2660 * request contains a single non-RTMP byte
2661 * response is a single non-RTMP byte followed by RTMP data
2662 * POST /close/<cid>/<seq>
2663 * request and response contain a single non-RTMP byte
2665 * Ideally here we'd know:
2667 * 1) Whether this is was a HTTP request or response
2668 * (this gives us cdir directly)
2669 * 2) The requested URL (for both cases)
2670 * (this tells us the type of framing bytes present,
2671 * so whether there are any real bytes present). We
2672 * could also use the client ID to identify the
2673 * conversation, since each POST is likely to be on
2674 * a different TCP connection, and there could be
2675 * multiple simultaneous sessions from a single
2676 * client (which we don't deal with here.)
2678 * As it is, we currently have to just guess, and are
2679 * likely easily confused.
2682 cdir = pinfo->srcport == pinfo->match_uint;
2684 if (cdir) {
2685 conv = find_conversation(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->srcport, 0);
2686 if (!conv) {
2687 ws_debug("RTMPT new conversation");
2688 conv = conversation_new(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->srcport, 0);
2690 } else {
2691 conv = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->destport, 0);
2692 if (!conv) {
2693 ws_debug("RTMPT new conversation");
2694 conv = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->destport, 0);
2698 rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
2699 if (!rconv) {
2700 rconv = rtmpt_init_rconv(conv);
2703 /* Work out a TCP-like sequence numbers for the tunneled data stream.
2704 * If we've seen the packet before we'll have stored the seq of our
2705 * last byte against the frame number - since we know how big we are
2706 * we can work out the seq of our first byte. If this is the first
2707 * time, we use the stored seq of the last byte of the previous frame
2708 * plus one. If there is no previous frame then we must be at seq=1!
2709 * (This is per-conversation and per-direction, of course.) */
2711 lastackseq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir ^ 1], pinfo->num))+1;
2713 if (cdir == 1 && lastackseq < 2 && remain == 17) {
2714 /* Session startup: the client makes an /open/ request and
2715 * the server responds with a 16 bytes client
2716 * identifier followed by a newline */
2717 offset += 17;
2718 remain -= 17;
2719 } else if (cdir || remain == 1) {
2720 /* All other server responses start with one byte which
2721 * is not part of the RTMP stream. Client /idle/ requests
2722 * contain a single byte also not part of the stream. We
2723 * must discard these */
2724 offset++;
2725 remain--;
2728 seq = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->seqs[cdir], pinfo->num));
2730 if (seq == 0) {
2731 seq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir], pinfo->num));
2732 seq += remain;
2733 wmem_tree_insert32(rconv->seqs[cdir], pinfo->num, GINT_TO_POINTER(seq));
2736 seq -= remain-1;
2738 ws_debug("RTMPT f=%d cdir=%d seq=%d lastackseq=%d len=%d", pinfo->num, cdir, seq, lastackseq, remain);
2740 if (remain < 1)
2741 return offset;
2743 if (offset > 0) {
2744 tvbuff_t *tvbrtmp = tvb_new_subset_length(tvb, offset, remain);
2745 dissect_rtmpt_common(tvbrtmp, pinfo, tree, rconv, cdir, seq, lastackseq);
2746 } else {
2747 dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, seq, lastackseq);
2749 return tvb_captured_length(tvb);
2752 static bool
2753 dissect_rtmpt_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
2755 conversation_t *conversation;
2756 if (tvb_reported_length(tvb) >= 12)
2758 /* To avoid a too high rate of false positive, this heuristics only matches the protocol
2759 from the first server response packet and not from the client request packets before.
2760 Therefore it is necessary to a "Decode as" to properly decode the first packets */
2761 struct tcpinfo *tcpinfo = (struct tcpinfo *)data;
2762 if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2
2763 && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1
2764 && tvb_get_uint8(tvb, 0) == RTMPT_MAGIC)
2766 /* Register this dissector for this conversation */
2767 conversation = find_or_create_conversation(pinfo);
2768 conversation_set_dissector(conversation, rtmpt_tcp_handle);
2770 /* Dissect the packet */
2771 dissect_rtmpt_tcp(tvb, pinfo, tree, data);
2772 return true;
2775 return false;
2778 static int
2779 dissect_amf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
2781 proto_item *ti;
2782 proto_tree *amf_tree, *headers_tree, *messages_tree;
2783 int offset;
2784 unsigned header_count, message_count, i;
2785 unsigned string_length;
2786 unsigned header_length, message_length;
2787 bool amf3_encoding = false;
2790 * XXX - is "application/x-amf" just AMF3?
2792 ti = proto_tree_add_item(tree, proto_amf, tvb, 0, -1, ENC_NA);
2793 amf_tree = proto_item_add_subtree(ti, ett_amf);
2794 offset = 0;
2795 proto_tree_add_item(amf_tree, hf_amf_version, tvb, offset, 2, ENC_BIG_ENDIAN);
2796 offset += 2;
2797 header_count = tvb_get_ntohs(tvb, offset);
2798 proto_tree_add_item(amf_tree, hf_amf_header_count, tvb, offset, 2, ENC_BIG_ENDIAN);
2799 offset += 2;
2800 if (header_count != 0) {
2801 headers_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_headers, NULL, "Headers");
2802 for (i = 0; i < header_count; i++) {
2803 string_length = tvb_get_ntohs(tvb, offset);
2804 proto_tree_add_item(headers_tree, hf_amf_header_name, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
2805 offset += 2 + string_length;
2806 proto_tree_add_item(headers_tree, hf_amf_header_must_understand, tvb, offset, 1, ENC_NA);
2807 offset += 1;
2808 header_length = tvb_get_ntohl(tvb, offset);
2809 if (header_length == 0xFFFFFFFF)
2810 proto_tree_add_uint_format_value(headers_tree, hf_amf_header_length, tvb, offset, 4, header_length, "Unknown");
2811 else
2812 proto_tree_add_item(headers_tree, hf_amf_header_length, tvb, offset, 4, ENC_BIG_ENDIAN);
2813 offset += 4;
2814 if (amf3_encoding)
2815 offset = dissect_amf3_value_type(tvb, pinfo, offset, headers_tree, NULL);
2816 else
2817 offset = dissect_amf0_value_type(tvb, pinfo, offset, headers_tree, &amf3_encoding, NULL);
2820 message_count = tvb_get_ntohs(tvb, offset);
2821 proto_tree_add_item(amf_tree, hf_amf_message_count, tvb, offset, 2, ENC_BIG_ENDIAN);
2822 offset += 2;
2823 if (message_count != 0) {
2824 messages_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_messages, NULL, "Messages");
2825 for (i = 0; i < message_count; i++) {
2826 string_length = tvb_get_ntohs(tvb, offset);
2827 proto_tree_add_item(messages_tree, hf_amf_message_target_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
2828 offset += 2 + string_length;
2829 string_length = tvb_get_ntohs(tvb, offset);
2830 proto_tree_add_item(messages_tree, hf_amf_message_response_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN);
2831 offset += 2 + string_length;
2832 message_length = tvb_get_ntohl(tvb, offset);
2833 if (message_length == 0xFFFFFFFF)
2834 proto_tree_add_uint_format_value(messages_tree, hf_amf_message_length, tvb, offset, 4, message_length, "Unknown");
2835 else
2836 proto_tree_add_item(messages_tree, hf_amf_message_length, tvb, offset, 4, ENC_BIG_ENDIAN);
2837 offset += 4;
2838 offset = dissect_rtmpt_body_command(tvb, pinfo, offset, messages_tree, false);
2841 return tvb_captured_length(tvb);
2844 void
2845 proto_register_rtmpt(void)
2847 static hf_register_info hf[] = {
2848 /* RTMP Handshake data */
2849 { &hf_rtmpt_handshake_c0,
2850 { "Protocol version", "rtmpt.handshake.c0", FT_BYTES, BASE_NONE,
2851 NULL, 0x0, "RTMPT Handshake C0", HFILL }},
2853 { &hf_rtmpt_handshake_s0,
2854 { "Protocol version", "rtmpt.handshake.s0", FT_BYTES, BASE_NONE,
2855 NULL, 0x0, "RTMPT Handshake S0", HFILL }},
2857 { &hf_rtmpt_handshake_c1,
2858 { "Handshake data", "rtmpt.handshake.c1", FT_BYTES, BASE_NONE,
2859 NULL, 0x0, "RTMPT Handshake C1", HFILL }},
2861 { &hf_rtmpt_handshake_s1,
2862 { "Handshake data", "rtmpt.handshake.s1", FT_BYTES, BASE_NONE,
2863 NULL, 0x0, "RTMPT Handshake S1", HFILL }},
2865 { &hf_rtmpt_handshake_c2,
2866 { "Handshake data", "rtmpt.handshake.c2", FT_BYTES, BASE_NONE,
2867 NULL, 0x0, "RTMPT Handshake C2", HFILL }},
2869 { &hf_rtmpt_handshake_s2,
2870 { "Handshake data", "rtmpt.handshake.s2", FT_BYTES, BASE_NONE,
2871 NULL, 0x0, "RTMPT Handshake S2", HFILL }},
2873 /* RTMP chunk/packet header */
2874 { &hf_rtmpt_header_format,
2875 { "Format", "rtmpt.header.format", FT_UINT8, BASE_DEC,
2876 NULL, 0xC0, "RTMPT Basic Header format", HFILL }},
2878 { &hf_rtmpt_header_csid,
2879 { "Chunk Stream ID", "rtmpt.header.csid", FT_UINT8, BASE_DEC,
2880 NULL, 0x3F, "RTMPT Basic Header chunk stream ID", HFILL }},
2882 { &hf_rtmpt_header_timestamp,
2883 { "Timestamp", "rtmpt.header.timestamp", FT_UINT24, BASE_DEC,
2884 NULL, 0x0, "RTMPT Message Header timestamp", HFILL }},
2886 { &hf_rtmpt_header_timestamp_delta,
2887 { "Timestamp delta", "rtmpt.header.timestampdelta", FT_UINT24, BASE_DEC,
2888 NULL, 0x0, "RTMPT Message Header timestamp delta", HFILL }},
2890 { &hf_rtmpt_header_body_size,
2891 { "Body size", "rtmpt.header.bodysize", FT_UINT24, BASE_DEC,
2892 NULL, 0x0, "RTMPT Message Header body size", HFILL }},
2894 { &hf_rtmpt_header_typeid,
2895 { "Type ID", "rtmpt.header.typeid", FT_UINT8, BASE_HEX,
2896 VALS(rtmpt_opcode_vals), 0x0, "RTMPT Message Header type ID", HFILL }},
2898 { &hf_rtmpt_header_streamid,
2899 { "Stream ID", "rtmpt.header.streamid", FT_UINT32, BASE_DEC,
2900 NULL, 0x0, "RTMPT Header stream ID", HFILL }},
2902 { &hf_rtmpt_header_ets,
2903 { "Extended timestamp", "rtmpt.header.ets", FT_UINT24, BASE_DEC,
2904 NULL, 0x0, "RTMPT Message Header extended timestamp", HFILL }},
2906 /* Stream Control Messages */
2908 { &hf_rtmpt_scm_chunksize,
2909 { "Chunk size", "rtmpt.scm.chunksize", FT_UINT32, BASE_DEC,
2910 NULL, 0x0, "RTMPT SCM chunk size", HFILL }},
2912 { &hf_rtmpt_scm_csid,
2913 { "Chunk stream ID", "rtmpt.scm.csid", FT_UINT32, BASE_DEC,
2914 NULL, 0x0, "RTMPT SCM chunk stream ID", HFILL }},
2916 { &hf_rtmpt_scm_seq,
2917 { "Sequence number", "rtmpt.scm.seq", FT_UINT32, BASE_DEC,
2918 NULL, 0x0, "RTMPT SCM acknowledgement sequence number", HFILL }},
2920 { &hf_rtmpt_scm_was,
2921 { "Window acknowledgement size", "rtmpt.scm.was", FT_UINT32, BASE_DEC,
2922 NULL, 0x0, "RTMPT SCM window acknowledgement size", HFILL }},
2924 { &hf_rtmpt_scm_limittype,
2925 { "Limit type", "rtmpt.scm.limittype", FT_UINT8, BASE_DEC,
2926 VALS(rtmpt_limit_vals), 0x0, "RTMPT SCM window acknowledgement size", HFILL }},
2928 /* User Control Messages */
2929 { &hf_rtmpt_ucm_eventtype,
2930 { "Event type", "rtmpt.ucm.eventtype", FT_UINT16, BASE_DEC,
2931 VALS(rtmpt_ucm_vals), 0x0, "RTMPT UCM event type", HFILL }},
2933 /* Frame links */
2935 { &hf_rtmpt_function_call,
2936 { "Response to this call in frame", "rtmpt.function.call", FT_FRAMENUM, BASE_NONE,
2937 FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0, "RTMPT function call", HFILL }},
2939 { &hf_rtmpt_function_response,
2940 { "Call for this response in frame", "rtmpt.function.response", FT_FRAMENUM, BASE_NONE,
2941 FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0, "RTMPT function response", HFILL }},
2943 /* Audio packets */
2944 { &hf_rtmpt_audio_control,
2945 { "Control", "rtmpt.audio.control", FT_UINT8, BASE_HEX,
2946 NULL, 0x0, NULL, HFILL }},
2948 { &hf_rtmpt_audio_is_ex_header,
2949 { "IsExAudioHeader", "rtmpt.audio.is_ex_header", FT_UINT8, BASE_DEC,
2950 NULL, 0x90, "RTMPT IsExHeader flag introduced in enhanced RTMP", HFILL } },
2952 { &hf_rtmpt_audio_multitrack_control,
2953 { "Audio multitrack control", "rtmpt.audio.multitrack.control", FT_UINT8, BASE_HEX,
2954 NULL, 0x0, NULL, HFILL } },
2956 { &hf_rtmpt_audio_multitrack_type,
2957 { "Audio multitrack type", "rtmpt.audio.multitrack.type", FT_UINT8, BASE_HEX,
2958 VALS(rtmpt_av_multitrack_types), 0x0f, NULL, HFILL } },
2960 { &hf_rtmpt_audio_multitrack_packet_type,
2961 { "Audio multitrack packet type", "rtmpt.audio.multitrack.track.packet_type", FT_UINT8, BASE_HEX,
2962 VALS(rtmpt_audio_packet_types), 0xf0, NULL, HFILL } },
2964 { &hf_rtmpt_audio_packet_type,
2965 { "Audio packet type", "rtmpt.audio.packet_type", FT_UINT8, BASE_HEX,
2966 VALS(rtmpt_audio_packet_types), 0x0f, NULL, HFILL } },
2968 { &hf_rtmpt_audio_fourcc,
2969 { "FourCC", "rtmpt.audio.fourcc", FT_STRING, BASE_NONE,
2970 NULL, 0x0, NULL, HFILL } },
2972 { &hf_rtmpt_audio_track_id,
2973 { "Track ID", "rtmpt.audio.multitrack.track.id", FT_UINT8, BASE_DEC,
2974 NULL, 0x0, NULL, HFILL } },
2976 { &hf_rtmpt_audio_track_length,
2977 { "Track length", "rtmpt.audio.multitrack.track.length", FT_UINT24, BASE_DEC,
2978 NULL, 0x0, NULL, HFILL } },
2980 { &hf_rtmpt_audio_format,
2981 { "Format", "rtmpt.audio.format", FT_UINT8, BASE_DEC,
2982 VALS(rtmpt_audio_codecs), 0xf0, NULL, HFILL }},
2984 { &hf_rtmpt_audio_rate,
2985 { "Sample rate", "rtmpt.audio.rate", FT_UINT8, BASE_DEC,
2986 VALS(rtmpt_audio_rates), 0x0c, NULL, HFILL }},
2988 { &hf_rtmpt_audio_size,
2989 { "Sample size", "rtmpt.audio.size", FT_UINT8, BASE_DEC,
2990 VALS(rtmpt_audio_sizes), 0x02, NULL, HFILL }},
2992 { &hf_rtmpt_audio_type,
2993 { "Channels", "rtmpt.audio.type", FT_UINT8, BASE_DEC,
2994 VALS(rtmpt_audio_types), 0x01, NULL, HFILL }},
2996 { &hf_rtmpt_audio_data,
2997 { "Audio data", "rtmpt.audio.data", FT_BYTES, BASE_NONE,
2998 NULL, 0x0, NULL, HFILL }},
3000 /* Video packets */
3001 { &hf_rtmpt_video_control,
3002 { "Control", "rtmpt.video.control", FT_UINT8, BASE_HEX,
3003 NULL, 0x0, NULL, HFILL }},
3005 { &hf_rtmpt_video_multitrack_control,
3006 { "Video multitrack control", "rtmpt.video.multitrack.control", FT_UINT8, BASE_HEX,
3007 NULL, 0x0, NULL, HFILL }},
3009 { &hf_rtmpt_video_is_ex_header,
3010 { "IsExVideoHeader", "rtmpt.video.is_ex_header", FT_UINT8, BASE_DEC,
3011 NULL, 0x80, "RTMPT IsExHeader flag introduced in enhanced RTMP", HFILL }},
3013 { &hf_rtmpt_video_type,
3014 { "Video type", "rtmpt.video.type", FT_UINT8, BASE_DEC,
3015 VALS(rtmpt_video_types), 0x70, NULL, HFILL }},
3017 { &hf_rtmpt_video_command,
3018 { "Video command", "rtmpt.video.command", FT_UINT8, BASE_DEC,
3019 VALS(rtmpt_video_commands), 0x0, NULL, HFILL}},
3021 { &hf_rtmpt_video_format,
3022 { "Format", "rtmpt.video.format", FT_UINT8, BASE_DEC,
3023 VALS(rtmpt_video_codecs), 0x0f, NULL, HFILL }},
3025 { &hf_rtmpt_video_packet_type,
3026 { "Video packet type", "rtmpt.video.packet_type", FT_UINT8, BASE_DEC,
3027 VALS(rtmpt_video_packet_types), 0x0f, NULL, HFILL }},
3029 { &hf_rtmpt_video_multitrack_type,
3030 { "Video multitrack type", "rtmpt.video.multitrack.type", FT_UINT8, BASE_DEC,
3031 VALS(rtmpt_av_multitrack_types), 0xf0, NULL, HFILL } },
3033 { &hf_rtmpt_video_multitrack_packet_type,
3034 { "Video multitrack packet type", "rtmpt.video.multitrack.packet_type", FT_UINT8, BASE_DEC,
3035 VALS(rtmpt_video_packet_types), 0x0f, NULL, HFILL } },
3037 { &hf_rtmpt_video_fourcc,
3038 { "FourCC", "rtmpt.video.fourcc", FT_STRING, BASE_NONE,
3039 NULL, 0x0, NULL, HFILL }},
3041 { &hf_rtmpt_video_track_id,
3042 { "Track ID", "rtmpt.video.multitrack.track.id", FT_UINT8, BASE_DEC,
3043 NULL, 0x0, NULL, HFILL } },
3045 { &hf_rtmpt_video_track_length,
3046 { "Track length", "rtmpt.video.multitrack.track.length", FT_UINT24, BASE_DEC,
3047 NULL, 0x0, NULL, HFILL } },
3049 { &hf_rtmpt_video_data,
3050 { "Video data", "rtmpt.video.data", FT_BYTES, BASE_NONE,
3051 NULL, 0x0, NULL, HFILL }},
3053 /* Aggregate packets */
3054 { &hf_rtmpt_tag_type,
3055 { "Type", "rtmpt.tag.type", FT_UINT8, BASE_DEC,
3056 VALS(rtmpt_tag_vals), 0x0, "RTMPT Aggregate tag type", HFILL }},
3058 { &hf_rtmpt_tag_datasize,
3059 { "Data size", "rtmpt.tag.datasize", FT_UINT24, BASE_DEC,
3060 NULL, 0x0, "RTMPT Aggregate tag data size", HFILL }},
3062 { &hf_rtmpt_tag_timestamp,
3063 { "Timestamp", "rtmpt.tag.timestamp", FT_UINT24, BASE_DEC,
3064 NULL, 0x0, "RTMPT Aggregate tag timestamp", HFILL }},
3066 { &hf_rtmpt_tag_ets,
3067 { "Timestamp Extended", "rtmpt.tag.ets", FT_UINT8, BASE_DEC,
3068 NULL, 0x0, "RTMPT Aggregate tag timestamp extended", HFILL }},
3070 { &hf_rtmpt_tag_streamid,
3071 { "Stream ID", "rtmpt.tag.streamid", FT_UINT24, BASE_DEC,
3072 NULL, 0x0, "RTMPT Aggregate tag stream ID", HFILL }},
3074 { &hf_rtmpt_tag_tagsize,
3075 { "Previous tag size", "rtmpt.tag.tagsize", FT_UINT32, BASE_DEC,
3076 NULL, 0x0, "RTMPT Aggregate previous tag size", HFILL }}
3078 static int *ett[] = {
3079 &ett_rtmpt,
3080 &ett_rtmpt_handshake,
3081 &ett_rtmpt_header,
3082 &ett_rtmpt_body,
3083 &ett_rtmpt_ucm,
3084 &ett_rtmpt_audio_control,
3085 &ett_rtmpt_video_control,
3086 &ett_rtmpt_audio_multitrack_control,
3087 &ett_rtmpt_audio_multitrack_track,
3088 &ett_rtmpt_video_multitrack_control,
3089 &ett_rtmpt_video_multitrack_track,
3090 &ett_rtmpt_tag,
3091 &ett_rtmpt_tag_data
3094 module_t *rtmpt_module;
3096 proto_rtmpt = proto_register_protocol("Real Time Messaging Protocol", "RTMPT", "rtmpt");
3097 proto_register_field_array(proto_rtmpt, hf, array_length(hf));
3098 proto_register_subtree_array(ett, array_length(ett));
3100 rtmpt_tcp_handle = register_dissector("rtmpt.tcp", dissect_rtmpt_tcp, proto_rtmpt);
3101 rtmpt_http_handle = register_dissector("rtmpt.http", dissect_rtmpt_http, proto_rtmpt);
3103 rtmpt_module = prefs_register_protocol(proto_rtmpt, NULL);
3104 /* XXX: This desegment preference doesn't do anything */
3105 prefs_register_bool_preference(rtmpt_module, "desegment",
3106 "Reassemble RTMPT messages spanning multiple TCP segments",
3107 "Whether the RTMPT dissector should reassemble messages spanning multiple TCP segments."
3108 " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\""
3109 " in the TCP protocol settings.",
3110 &rtmpt_desegment);
3112 prefs_register_obsolete_preference(rtmpt_module, "max_packet_size");
3114 prefs_register_uint_preference(rtmpt_module, "default_chunk_size",
3115 "Default chunk size",
3116 "Chunk size to use for connections where the initial handshake is missing,"
3117 " i.e. are already in progress at the beginning of the capture file.",
3118 10, &rtmpt_default_chunk_size);
3121 void
3122 proto_register_amf(void)
3124 static hf_register_info hf[] = {
3125 { &hf_amf_version,
3126 { "AMF version", "amf.version", FT_UINT16, BASE_DEC,
3127 NULL, 0x0, NULL, HFILL }},
3129 { &hf_amf_header_count,
3130 { "Header count", "amf.header_count", FT_UINT16, BASE_DEC,
3131 NULL, 0x0, NULL, HFILL }},
3133 { &hf_amf_header_name,
3134 { "Name", "amf.header.name", FT_UINT_STRING, BASE_NONE,
3135 NULL, 0x0, NULL, HFILL }},
3137 { &hf_amf_header_must_understand,
3138 { "Must understand", "amf.header.must_understand", FT_BOOLEAN, BASE_NONE,
3139 NULL, 0x0, NULL, HFILL }},
3141 { &hf_amf_header_length,
3142 { "Length", "amf.header.length", FT_UINT32, BASE_DEC,
3143 NULL, 0x0, NULL, HFILL }},
3145 #if 0
3146 { &hf_amf_header_value_type,
3147 { "Value type", "amf.header.value_type", FT_UINT32, BASE_HEX,
3148 VALS(rtmpt_type_vals), 0x0, NULL, HFILL }},
3149 #endif
3151 { &hf_amf_message_count,
3152 { "Message count", "amf.message_count", FT_UINT16, BASE_DEC,
3153 NULL, 0x0, NULL, HFILL }},
3155 { &hf_amf_message_target_uri,
3156 { "Target URI", "amf.message.target_uri", FT_UINT_STRING, BASE_NONE,
3157 NULL, 0x0, NULL, HFILL }},
3159 { &hf_amf_message_response_uri,
3160 { "Response URI", "amf.message.response_uri", FT_UINT_STRING, BASE_NONE,
3161 NULL, 0x0, NULL, HFILL }},
3163 { &hf_amf_message_length,
3164 { "Length", "amf.message.length", FT_UINT32, BASE_DEC,
3165 NULL, 0x0, NULL, HFILL }},
3168 /* AMF basic types */
3169 { &hf_amf_amf0_type,
3170 { "AMF0 type", "amf.amf0_type", FT_UINT8, BASE_HEX,
3171 VALS(amf0_type_vals), 0x0, NULL, HFILL }},
3173 { &hf_amf_amf3_type,
3174 { "AMF3 type", "amf.amf3_type", FT_UINT8, BASE_HEX,
3175 VALS(amf3_type_vals), 0x0, NULL, HFILL }},
3177 { &hf_amf_number,
3178 { "Number", "amf.number", FT_DOUBLE, BASE_NONE,
3179 NULL, 0x0, "AMF number", HFILL }},
3181 { &hf_amf_integer,
3182 { "Integer", "amf.integer", FT_UINT32, BASE_DEC,
3183 NULL, 0x0, "RTMPT AMF3 integer", HFILL }},
3185 { &hf_amf_boolean,
3186 { "Boolean", "amf.boolean", FT_BOOLEAN, BASE_NONE,
3187 NULL, 0x0, "AMF boolean", HFILL }},
3189 { &hf_amf_stringlength,
3190 { "String length", "amf.stringlength", FT_UINT32, BASE_DEC,
3191 NULL, 0x0, "AMF string length", HFILL }},
3193 { &hf_amf_string,
3194 { "String", "amf.string", FT_STRING, BASE_NONE,
3195 NULL, 0x0, "AMF string", HFILL }},
3197 { &hf_amf_string_reference,
3198 { "String reference", "amf.string_reference", FT_UINT32, BASE_DEC,
3199 NULL, 0x0, "RTMPT AMF3 string reference", HFILL }},
3201 { &hf_amf_object_reference,
3202 { "Object reference", "amf.object_reference", FT_UINT32, BASE_DEC,
3203 NULL, 0x0, "AMF object reference", HFILL }},
3205 { &hf_amf_date,
3206 { "Date", "amf.date", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL,
3207 NULL, 0x0, "AMF date", HFILL }},
3209 #if 0
3210 { &hf_amf_longstringlength,
3211 { "String length", "amf.longstringlength", FT_UINT32, BASE_DEC,
3212 NULL, 0x0, "AMF long string length", HFILL }},
3213 #endif
3215 { &hf_amf_longstring,
3216 { "Long string", "amf.longstring", FT_STRING, BASE_NONE,
3217 NULL, 0x0, "AMF long string", HFILL }},
3219 { &hf_amf_xml_doc,
3220 { "XML document", "amf.xml_doc", FT_STRING, BASE_NONE,
3221 NULL, 0x0, "AMF XML document", HFILL }},
3223 { &hf_amf_xmllength,
3224 { "XML text length", "amf.xmllength", FT_UINT32, BASE_DEC,
3225 NULL, 0x0, "AMF E4X XML length", HFILL }},
3227 { &hf_amf_xml,
3228 { "XML", "amf.xml", FT_STRING, BASE_NONE,
3229 NULL, 0x0, "AMF E4X XML", HFILL }},
3231 { &hf_amf_int64,
3232 { "Int64", "amf.int64", FT_INT64, BASE_DEC,
3233 NULL, 0x0, "AMF int64", HFILL }},
3235 { &hf_amf_bytearraylength,
3236 { "ByteArray length", "amf.bytearraylength", FT_UINT32, BASE_DEC,
3237 NULL, 0x0, "RTMPT AMF3 ByteArray length", HFILL }},
3239 { &hf_amf_bytearray,
3240 { "ByteArray", "amf.bytearray", FT_BYTES, BASE_NONE,
3241 NULL, 0x0, "RTMPT AMF3 ByteArray", HFILL }},
3243 /* AMF object types and subfields of the object types */
3244 { &hf_amf_object,
3245 { "Object", "amf.object", FT_NONE, BASE_NONE,
3246 NULL, 0x0, "AMF object", HFILL }},
3248 { &hf_amf_traitcount,
3249 { "Trait count", "amf.traitcount", FT_UINT32, BASE_DEC,
3250 NULL, 0x0, "AMF count of traits for an object", HFILL }},
3252 { &hf_amf_classnamelength,
3253 { "Class name length", "amf.classnamelength", FT_UINT32, BASE_DEC,
3254 NULL, 0x0, "AMF class name length", HFILL }},
3256 { &hf_amf_classname,
3257 { "Class name", "amf.classname", FT_STRING, BASE_NONE,
3258 NULL, 0x0, "AMF class name", HFILL }},
3260 { &hf_amf_membernamelength,
3261 { "Member name length", "amf.membernamelength", FT_UINT32, BASE_DEC,
3262 NULL, 0x0, "AMF member name length", HFILL }},
3264 { &hf_amf_membername,
3265 { "Member name", "amf.membername", FT_STRING, BASE_NONE,
3266 NULL, 0x0, "AMF member name", HFILL }},
3268 { &hf_amf_trait_reference,
3269 { "Trait reference", "amf.trait_reference", FT_UINT32, BASE_DEC,
3270 NULL, 0x0, "AMF trait reference", HFILL }},
3272 { &hf_amf_ecmaarray,
3273 { "ECMA array", "amf.ecmaarray", FT_NONE, BASE_NONE,
3274 NULL, 0x0, "AMF ECMA array", HFILL }},
3276 { &hf_amf_strictarray,
3277 { "Strict array", "amf.strictarray", FT_NONE, BASE_NONE,
3278 NULL, 0x0, "AMF strict array", HFILL }},
3280 { &hf_amf_array,
3281 { "Array", "amf.array", FT_NONE, BASE_NONE,
3282 NULL, 0x0, "RTMPT AMF3 array", HFILL }},
3284 { &hf_amf_arraylength,
3285 { "Array length", "amf.arraylength", FT_UINT32, BASE_DEC,
3286 NULL, 0x0, "AMF array length", HFILL }},
3288 { &hf_amf_arraydenselength,
3289 { "Length of dense portion", "amf.arraydenselength", FT_UINT32, BASE_DEC,
3290 NULL, 0x0, "AMF length of dense portion of array", HFILL }},
3292 { &hf_amf_end_of_object_marker,
3293 { "End Of Object Marker", "amf.end_of_object_marker", FT_NONE, BASE_NONE,
3294 NULL, 0x0, NULL, HFILL }},
3296 { &hf_amf_end_of_associative_part,
3297 { "End of associative part", "amf.end_of_associative_part", FT_NONE, BASE_NONE,
3298 NULL, 0x0, NULL, HFILL }},
3300 { &hf_amf_end_of_dynamic_members,
3301 { "End Of dynamic members", "amf.end_of_dynamic_members", FT_NONE, BASE_NONE,
3302 NULL, 0x0, NULL, HFILL }},
3305 static ei_register_info ei[] = {
3306 { &ei_amf_loop, { "amf.loop", PI_MALFORMED, PI_ERROR, "Loop in AMF dissection", EXPFILL }}
3310 static int *ett[] = {
3311 &ett_amf,
3312 &ett_amf_headers,
3313 &ett_amf_messages,
3314 &ett_amf_value,
3315 &ett_amf_property,
3316 &ett_amf_string,
3317 &ett_amf_array_element,
3318 &ett_amf_traits,
3319 &ett_amf_trait_member,
3322 expert_module_t* expert_amf;
3324 proto_amf = proto_register_protocol("Action Message Format", "AMF", "amf");
3325 proto_register_field_array(proto_amf, hf, array_length(hf));
3326 proto_register_subtree_array(ett, array_length(ett));
3327 expert_amf = expert_register_protocol(proto_amf);
3328 expert_register_field_array(expert_amf, ei, array_length(ei));
3330 amf_handle = register_dissector("amf", dissect_amf, proto_amf);
3333 void
3334 proto_reg_handoff_rtmpt(void)
3336 heur_dissector_add("tcp", dissect_rtmpt_heur, "RTMPT over TCP", "rtmpt_tcp", proto_rtmpt, HEURISTIC_DISABLE);
3337 dissector_add_uint_with_preference("tcp.port", RTMP_PORT, rtmpt_tcp_handle);
3339 dissector_add_string("media_type", "application/x-fcs", rtmpt_http_handle);
3340 dissector_add_string("media_type", "application/x-amf", amf_handle);
3344 * Editor modelines - https://www.wireshark.org/tools/modelines.html
3346 * Local variables:
3347 * c-basic-offset: 8
3348 * tab-width: 8
3349 * indent-tabs-mode: nil
3350 * End:
3352 * vi: set shiftwidth=8 tabstop=8 expandtab:
3353 * :indentSize=8:tabSize=8:noTabs=true: