/src/wireshark/epan/dissectors/packet-rtmpt.c
Line | Count | Source |
1 | | /* packet-rtmpt.c |
2 | | * Routines for Real Time Messaging Protocol packet dissection |
3 | | * metatech <metatech@flashmail.com> |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-2.0-or-later |
10 | | */ |
11 | | |
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) |
15 | | * |
16 | | * RTMP in a nutshell |
17 | | * |
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. |
22 | | * |
23 | | * Documentation: |
24 | | * RTMP protocol description on Wiki of Red5 Open Source Flash Server at |
25 | | * |
26 | | * http://trac.red5.org/wiki/Codecs/RTMPSpecification |
27 | | * |
28 | | * and the pages to which it links: |
29 | | * |
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 |
35 | | * |
36 | | * It's also available from Adobe at |
37 | | * |
38 | | * https://www.adobe.com/devnet/rtmp.html |
39 | | * |
40 | | * For AMF, see: |
41 | | * |
42 | | * http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf |
43 | | * |
44 | | * for AMF0 and |
45 | | * |
46 | | * http://amf3cplusplus.googlecode.com/svn-history/r4/trunk/doc/amf3_spec_05_05_08.pdf |
47 | | * |
48 | | * for AMF3. |
49 | | * |
50 | | * For FLV, see: |
51 | | * |
52 | | * https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf |
53 | | * |
54 | | * For Enhanced RTMP, see: |
55 | | * |
56 | | * https://veovera.org/docs/enhanced/enhanced-rtmp-v2.pdf |
57 | | * |
58 | | * Default TCP port is 1935 |
59 | | */ |
60 | | |
61 | | #include "config.h" |
62 | 0 | #define WS_LOG_DOMAIN "RTMPT" |
63 | | |
64 | | #include <epan/packet.h> |
65 | | #include <wsutil/pint.h> |
66 | | |
67 | | #include <epan/prefs.h> |
68 | | #include <epan/to_str.h> |
69 | | #include <epan/expert.h> |
70 | | #include "packet-tcp.h" |
71 | | |
72 | 4 | #define MAX_AMF_ITERATIONS 1000 |
73 | | |
74 | | void proto_register_rtmpt(void); |
75 | | void proto_reg_handoff_rtmpt(void); |
76 | | |
77 | | void proto_register_amf(void); |
78 | | |
79 | | static int proto_rtmpt; |
80 | | |
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; |
87 | | |
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; |
96 | | |
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; |
102 | | |
103 | | static int hf_rtmpt_ucm_eventtype; |
104 | | |
105 | | static int hf_rtmpt_function_call; |
106 | | static int hf_rtmpt_function_response; |
107 | | |
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; |
122 | | |
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; |
136 | | |
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; |
143 | | |
144 | | static expert_field ei_amf_loop; |
145 | | |
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; |
159 | | |
160 | | static dissector_handle_t amf_handle; |
161 | | static dissector_handle_t rtmpt_tcp_handle; |
162 | | static dissector_handle_t rtmpt_http_handle; |
163 | | |
164 | | static bool rtmpt_desegment = true; |
165 | | |
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 |
176 | | */ |
177 | | #define RTMPT_INIT_ALLOC_SIZE 32768 |
178 | | |
179 | 15 | #define RTMP_PORT 1935 /* Not IANA registered */ |
180 | | |
181 | 8.44k | #define RTMPT_MAGIC 0x03 |
182 | 27 | #define RTMPT_HANDSHAKE_OFFSET_1 1 |
183 | 4.22k | #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 | 0 | #define RTMPT_INITIAL_CHUNK_SIZE 128 |
189 | | |
190 | | static unsigned rtmpt_default_chunk_size = 128; |
191 | | |
192 | 18.7k | #define RTMPT_ID_MAX 65599 |
193 | 3 | #define RTMPT_TYPE_HANDSHAKE_1 0x100001 |
194 | 4 | #define RTMPT_TYPE_HANDSHAKE_2 0x100002 |
195 | 0 | #define RTMPT_TYPE_HANDSHAKE_3 0x100003 |
196 | | |
197 | 7.59k | #define RTMPT_TYPE_CHUNK_SIZE 0x01 |
198 | 3.49k | #define RTMPT_TYPE_ABORT_MESSAGE 0x02 |
199 | 3.50k | #define RTMPT_TYPE_ACKNOWLEDGEMENT 0x03 |
200 | 1.62k | #define RTMPT_TYPE_UCM 0x04 |
201 | 1.76k | #define RTMPT_TYPE_WINDOW 0x05 |
202 | 1.67k | #define RTMPT_TYPE_PEER_BANDWIDTH 0x06 |
203 | 15 | #define RTMPT_TYPE_AUDIO_DATA 0x08 |
204 | 3 | #define RTMPT_TYPE_VIDEO_DATA 0x09 |
205 | 3.23k | #define RTMPT_TYPE_DATA_AMF3 0x0F |
206 | | #define RTMPT_TYPE_SHARED_AMF3 0x10 |
207 | 6.59k | #define RTMPT_TYPE_COMMAND_AMF3 0x11 |
208 | 6.48k | #define RTMPT_TYPE_DATA_AMF0 0x12 |
209 | | #define RTMPT_TYPE_SHARED_AMF0 0x13 |
210 | 6.51k | #define RTMPT_TYPE_COMMAND_AMF0 0x14 |
211 | 0 | #define RTMPT_TYPE_AGGREGATE 0x16 |
212 | | |
213 | 6 | #define RTMPT_UCM_STREAM_BEGIN 0x00 |
214 | 4 | #define RTMPT_UCM_STREAM_EOF 0x01 |
215 | 4 | #define RTMPT_UCM_STREAM_DRY 0x02 |
216 | 1 | #define RTMPT_UCM_SET_BUFFER 0x03 |
217 | 1 | #define RTMPT_UCM_STREAM_ISRECORDED 0x04 |
218 | | #define RTMPT_UCM_PING_REQUEST 0x06 |
219 | | #define RTMPT_UCM_PING_RESPONSE 0x07 |
220 | | |
221 | 30 | #define RTMPT_IS_EX_AUDIO_HEADER 0x90 |
222 | 3 | #define RTMPT_IS_EX_VIDEO_HEADER 0x80 |
223 | 2 | #define RTMPT_IS_PACKET_TYPE_METADATA 0x04 |
224 | 3 | #define RTMPT_IS_FRAME_TYPE_COMMAND 0x05 |
225 | 11 | #define RTMPT_IS_AUDIO_MULTITRACK 0x05 |
226 | 1 | #define RTMPT_IS_VIDEO_MULTITRACK 0x06 |
227 | 0 | #define RTMPT_IS_ONETRACK 0x00 |
228 | 0 | #define RTMPT_IS_MANYTRACKSMANYCODECS 0x02 |
229 | | |
230 | 1.81k | #define RTMPT_TEXT_RTMP_HEADER "RTMP Header" |
231 | 1.21k | #define RTMPT_TEXT_RTMP_BODY "RTMP Body" |
232 | | |
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 } |
238 | | }; |
239 | | |
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 } |
257 | | }; |
258 | | |
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 } |
266 | | }; |
267 | | |
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 } |
277 | | }; |
278 | | |
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 } |
284 | | }; |
285 | | |
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 } |
303 | | }; |
304 | | |
305 | | static const value_string rtmpt_audio_packet_types[] = { |
306 | | { 0, "PacketTypeSequenceStart" }, |
307 | | { 1, "PacketTypeCodedFrames" }, |
308 | | { 4, "PacketTypeMultichannelConfig" }, |
309 | | { 5, "PacketTypeMultitrack" }, |
310 | | { 0, NULL } |
311 | | }; |
312 | | |
313 | | static const value_string rtmpt_av_multitrack_types[] = { |
314 | | { 0, "AvMultitrackTypeOneTrack" }, |
315 | | { 1, "AvMultitrackTypeManyTracks" }, |
316 | | { 2, "AvMultitrackTypeManyTracksManyCodecs" }, |
317 | | { 0, NULL } |
318 | | }; |
319 | | |
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 } |
326 | | }; |
327 | | |
328 | | static const value_string rtmpt_audio_sizes[] = { |
329 | | { 0, "8 bit" }, |
330 | | { 1, "16 bit" }, |
331 | | { 0, NULL } |
332 | | }; |
333 | | |
334 | | static const value_string rtmpt_audio_types[] = { |
335 | | { 0, "mono" }, |
336 | | { 1, "stereo" }, |
337 | | { 0, NULL } |
338 | | }; |
339 | | |
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 } |
348 | | }; |
349 | | |
350 | | /* From Enhanced RTMP */ |
351 | | static const value_string rtmpt_video_commands[] = { |
352 | | { 0, "StartSeek" }, |
353 | | { 1, "EndSeek" }, |
354 | | { 0, NULL } |
355 | | }; |
356 | | |
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 } |
367 | | }; |
368 | | |
369 | | /* |
370 | | * https://raw.githubusercontent.com/veovera/enhanced-rtmp/main/enhanced-rtmp.pdf |
371 | | */ |
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 } |
381 | | }; |
382 | | |
383 | | static int proto_amf; |
384 | | |
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; |
395 | | |
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; |
414 | | |
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; |
427 | | |
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; |
431 | | |
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; |
441 | | |
442 | | /* AMF0 type markers */ |
443 | 6 | #define AMF0_NUMBER 0x00 |
444 | 0 | #define AMF0_BOOLEAN 0x01 |
445 | 0 | #define AMF0_STRING 0x02 |
446 | 0 | #define AMF0_OBJECT 0x03 |
447 | | #define AMF0_MOVIECLIP 0x04 |
448 | 0 | #define AMF0_NULL 0x05 |
449 | 0 | #define AMF0_UNDEFINED 0x06 |
450 | 0 | #define AMF0_REFERENCE 0x07 |
451 | 0 | #define AMF0_ECMA_ARRAY 0x08 |
452 | 0 | #define AMF0_END_OF_OBJECT 0x09 |
453 | 0 | #define AMF0_STRICT_ARRAY 0x0A |
454 | 0 | #define AMF0_DATE 0x0B |
455 | 0 | #define AMF0_LONG_STRING 0x0C |
456 | 0 | #define AMF0_UNSUPPORTED 0x0D |
457 | | #define AMF0_RECORDSET 0x0E |
458 | 0 | #define AMF0_XML 0x0F |
459 | 0 | #define AMF0_TYPED_OBJECT 0x10 |
460 | 0 | #define AMF0_AMF3_MARKER 0x11 |
461 | 0 | #define AMF0_INT64 0x22 |
462 | | |
463 | | /* AMF3 type markers */ |
464 | 0 | #define AMF3_UNDEFINED 0x00 |
465 | 0 | #define AMF3_NULL 0x01 |
466 | 0 | #define AMF3_FALSE 0x02 |
467 | 0 | #define AMF3_TRUE 0x03 |
468 | 0 | #define AMF3_INTEGER 0x04 |
469 | 0 | #define AMF3_DOUBLE 0x05 |
470 | 0 | #define AMF3_STRING 0x06 |
471 | | #define AMF3_XML_DOC 0x07 |
472 | 0 | #define AMF3_DATE 0x08 |
473 | 0 | #define AMF3_ARRAY 0x09 |
474 | 0 | #define AMF3_OBJECT 0x0A |
475 | 0 | #define AMF3_XML 0x0B |
476 | 0 | #define AMF3_BYTEARRAY 0x0C |
477 | | |
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 } |
499 | | }; |
500 | | |
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 } |
516 | | }; |
517 | | |
518 | | /* Holds the reassembled data for a packet during un-chunking |
519 | | */ |
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. |
528 | | */ |
529 | | typedef struct rtmpt_packet { |
530 | | uint32_t seq; |
531 | | uint32_t lastseq; |
532 | | |
533 | | int resident; |
534 | | union { |
535 | | uint8_t *p; |
536 | | uint32_t offset; |
537 | | } data; |
538 | | |
539 | | wmem_list_t *frames; |
540 | | |
541 | | /* used during unchunking */ |
542 | | int alloc; |
543 | | int want; |
544 | | int have; |
545 | | int chunkwant; |
546 | | int chunkhave; |
547 | | |
548 | | uint8_t bhlen; |
549 | | uint8_t mhlen; |
550 | | |
551 | | /* Chunk Basic Header */ |
552 | | uint8_t fmt; /* byte 0 */ |
553 | | uint32_t id; /* byte 0 */ |
554 | | |
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 */ |
560 | | |
561 | | uint32_t txid; |
562 | | int isresponse; |
563 | | int otherframe; |
564 | | |
565 | | } rtmpt_packet_t; |
566 | | |
567 | | /* Represents a header or a chunk that is split over two TCP |
568 | | * segments |
569 | | */ |
570 | | typedef struct rtmpt_frag { |
571 | | int ishdr; |
572 | | uint32_t seq; |
573 | | uint32_t lastseq; |
574 | | int have; |
575 | | int len; |
576 | | |
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; |
582 | | |
583 | | /* The full message header information for the last packet on a particular |
584 | | * ID - used for defaulting short headers |
585 | | */ |
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 */ |
592 | | |
593 | | wmem_tree_t *packets; |
594 | | } rtmpt_id_t; |
595 | | |
596 | | /* Historical view of a whole TCP connection |
597 | | */ |
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; |
606 | | |
607 | | static void |
608 | | rtmpt_packet_mark_depended(void *data, void *user_data) |
609 | 68 | { |
610 | 68 | frame_data *fd = (frame_data *)user_data; |
611 | 68 | uint32_t frame_num = GPOINTER_TO_UINT(data); |
612 | 68 | mark_frame_as_depended_upon(fd, frame_num); |
613 | 68 | } |
614 | | |
615 | | /* Header length helpers */ |
616 | | |
617 | | static int rtmpt_basic_header_length(int id) |
618 | 4.22k | { |
619 | 4.22k | switch (id & 0x3f) { |
620 | 751 | case 0: return 2; |
621 | 77 | case 1: return 3; |
622 | 3.39k | default: return 1; |
623 | 4.22k | } |
624 | 4.22k | } |
625 | | |
626 | | static int rtmpt_message_header_length(int id) |
627 | 4.22k | { |
628 | 4.22k | switch ((id>>6) & 3) { |
629 | 1.19k | case 0: return 11; |
630 | 303 | case 1: return 7; |
631 | 558 | case 2: return 3; |
632 | 2.16k | default: return 0; |
633 | 4.22k | } |
634 | 4.22k | } |
635 | | |
636 | | /* Lightweight access to AMF0 blobs - more complete dissection is done |
637 | | * in dissect_rtmpt_body_command */ |
638 | | |
639 | | static uint32_t |
640 | | rtmpt_get_amf_length(tvbuff_t *tvb, int offset, proto_item* pi) |
641 | 4 | { |
642 | 4 | uint8_t iObjType; |
643 | 4 | int remain = tvb_reported_length_remaining(tvb, offset); |
644 | 4 | uint32_t depth = 0; |
645 | 4 | uint32_t itemlen = 0; |
646 | 4 | uint32_t rv = 0; |
647 | 4 | unsigned iterations = MAX_AMF_ITERATIONS; |
648 | | |
649 | 6 | while (rv == 0 || depth > 0) { |
650 | | |
651 | 4 | if (--iterations == 0) { |
652 | 0 | expert_add_info(NULL, pi, &ei_amf_loop); |
653 | 0 | return 0; |
654 | 0 | } |
655 | | |
656 | 4 | if (depth > 0) { |
657 | 0 | if (remain-rv < 2) |
658 | 0 | return remain; |
659 | 0 | itemlen = tvb_get_ntohs(tvb, offset+rv) + 2; |
660 | 0 | if (remain-rv<itemlen+1) |
661 | 0 | return remain; |
662 | 0 | rv += itemlen; |
663 | 0 | } |
664 | | |
665 | 4 | if (remain-rv < 1) |
666 | 0 | return remain; |
667 | 4 | iObjType = tvb_get_uint8(tvb, offset+rv); |
668 | | |
669 | 4 | if (depth > 0 && itemlen == 2 && iObjType == AMF0_END_OF_OBJECT) { |
670 | 0 | rv++; |
671 | 0 | depth--; |
672 | 0 | continue; |
673 | 0 | } |
674 | | |
675 | 4 | switch (iObjType) { |
676 | 2 | case AMF0_NUMBER: |
677 | 2 | itemlen = 9; |
678 | 2 | break; |
679 | 0 | case AMF0_BOOLEAN: |
680 | 0 | itemlen = 2; |
681 | 0 | break; |
682 | 0 | case AMF0_STRING: |
683 | 0 | if (remain-rv < 3) |
684 | 0 | return remain; |
685 | 0 | itemlen = tvb_get_ntohs(tvb, offset+rv+1) + 3; |
686 | 0 | break; |
687 | 0 | case AMF0_NULL: |
688 | 0 | case AMF0_UNDEFINED: |
689 | 0 | case AMF0_UNSUPPORTED: |
690 | 0 | itemlen = 1; |
691 | 0 | break; |
692 | 0 | case AMF0_DATE: |
693 | 0 | itemlen = 11; |
694 | 0 | break; |
695 | 0 | case AMF0_LONG_STRING: |
696 | 0 | case AMF0_XML: |
697 | 0 | if (remain-rv < 5) |
698 | 0 | return remain; |
699 | 0 | itemlen = tvb_get_ntohl(tvb, offset+rv+1) + 5; |
700 | 0 | break; |
701 | 0 | case AMF0_INT64: |
702 | 0 | itemlen = 9; |
703 | 0 | break; |
704 | 0 | case AMF0_OBJECT: |
705 | 0 | itemlen = 1; |
706 | 0 | depth++; |
707 | 0 | break; |
708 | 0 | case AMF0_ECMA_ARRAY: |
709 | 0 | itemlen = 5; |
710 | 0 | depth++; |
711 | 0 | break; |
712 | 2 | default: |
713 | 2 | return remain; |
714 | 4 | } |
715 | | |
716 | 2 | if (remain-rv < itemlen) |
717 | 0 | return remain; |
718 | 2 | rv += itemlen; |
719 | | |
720 | 2 | } |
721 | | |
722 | 2 | return rv; |
723 | 4 | } |
724 | | |
725 | | static char * |
726 | | rtmpt_get_amf_param(wmem_allocator_t* allocator, tvbuff_t *tvb, int offset, proto_item* pi, int param, const char *prop) |
727 | 0 | { |
728 | 0 | uint32_t remain = tvb_reported_length_remaining(tvb, offset); |
729 | 0 | uint32_t itemlen; |
730 | 0 | uint32_t iStringLength; |
731 | |
|
732 | 0 | while (remain > 0 && param > 0) { |
733 | 0 | itemlen = rtmpt_get_amf_length(tvb, offset, pi); |
734 | 0 | if (itemlen == 0) |
735 | 0 | break; |
736 | 0 | offset += itemlen; |
737 | 0 | remain -= itemlen; |
738 | 0 | param--; |
739 | 0 | } |
740 | |
|
741 | 0 | if (remain > 0 && param == 0) { |
742 | 0 | uint8_t iObjType = tvb_get_uint8(tvb, offset); |
743 | |
|
744 | 0 | if (!prop && iObjType == AMF0_STRING && remain >= 3) { |
745 | 0 | iStringLength = tvb_get_ntohs(tvb, offset+1); |
746 | 0 | if (remain >= iStringLength+3) { |
747 | 0 | return (char*)tvb_get_string_enc(allocator, tvb, offset+3, iStringLength, ENC_ASCII); |
748 | 0 | } |
749 | 0 | } |
750 | | |
751 | 0 | if (prop && iObjType == AMF0_OBJECT) { |
752 | 0 | offset++; |
753 | 0 | remain--; |
754 | |
|
755 | 0 | while (remain > 2) { |
756 | 0 | uint32_t iPropLength = tvb_get_ntohs(tvb, offset); |
757 | 0 | if (remain < 2+iPropLength+3) |
758 | 0 | break; |
759 | | |
760 | 0 | if (tvb_strneql(tvb, offset+2, prop, strlen(prop)) == 0) { |
761 | 0 | if (tvb_get_uint8(tvb, offset+2+iPropLength) != AMF0_STRING) |
762 | 0 | break; |
763 | | |
764 | 0 | iStringLength = tvb_get_ntohs(tvb, offset+2+iPropLength+1); |
765 | 0 | if (remain < 2+iPropLength+3+iStringLength) |
766 | 0 | break; |
767 | | |
768 | 0 | return (char*)tvb_get_string_enc(allocator, tvb, offset+2+iPropLength+3, iStringLength, ENC_ASCII); |
769 | 0 | } |
770 | | |
771 | 0 | itemlen = rtmpt_get_amf_length(tvb, offset+2+iPropLength, pi); |
772 | 0 | if (itemlen == 0) |
773 | 0 | break; |
774 | 0 | offset += 2+iPropLength+itemlen; |
775 | 0 | remain -= 2+iPropLength+itemlen; |
776 | 0 | } |
777 | 0 | } |
778 | 0 | } |
779 | | |
780 | 0 | return NULL; |
781 | 0 | } |
782 | | |
783 | | static uint32_t |
784 | | rtmpt_get_amf_txid(tvbuff_t *tvb, int offset, proto_item* pi) |
785 | 21 | { |
786 | 21 | uint32_t remain = tvb_reported_length_remaining(tvb, offset); |
787 | | |
788 | 21 | if (remain > 0) { |
789 | 4 | uint32_t itemlen = rtmpt_get_amf_length(tvb, offset, pi); |
790 | 4 | if (itemlen == 0 || remain < itemlen) |
791 | 0 | return 0; |
792 | 4 | offset += itemlen; |
793 | 4 | remain -= itemlen; |
794 | 4 | } |
795 | 21 | if (remain >= 9) { |
796 | 2 | uint8_t iObjType = tvb_get_uint8(tvb, offset); |
797 | 2 | if (iObjType == AMF0_NUMBER) { |
798 | 0 | return (uint32_t)tvb_get_ntohieee_double(tvb, offset+1); |
799 | 0 | } |
800 | 2 | } |
801 | | |
802 | 21 | return 0; |
803 | 21 | } |
804 | | |
805 | | |
806 | | /* Generate a useful description for various packet types */ |
807 | | |
808 | | static char * |
809 | | rtmpt_get_packet_desc(wmem_allocator_t* allocator, 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) |
811 | 1.81k | { |
812 | 1.81k | if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE || tp->cmd == RTMPT_TYPE_ABORT_MESSAGE || |
813 | 1.51k | tp->cmd == RTMPT_TYPE_ACKNOWLEDGEMENT || tp->cmd == RTMPT_TYPE_WINDOW) { |
814 | 364 | if (tp->len >= 4 && remain >= 4) { |
815 | 210 | *deschasopcode = true; |
816 | 210 | return wmem_strdup_printf(allocator, "%s %d", |
817 | 210 | val_to_str(allocator, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"), |
818 | 210 | tvb_get_ntohl(tvb, offset)); |
819 | 210 | } |
820 | | |
821 | 1.45k | } else if (tp->cmd == RTMPT_TYPE_PEER_BANDWIDTH) { |
822 | 3 | if (tp->len >= 5 && remain >= 5) { |
823 | 2 | *deschasopcode = true; |
824 | 2 | return wmem_strdup_printf(allocator, "%s %d,%s", |
825 | 2 | val_to_str(allocator, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"), |
826 | 2 | tvb_get_ntohl(tvb, offset), |
827 | 2 | val_to_str(allocator, tvb_get_uint8(tvb, offset+4), rtmpt_limit_vals, "Unknown (%d)")); |
828 | 2 | } |
829 | | |
830 | 1.45k | } else if (tp->cmd == RTMPT_TYPE_UCM) { |
831 | 10 | uint16_t iUCM = -1; |
832 | 10 | const char *sFunc; |
833 | 10 | const char *sParam = ""; |
834 | | |
835 | 10 | if (tp->len < 2 || remain < 2) |
836 | 7 | return NULL; |
837 | | |
838 | 3 | iUCM = tvb_get_ntohs(tvb, offset); |
839 | 3 | sFunc = try_val_to_str(iUCM, rtmpt_ucm_vals); |
840 | 3 | if (sFunc == NULL) { |
841 | 1 | *deschasopcode = true; |
842 | 1 | sFunc = wmem_strdup_printf(allocator, "User Control Message 0x%01x", iUCM); |
843 | 1 | } |
844 | | |
845 | 3 | if (iUCM == RTMPT_UCM_STREAM_BEGIN || iUCM == RTMPT_UCM_STREAM_EOF || |
846 | 2 | iUCM == RTMPT_UCM_STREAM_DRY || iUCM == RTMPT_UCM_STREAM_ISRECORDED) { |
847 | 2 | if (tp->len >= 6 && remain >= 6) { |
848 | 2 | sParam = wmem_strdup_printf(allocator, " %d", tvb_get_ntohl(tvb, offset+2)); |
849 | 2 | } |
850 | 2 | } else if (iUCM == RTMPT_UCM_SET_BUFFER) { |
851 | 0 | if (tp->len >= 10 && remain >= 10) { |
852 | 0 | sParam = wmem_strdup_printf(allocator, " %d,%dms", |
853 | 0 | tvb_get_ntohl(tvb, offset+2), |
854 | 0 | tvb_get_ntohl(tvb, offset+6)); |
855 | 0 | } |
856 | 0 | } |
857 | | |
858 | 3 | return wmem_strdup_printf(allocator, "%s%s", sFunc, sParam); |
859 | | |
860 | 1.44k | } else if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || |
861 | 1.42k | tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) { |
862 | 21 | uint32_t slen = 0; |
863 | 21 | uint32_t soff = 0; |
864 | 21 | char *sFunc = NULL; |
865 | 21 | char *sParam = NULL; |
866 | | |
867 | 21 | if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) { |
868 | 17 | soff = 1; |
869 | 17 | } |
870 | 21 | if (tp->len >= 3+soff && remain >= 3+soff) { |
871 | 4 | slen = tvb_get_ntohs(tvb, offset+1+soff); |
872 | 4 | } |
873 | 21 | if (slen > 0) { |
874 | 4 | sFunc = (char*)tvb_get_string_enc(allocator, tvb, offset+3+soff, slen, ENC_ASCII); |
875 | 4 | ws_debug("got function call '%s'", sFunc); |
876 | | |
877 | 4 | if (strcmp(sFunc, "connect") == 0) { |
878 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 2, "app"); |
879 | 4 | } else if (strcmp(sFunc, "play") == 0) { |
880 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, NULL); |
881 | 4 | } else if (strcmp(sFunc, "play2") == 0) { |
882 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, "streamName"); |
883 | 4 | } else if (strcmp(sFunc, "releaseStream") == 0) { |
884 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, NULL); |
885 | 4 | } else if (strcmp(sFunc, "FCPublish") == 0) { |
886 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, NULL); |
887 | 4 | } else if (strcmp(sFunc, "publish") == 0) { |
888 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, NULL); |
889 | 4 | } else if (strcmp(sFunc, "onStatus") == 0) { |
890 | 0 | if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3) { |
891 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, "code"); |
892 | 0 | } else { |
893 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 1, "code"); |
894 | 0 | } |
895 | 4 | } else if (strcmp(sFunc, "onPlayStatus") == 0) { |
896 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 1, "code"); |
897 | 4 | } else if (strcmp(sFunc, "_result") == 0) { |
898 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, "code"); |
899 | 0 | tp->isresponse = true; |
900 | 4 | } else if (strcmp(sFunc, "_error") == 0) { |
901 | 0 | sParam = rtmpt_get_amf_param(allocator, tvb, offset+soff, pi, 3, "code"); |
902 | 0 | tp->isresponse = true; |
903 | 0 | } |
904 | | |
905 | 4 | if (tp->txid != 0 && tp->otherframe == 0) { |
906 | 0 | tp->otherframe = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->txids[cdir^1], tp->txid)); |
907 | 0 | if (tp->otherframe) { |
908 | 0 | ws_debug("got otherframe=%d", tp->otherframe); |
909 | 0 | } |
910 | 0 | } |
911 | 4 | } |
912 | | |
913 | 21 | if (sFunc) { |
914 | 2 | if (sParam) { |
915 | 0 | return wmem_strdup_printf(allocator, "%s('%s')", sFunc, sParam); |
916 | 2 | } else { |
917 | 2 | return wmem_strdup_printf(allocator, "%s()", sFunc); |
918 | 2 | } |
919 | 2 | } |
920 | 21 | } |
921 | | |
922 | 1.59k | return NULL; |
923 | 1.81k | } |
924 | | |
925 | | |
926 | | /* Tree dissection helpers for various packet body forms */ |
927 | | |
928 | | static void |
929 | | dissect_rtmpt_body_scm(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree, unsigned scm) |
930 | 219 | { |
931 | 219 | switch (scm) { |
932 | 159 | case RTMPT_TYPE_CHUNK_SIZE: |
933 | 159 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_chunksize, tvb, offset, 4, ENC_BIG_ENDIAN); |
934 | 159 | break; |
935 | 1 | case RTMPT_TYPE_ABORT_MESSAGE: |
936 | 1 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_csid, tvb, offset, 4, ENC_BIG_ENDIAN); |
937 | 1 | break; |
938 | 8 | case RTMPT_TYPE_ACKNOWLEDGEMENT: |
939 | 8 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_seq, tvb, offset, 4, ENC_BIG_ENDIAN); |
940 | 8 | break; |
941 | 3 | case RTMPT_TYPE_UCM: |
942 | 3 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_ucm_eventtype, tvb, offset, 2, ENC_BIG_ENDIAN); |
943 | 3 | break; |
944 | 45 | case RTMPT_TYPE_WINDOW: |
945 | 45 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN); |
946 | 45 | break; |
947 | 3 | case RTMPT_TYPE_PEER_BANDWIDTH: |
948 | 3 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_was, tvb, offset, 4, ENC_BIG_ENDIAN); |
949 | 3 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_scm_limittype, tvb, offset+4, 1, ENC_BIG_ENDIAN); |
950 | 3 | break; |
951 | 219 | } |
952 | 219 | } |
953 | | |
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); |
956 | | |
957 | | /* |
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. |
961 | | */ |
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) |
965 | 0 | { |
966 | 0 | proto_item *prop_ti; |
967 | 0 | proto_tree *prop_tree; |
968 | 0 | proto_tree *name_tree; |
969 | 0 | unsigned iStringLength; |
970 | 0 | char *iStringValue; |
971 | 0 | unsigned count = 0; |
972 | | |
973 | | /* |
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. |
978 | | */ |
979 | 0 | for (;;) { |
980 | | /* UTF-8: property name */ |
981 | 0 | iStringLength = tvb_get_ntohs(tvb, offset); |
982 | 0 | if (iStringLength == 0 && |
983 | 0 | tvb_get_uint8(tvb, offset + 2) == AMF0_END_OF_OBJECT) |
984 | 0 | break; |
985 | 0 | count++; |
986 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 2, iStringLength, ENC_ASCII); |
987 | 0 | prop_tree = proto_tree_add_subtree_format(tree, tvb, offset, -1, |
988 | 0 | ett_amf_property, &prop_ti, "Property '%s'", |
989 | 0 | iStringValue); |
990 | |
|
991 | 0 | name_tree = proto_tree_add_subtree_format(prop_tree, tvb, |
992 | 0 | offset, 2+iStringLength, |
993 | 0 | ett_amf_string, NULL, "Name: %s", iStringValue); |
994 | |
|
995 | 0 | proto_tree_add_uint(name_tree, hf_amf_stringlength, tvb, offset, 2, iStringLength); |
996 | 0 | offset += 2; |
997 | 0 | proto_tree_add_item(name_tree, hf_amf_string, tvb, offset, iStringLength, ENC_UTF_8); |
998 | 0 | offset += iStringLength; |
999 | | |
1000 | | /* value-type: property value */ |
1001 | 0 | offset = dissect_amf0_value_type(tvb, pinfo, offset, prop_tree, amf3_encoding, prop_ti); |
1002 | 0 | proto_item_set_end(prop_ti, tvb, offset); |
1003 | 0 | } |
1004 | 0 | proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, offset, 3, ENC_NA); |
1005 | 0 | offset += 3; |
1006 | |
|
1007 | 0 | *countp = count; |
1008 | |
|
1009 | 0 | return offset; |
1010 | 0 | } |
1011 | | |
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) |
1015 | 4 | { |
1016 | 4 | uint8_t iObjType; |
1017 | 4 | proto_item *ti; |
1018 | 4 | proto_tree *val_tree; |
1019 | 4 | int iValueOffset = offset; |
1020 | 4 | uint32_t iIntegerValue; |
1021 | 4 | double iDoubleValue; |
1022 | 4 | bool iBooleanValue; |
1023 | 4 | unsigned iStringLength; |
1024 | 4 | char *iStringValue; |
1025 | 4 | unsigned iArrayLength; |
1026 | 4 | unsigned i; |
1027 | 4 | nstime_t t; |
1028 | 4 | int64_t iInteger64Value; |
1029 | 4 | unsigned count; |
1030 | | |
1031 | 4 | iObjType = tvb_get_uint8(tvb, offset); |
1032 | 4 | if (parent_ti != NULL) |
1033 | 0 | proto_item_append_text(parent_ti, " %s", |
1034 | 0 | val_to_str_const(iObjType, amf0_type_vals, "Unknown")); |
1035 | 4 | switch (iObjType) { |
1036 | | |
1037 | 0 | case AMF0_OBJECT: |
1038 | | /* |
1039 | | * For object types, make the top-level protocol tree |
1040 | | * item a field for that type. |
1041 | | */ |
1042 | 0 | ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA); |
1043 | 0 | val_tree = proto_item_add_subtree(ti, ett_amf_value); |
1044 | 0 | break; |
1045 | | |
1046 | 0 | case AMF0_ECMA_ARRAY: |
1047 | | /* |
1048 | | * For ECMA array types, make the top-level protocol tree |
1049 | | * item a field for that type. |
1050 | | */ |
1051 | 0 | ti = proto_tree_add_item(tree, hf_amf_ecmaarray, tvb, offset, -1, ENC_NA); |
1052 | 0 | val_tree = proto_item_add_subtree(ti, ett_amf_value); |
1053 | 0 | break; |
1054 | | |
1055 | 0 | case AMF0_STRICT_ARRAY: |
1056 | | /* |
1057 | | * For strict array types, make the top-level protocol tree |
1058 | | * item a field for that type. |
1059 | | */ |
1060 | 0 | ti = proto_tree_add_item(tree, hf_amf_strictarray, tvb, offset, -1, ENC_NA); |
1061 | 0 | val_tree = proto_item_add_subtree(ti, ett_amf_value); |
1062 | 0 | break; |
1063 | | |
1064 | 4 | default: |
1065 | | /* |
1066 | | * For all other types, make it just a text item; the |
1067 | | * field for that type will be used for the value. |
1068 | | */ |
1069 | 4 | val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti, |
1070 | 4 | val_to_str_const(iObjType, amf0_type_vals, "Unknown")); |
1071 | 4 | break; |
1072 | 4 | } |
1073 | | |
1074 | 4 | proto_tree_add_uint(val_tree, hf_amf_amf0_type, tvb, iValueOffset, 1, iObjType); |
1075 | 4 | iValueOffset++; |
1076 | | |
1077 | 4 | increment_dissection_depth(pinfo); |
1078 | 4 | switch (iObjType) { |
1079 | 2 | case AMF0_NUMBER: |
1080 | 2 | iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset); |
1081 | 2 | proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue); |
1082 | 2 | iValueOffset += 8; |
1083 | 2 | proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue); |
1084 | 2 | if (parent_ti != NULL) |
1085 | 0 | proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue); |
1086 | 2 | break; |
1087 | 0 | case AMF0_BOOLEAN: |
1088 | 0 | iBooleanValue = tvb_get_uint8(tvb, iValueOffset); |
1089 | 0 | proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, iValueOffset, 1, iBooleanValue); |
1090 | 0 | iValueOffset += 1; |
1091 | 0 | proto_item_append_text(ti, iBooleanValue ? " true" : " false"); |
1092 | 0 | if (parent_ti != NULL) |
1093 | 0 | proto_item_append_text(parent_ti, iBooleanValue ? " true" : " false"); |
1094 | 0 | break; |
1095 | 0 | case AMF0_STRING: |
1096 | 0 | iStringLength = tvb_get_ntohs(tvb, iValueOffset); |
1097 | 0 | proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength); |
1098 | 0 | iValueOffset += 2; |
1099 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA); |
1100 | 0 | if (iStringLength != 0) |
1101 | 0 | proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue); |
1102 | 0 | iValueOffset += iStringLength; |
1103 | 0 | proto_item_append_text(ti, " '%s'", iStringValue); |
1104 | 0 | if (parent_ti != NULL) |
1105 | 0 | proto_item_append_text(parent_ti, " '%s'", iStringValue); |
1106 | 0 | break; |
1107 | 0 | case AMF0_OBJECT: |
1108 | 0 | iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding); |
1109 | 0 | proto_item_append_text(ti, " (%u items)", count); |
1110 | 0 | break; |
1111 | 0 | case AMF0_NULL: |
1112 | 0 | case AMF0_UNDEFINED: |
1113 | 0 | break; |
1114 | 0 | case AMF0_REFERENCE: |
1115 | 0 | iIntegerValue = tvb_get_ntohs(tvb, iValueOffset); |
1116 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, 2, iIntegerValue); |
1117 | 0 | iValueOffset += 2; |
1118 | 0 | proto_item_append_text(ti, " %d", iIntegerValue); |
1119 | 0 | break; |
1120 | 0 | case AMF0_ECMA_ARRAY: |
1121 | | /* |
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. |
1125 | | * |
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. |
1131 | | */ |
1132 | 0 | iArrayLength = tvb_get_ntohl(tvb, iValueOffset); |
1133 | 0 | proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength); |
1134 | 0 | iValueOffset += 4; |
1135 | 0 | iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding); |
1136 | 0 | proto_item_append_text(ti, " (%u items)", count); |
1137 | 0 | break; |
1138 | 0 | case AMF0_END_OF_OBJECT: |
1139 | 0 | proto_tree_add_item(tree, hf_amf_end_of_object_marker, tvb, iValueOffset, 3, ENC_NA); |
1140 | 0 | iValueOffset += 3; |
1141 | 0 | break; |
1142 | 0 | case AMF0_STRICT_ARRAY: |
1143 | | /* |
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. |
1148 | | */ |
1149 | 0 | iArrayLength = tvb_get_ntohl(tvb, iValueOffset); |
1150 | 0 | proto_tree_add_uint(val_tree, hf_amf_arraylength, tvb, iValueOffset, 4, iArrayLength); |
1151 | 0 | iValueOffset += 4; |
1152 | 0 | for (i = 0; i < iArrayLength; i++) |
1153 | 0 | iValueOffset = dissect_amf0_value_type(tvb, pinfo, iValueOffset, val_tree, amf3_encoding, NULL); |
1154 | 0 | proto_item_append_text(ti, " (%u items)", iArrayLength); |
1155 | 0 | break; |
1156 | 0 | case AMF0_DATE: |
1157 | 0 | iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset); |
1158 | 0 | t.secs = (time_t)(iDoubleValue/1000); |
1159 | 0 | t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000); |
1160 | 0 | proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t); |
1161 | 0 | iValueOffset += 8; |
1162 | 0 | proto_item_append_text(ti, " %s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true)); |
1163 | 0 | if (parent_ti != NULL) |
1164 | 0 | proto_item_append_text(parent_ti, " %s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true)); |
1165 | | /* time-zone */ |
1166 | 0 | iValueOffset += 2; |
1167 | 0 | break; |
1168 | 0 | case AMF0_LONG_STRING: |
1169 | 0 | case AMF0_XML: /* same representation */ |
1170 | 0 | iStringLength = tvb_get_ntohl(tvb, iValueOffset); |
1171 | 0 | proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength); |
1172 | 0 | iValueOffset += 4; |
1173 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA); |
1174 | 0 | if (iStringLength != 0) |
1175 | 0 | proto_tree_add_string(val_tree, (iObjType == AMF0_XML) ? hf_amf_xml_doc : hf_amf_longstring, tvb, iValueOffset, iStringLength, iStringValue); |
1176 | 0 | iValueOffset += iStringLength; |
1177 | 0 | proto_item_append_text(ti, " '%s'", iStringValue); |
1178 | 0 | if (parent_ti != NULL) |
1179 | 0 | proto_item_append_text(parent_ti, " '%s'", iStringValue); |
1180 | 0 | break; |
1181 | 0 | case AMF0_UNSUPPORTED: |
1182 | 0 | break; |
1183 | 0 | case AMF0_TYPED_OBJECT: |
1184 | | /* class-name */ |
1185 | 0 | iStringLength = tvb_get_ntohs(tvb, iValueOffset); |
1186 | 0 | proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, 2, iStringLength); |
1187 | 0 | iValueOffset += 2; |
1188 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA); |
1189 | 0 | proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue); |
1190 | 0 | iValueOffset += iStringLength; |
1191 | 0 | iValueOffset = dissect_amf0_property_list(tvb, pinfo, iValueOffset, val_tree, &count, amf3_encoding); |
1192 | 0 | break; |
1193 | 0 | case AMF0_AMF3_MARKER: |
1194 | 0 | *amf3_encoding = true; |
1195 | 0 | break; |
1196 | 0 | case AMF0_INT64: |
1197 | 0 | iInteger64Value = tvb_get_ntoh64(tvb, iValueOffset); |
1198 | 0 | proto_tree_add_int64(val_tree, hf_amf_int64, tvb, iValueOffset, 8, iInteger64Value); |
1199 | 0 | iValueOffset += 8; |
1200 | 0 | proto_item_append_text(ti," %" PRId64, iInteger64Value); |
1201 | 0 | if (parent_ti != NULL) |
1202 | 0 | proto_item_append_text(parent_ti," %" PRId64, iInteger64Value); |
1203 | 0 | break; |
1204 | 2 | default: |
1205 | | /* |
1206 | | * If we can't determine the length, don't carry on; |
1207 | | * just skip to the end of the tvbuff. |
1208 | | */ |
1209 | 2 | iValueOffset = tvb_reported_length(tvb); |
1210 | 2 | break; |
1211 | 4 | } |
1212 | 4 | decrement_dissection_depth(pinfo); |
1213 | 4 | proto_item_set_end(ti, tvb, iValueOffset); |
1214 | 4 | return iValueOffset; |
1215 | 4 | } |
1216 | | |
1217 | | static uint32_t |
1218 | | amf_get_u29(tvbuff_t *tvb, int offset, unsigned *lenp) |
1219 | 0 | { |
1220 | 0 | unsigned len = 0; |
1221 | 0 | uint8_t iByte; |
1222 | 0 | uint32_t iValue; |
1223 | |
|
1224 | 0 | iByte = tvb_get_uint8(tvb, offset); |
1225 | 0 | iValue = (iByte & 0x7F); |
1226 | 0 | offset++; |
1227 | 0 | len++; |
1228 | 0 | if (!(iByte & 0x80)) { |
1229 | | /* 1 byte value */ |
1230 | 0 | *lenp = len; |
1231 | 0 | return iValue; |
1232 | 0 | } |
1233 | 0 | iByte = tvb_get_uint8(tvb, offset); |
1234 | 0 | iValue = (iValue << 7) | (iByte & 0x7F); |
1235 | 0 | offset++; |
1236 | 0 | len++; |
1237 | 0 | if (!(iByte & 0x80)) { |
1238 | | /* 2 byte value */ |
1239 | 0 | *lenp = len; |
1240 | 0 | return iValue; |
1241 | 0 | } |
1242 | 0 | iByte = tvb_get_uint8(tvb, offset); |
1243 | 0 | iValue = (iValue << 7) | (iByte & 0x7F); |
1244 | 0 | offset++; |
1245 | 0 | len++; |
1246 | 0 | if (!(iByte & 0x80)) { |
1247 | | /* 3 byte value */ |
1248 | 0 | *lenp = len; |
1249 | 0 | return iValue; |
1250 | 0 | } |
1251 | 0 | iByte = tvb_get_uint8(tvb, offset); |
1252 | 0 | iValue = (iValue << 8) | iByte; |
1253 | 0 | len++; |
1254 | 0 | *lenp = len; |
1255 | 0 | return iValue; |
1256 | 0 | } |
1257 | | |
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) |
1261 | 0 | { |
1262 | 0 | uint8_t iObjType; |
1263 | 0 | proto_item *ti; |
1264 | 0 | proto_tree *val_tree; |
1265 | 0 | int iValueOffset = offset; |
1266 | 0 | unsigned iValueLength; |
1267 | 0 | uint32_t iIntegerValue; |
1268 | 0 | double iDoubleValue; |
1269 | 0 | unsigned iStringLength; |
1270 | 0 | char *iStringValue; |
1271 | 0 | unsigned iArrayLength; |
1272 | 0 | proto_item *subval_ti; |
1273 | 0 | proto_tree *subval_tree; |
1274 | 0 | unsigned i; |
1275 | 0 | bool iTypeIsDynamic; |
1276 | 0 | unsigned iTraitCount; |
1277 | 0 | proto_item *traits_ti; |
1278 | 0 | proto_tree *traits_tree; |
1279 | 0 | proto_tree *name_tree; |
1280 | 0 | proto_tree *member_tree; |
1281 | 0 | uint8_t *iByteArrayValue; |
1282 | |
|
1283 | 0 | iObjType = tvb_get_uint8(tvb, offset); |
1284 | 0 | if (parent_ti != NULL) |
1285 | 0 | proto_item_append_text(parent_ti, " %s", |
1286 | 0 | val_to_str_const(iObjType, amf3_type_vals, "Unknown")); |
1287 | 0 | switch (iObjType) { |
1288 | | |
1289 | 0 | case AMF3_ARRAY: |
1290 | | /* |
1291 | | * For array types, make the top-level protocol tree |
1292 | | * item a field for that type. |
1293 | | */ |
1294 | 0 | ti = proto_tree_add_item(tree, hf_amf_array, tvb, offset, -1, ENC_NA); |
1295 | 0 | val_tree = proto_item_add_subtree(ti, ett_amf_value); |
1296 | 0 | break; |
1297 | | |
1298 | 0 | case AMF3_OBJECT: |
1299 | | /* |
1300 | | * For object types, make the top-level protocol tree |
1301 | | * item a field for that type. |
1302 | | */ |
1303 | 0 | ti = proto_tree_add_item(tree, hf_amf_object, tvb, offset, -1, ENC_NA); |
1304 | 0 | val_tree = proto_item_add_subtree(ti, ett_amf_value); |
1305 | 0 | break; |
1306 | | |
1307 | 0 | default: |
1308 | | /* |
1309 | | * For all other types, make it just a text item; the |
1310 | | * field for that type will be used for the value. |
1311 | | */ |
1312 | 0 | val_tree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_amf_value, &ti, |
1313 | 0 | val_to_str_const(iObjType, amf3_type_vals, "Unknown")); |
1314 | 0 | break; |
1315 | 0 | } |
1316 | | |
1317 | 0 | proto_tree_add_uint(val_tree, hf_amf_amf3_type, tvb, iValueOffset, 1, iObjType); |
1318 | 0 | iValueOffset++; |
1319 | |
|
1320 | 0 | increment_dissection_depth(pinfo); |
1321 | 0 | switch (iObjType) { |
1322 | 0 | case AMF3_UNDEFINED: |
1323 | 0 | case AMF3_NULL: |
1324 | 0 | break; |
1325 | 0 | case AMF3_FALSE: |
1326 | 0 | proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, false); |
1327 | 0 | proto_item_append_text(ti, " false"); |
1328 | 0 | break; |
1329 | 0 | case AMF3_TRUE: |
1330 | 0 | proto_tree_add_boolean(val_tree, hf_amf_boolean, tvb, 0, 0, true); |
1331 | 0 | proto_item_append_text(ti, " true"); |
1332 | 0 | break; |
1333 | 0 | case AMF3_INTEGER: |
1334 | | /* XXX - signed or unsigned? */ |
1335 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1336 | 0 | proto_tree_add_uint(val_tree, hf_amf_integer, tvb, iValueOffset, iValueLength, iIntegerValue); |
1337 | 0 | proto_item_append_text(ti, " %u", iIntegerValue); |
1338 | 0 | if (parent_ti != NULL) |
1339 | 0 | proto_item_append_text(parent_ti, " %u", iIntegerValue); |
1340 | 0 | iValueOffset += iValueLength; |
1341 | 0 | break; |
1342 | 0 | case AMF3_DOUBLE: |
1343 | 0 | iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset); |
1344 | 0 | proto_tree_add_double(val_tree, hf_amf_number, tvb, iValueOffset, 8, iDoubleValue); |
1345 | 0 | iValueOffset += 8; |
1346 | 0 | proto_item_append_text(ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue); |
1347 | 0 | if (parent_ti != NULL) |
1348 | 0 | proto_item_append_text(parent_ti, " %." G_STRINGIFY(DBL_DIG) "g", iDoubleValue); |
1349 | 0 | break; |
1350 | 0 | case AMF3_STRING: |
1351 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1352 | 0 | if (iIntegerValue & 0x00000001) { |
1353 | | /* the upper 28 bits of the integer value is a string length */ |
1354 | 0 | iStringLength = iIntegerValue >> 1; |
1355 | 0 | proto_tree_add_uint(val_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength); |
1356 | 0 | iValueOffset += iValueLength; |
1357 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset, iStringLength, ENC_UTF_8|ENC_NA); |
1358 | 0 | if (iStringLength != 0) |
1359 | 0 | proto_tree_add_string(val_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue); |
1360 | 0 | iValueOffset += iStringLength; |
1361 | 0 | proto_item_append_text(ti, " '%s'", iStringValue); |
1362 | 0 | if (parent_ti != NULL) |
1363 | 0 | proto_item_append_text(parent_ti, " '%s'", iStringValue); |
1364 | 0 | } else { |
1365 | | /* the upper 28 bits of the integer value are a string reference index */ |
1366 | 0 | proto_tree_add_uint(val_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1367 | 0 | iValueOffset += iValueLength; |
1368 | 0 | proto_item_append_text(ti, " reference %u", iIntegerValue >> 1); |
1369 | 0 | if (parent_ti != NULL) |
1370 | 0 | proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1); |
1371 | 0 | } |
1372 | 0 | break; |
1373 | 0 | case AMF3_DATE: |
1374 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1375 | 0 | if (iIntegerValue & 0x00000001) { |
1376 | | /* |
1377 | | * The upper 28 bits of the integer value are |
1378 | | * ignored; what follows is a double |
1379 | | * containing milliseconds since the Epoch. |
1380 | | */ |
1381 | 0 | nstime_t t; |
1382 | |
|
1383 | 0 | iValueOffset += iValueLength; |
1384 | 0 | iDoubleValue = tvb_get_ntohieee_double(tvb, iValueOffset); |
1385 | 0 | t.secs = (time_t)(iDoubleValue/1000); |
1386 | 0 | t.nsecs = (int)((iDoubleValue - 1000*(double)t.secs) * 1000000); |
1387 | 0 | proto_tree_add_time(val_tree, hf_amf_date, tvb, iValueOffset, 8, &t); |
1388 | 0 | iValueOffset += 8; |
1389 | 0 | proto_item_append_text(ti, "%s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true)); |
1390 | 0 | if (parent_ti != NULL) |
1391 | 0 | proto_item_append_text(parent_ti, "%s", abs_time_to_str(pinfo->pool, &t, ABSOLUTE_TIME_LOCAL, true)); |
1392 | 0 | } else { |
1393 | | /* the upper 28 bits of the integer value are an object reference index */ |
1394 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1395 | 0 | iValueOffset += iValueLength; |
1396 | 0 | proto_item_append_text(ti, " object reference %u", iIntegerValue >> 1); |
1397 | 0 | if (parent_ti != NULL) |
1398 | 0 | proto_item_append_text(parent_ti, " object reference %u", iIntegerValue >> 1); |
1399 | 0 | } |
1400 | 0 | break; |
1401 | 0 | case AMF3_ARRAY: |
1402 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1403 | 0 | if (iIntegerValue & 0x00000001) { |
1404 | | /* |
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. |
1408 | | */ |
1409 | 0 | iArrayLength = iIntegerValue >> 1; |
1410 | 0 | proto_tree_add_uint(val_tree, hf_amf_arraydenselength, tvb, iValueOffset, iValueLength, iArrayLength); |
1411 | 0 | iValueOffset += iValueLength; |
1412 | | |
1413 | | /* |
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. |
1420 | | * |
1421 | | * Dissect the associative portion. |
1422 | | */ |
1423 | 0 | for (;;) { |
1424 | | /* Fetch the name */ |
1425 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1426 | 0 | if (iIntegerValue & 0x00000001) { |
1427 | | /* the upper 28 bits of the integer value is a string length */ |
1428 | 0 | iStringLength = iIntegerValue >> 1; |
1429 | 0 | if (iStringLength == 0) { |
1430 | | /* null name marks the end of the associative part */ |
1431 | 0 | proto_tree_add_item(val_tree, hf_amf_end_of_associative_part, tvb, iValueOffset, iValueLength, ENC_NA); |
1432 | 0 | iValueOffset += iValueLength; |
1433 | 0 | break; |
1434 | 0 | } |
1435 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA); |
1436 | 0 | subval_tree = proto_tree_add_subtree(val_tree, tvb, iValueOffset, iStringLength, |
1437 | 0 | ett_amf_array_element, &subval_ti, iStringValue); |
1438 | 0 | proto_tree_add_uint(subval_tree, hf_amf_stringlength, tvb, iValueOffset, iValueLength, iStringLength); |
1439 | 0 | iValueOffset += iValueLength; |
1440 | 0 | proto_tree_add_string(subval_tree, hf_amf_string, tvb, iValueOffset, iStringLength, iStringValue); |
1441 | 0 | } else { |
1442 | | /* the upper 28 bits of the integer value are a string reference index */ |
1443 | 0 | subval_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength, |
1444 | 0 | ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1); |
1445 | 0 | proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1446 | 0 | } |
1447 | | |
1448 | | /* Fetch the value */ |
1449 | 0 | iObjType = tvb_get_uint8(tvb, offset); |
1450 | 0 | proto_item_append_text(subval_ti, "%s", |
1451 | 0 | val_to_str_const(iObjType, amf3_type_vals, "Unknown")); |
1452 | |
|
1453 | 0 | iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, subval_tree, subval_ti); |
1454 | 0 | } |
1455 | | |
1456 | | /* |
1457 | | * Dissect the dense portion. |
1458 | | */ |
1459 | 0 | for (i = 0; i < iArrayLength; i++) |
1460 | 0 | iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, val_tree, NULL); |
1461 | |
|
1462 | 0 | proto_item_set_end(ti, tvb, iValueOffset); |
1463 | 0 | } else { |
1464 | | /* the upper 28 bits of the integer value are an object reference index */ |
1465 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1466 | 0 | proto_item_append_text(ti, " reference %u", iIntegerValue >> 1); |
1467 | 0 | if (parent_ti != NULL) |
1468 | 0 | proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1); |
1469 | 0 | } |
1470 | 0 | break; |
1471 | 0 | case AMF3_OBJECT: |
1472 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1473 | 0 | if (iIntegerValue & 0x00000001) { |
1474 | 0 | if (iIntegerValue & 0x00000002) { |
1475 | 0 | if (iIntegerValue & 0x00000004) { |
1476 | | /* |
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. |
1484 | | */ |
1485 | 0 | iValueOffset += iValueLength; |
1486 | 0 | } else { |
1487 | | /* |
1488 | | * U29O-traits; the 0x00000008 bit |
1489 | | * specifies whether the type is |
1490 | | * dynamic. |
1491 | | */ |
1492 | 0 | iTypeIsDynamic = (iIntegerValue & 0x00000008) ? true : false; |
1493 | 0 | iTraitCount = iIntegerValue >> 4; |
1494 | 0 | proto_tree_add_uint(val_tree, hf_amf_traitcount, tvb, iValueOffset, iValueLength, iTraitCount); |
1495 | 0 | iValueOffset += iValueLength; |
1496 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1497 | 0 | if (iIntegerValue & 0x00000001) { |
1498 | | /* the upper 28 bits of the integer value is a string length */ |
1499 | 0 | iStringLength = iIntegerValue >> 1; |
1500 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA); |
1501 | 0 | traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, -1, |
1502 | 0 | ett_amf_traits, &traits_ti, "Traits for class %s (%u member names)", iStringValue, iTraitCount); |
1503 | 0 | name_tree = proto_tree_add_subtree_format(traits_tree, tvb, |
1504 | 0 | iValueOffset, |
1505 | 0 | iValueLength+iStringLength, |
1506 | 0 | ett_amf_string, NULL, "Class name: %s", |
1507 | 0 | iStringValue); |
1508 | 0 | proto_tree_add_uint(name_tree, hf_amf_classnamelength, tvb, iValueOffset, iValueLength, iStringLength); |
1509 | 0 | iValueOffset += iValueLength; |
1510 | 0 | proto_tree_add_string(name_tree, hf_amf_classname, tvb, iValueOffset, iStringLength, iStringValue); |
1511 | 0 | iValueOffset += iStringLength; |
1512 | 0 | } else { |
1513 | | /* the upper 28 bits of the integer value are a string reference index */ |
1514 | 0 | traits_tree = proto_tree_add_subtree_format(val_tree, tvb, iValueOffset, iValueLength, |
1515 | 0 | ett_amf_traits, &traits_ti, "Traits for class (reference %u for name)", iIntegerValue >> 1); |
1516 | 0 | proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1517 | 0 | iValueOffset += iValueLength; |
1518 | 0 | } |
1519 | 0 | for (i = 0; i < iTraitCount; i++) { |
1520 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1521 | 0 | if (iIntegerValue & 0x00000001) { |
1522 | | /* the upper 28 bits of the integer value is a string length */ |
1523 | 0 | iStringLength = iIntegerValue >> 1; |
1524 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA); |
1525 | 0 | member_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength+iStringLength, |
1526 | 0 | ett_amf_trait_member, NULL, "Member '%s'", iStringValue); |
1527 | 0 | proto_tree_add_uint(member_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength); |
1528 | 0 | iValueOffset += iValueLength; |
1529 | 0 | proto_tree_add_string(member_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue); |
1530 | 0 | iValueOffset += iStringLength; |
1531 | 0 | } else { |
1532 | | /* the upper 28 bits of the integer value are a string reference index */ |
1533 | 0 | proto_tree_add_uint(traits_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1534 | 0 | iValueOffset += iValueLength; |
1535 | 0 | } |
1536 | 0 | } |
1537 | 0 | for (i = 0; i < iTraitCount; i++) |
1538 | 0 | iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, traits_tree, NULL); |
1539 | 0 | if (iTypeIsDynamic) { |
1540 | 0 | for (;;) { |
1541 | | /* Fetch the name */ |
1542 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1543 | 0 | if (iIntegerValue & 0x00000001) { |
1544 | | /* the upper 28 bits of the integer value is a string length */ |
1545 | 0 | iStringLength = iIntegerValue >> 1; |
1546 | 0 | if (iStringLength == 0) { |
1547 | | /* null name marks the end of the associative part */ |
1548 | 0 | proto_tree_add_item(traits_tree, hf_amf_end_of_dynamic_members, tvb, iValueOffset, iValueLength, ENC_NA); |
1549 | 0 | iValueOffset += iValueLength; |
1550 | 0 | break; |
1551 | 0 | } |
1552 | 0 | iStringValue = (char*)tvb_get_string_enc(pinfo->pool, tvb, iValueOffset+iValueLength, iStringLength, ENC_UTF_8|ENC_NA); |
1553 | 0 | subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, -1, |
1554 | 0 | ett_amf_array_element, &subval_ti, "%s:", iStringValue); |
1555 | 0 | name_tree = proto_tree_add_subtree_format(subval_tree, tvb, |
1556 | 0 | iValueOffset, |
1557 | 0 | iValueLength+iStringLength, |
1558 | 0 | ett_amf_string, NULL, "Member name: %s", |
1559 | 0 | iStringValue); |
1560 | 0 | proto_tree_add_uint(name_tree, hf_amf_membernamelength, tvb, iValueOffset, iValueLength, iStringLength); |
1561 | 0 | iValueOffset += iValueLength; |
1562 | 0 | proto_tree_add_string(name_tree, hf_amf_membername, tvb, iValueOffset, iStringLength, iStringValue); |
1563 | 0 | iValueOffset += iStringLength; |
1564 | 0 | } else { |
1565 | | /* the upper 28 bits of the integer value are a string reference index */ |
1566 | 0 | subval_tree = proto_tree_add_subtree_format(traits_tree, tvb, iValueOffset, iValueLength, |
1567 | 0 | ett_amf_array_element, &subval_ti, "Reference %u:", iIntegerValue >> 1); |
1568 | 0 | proto_tree_add_uint(subval_tree, hf_amf_string_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1569 | 0 | iValueOffset += iValueLength; |
1570 | 0 | } |
1571 | | |
1572 | | /* Fetch the value */ |
1573 | 0 | iValueOffset = dissect_amf3_value_type(tvb, pinfo, iValueOffset, subval_tree, subval_ti); |
1574 | 0 | proto_item_set_end(subval_ti, tvb, iValueOffset); |
1575 | 0 | } |
1576 | 0 | } |
1577 | 0 | proto_item_set_end(traits_ti, tvb, iValueOffset); |
1578 | 0 | } |
1579 | 0 | } else { |
1580 | | /* |
1581 | | * U29O-traits-ref; the upper 27 bits of |
1582 | | * the integer value are a traits reference |
1583 | | * index. |
1584 | | */ |
1585 | 0 | proto_tree_add_uint(val_tree, hf_amf_trait_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 2); |
1586 | 0 | iValueOffset += iValueLength; |
1587 | 0 | } |
1588 | 0 | } else { |
1589 | | /* |
1590 | | * U29O-ref; the upper 28 bits of the integer value |
1591 | | * are an object reference index. |
1592 | | */ |
1593 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1594 | 0 | proto_item_append_text(ti, " reference %u", iIntegerValue >> 1); |
1595 | 0 | if (parent_ti != NULL) |
1596 | 0 | proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1); |
1597 | 0 | } |
1598 | 0 | break; |
1599 | 0 | case AMF3_XML: |
1600 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1601 | 0 | if (iIntegerValue & 0x00000001) { |
1602 | | /* |
1603 | | * The upper 28 bits of the integer value are |
1604 | | * a count of the number of bytes in the |
1605 | | * XML string. |
1606 | | */ |
1607 | 0 | iStringLength = iIntegerValue >> 1; |
1608 | 0 | proto_tree_add_uint(val_tree, hf_amf_xmllength, tvb, iValueOffset, iValueLength, iStringLength); |
1609 | 0 | iValueOffset += iValueLength; |
1610 | 0 | proto_tree_add_item(val_tree, hf_amf_xml, tvb, iValueOffset, iStringLength, ENC_UTF_8); |
1611 | 0 | } else { |
1612 | | /* the upper 28 bits of the integer value are a string reference index */ |
1613 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1614 | 0 | proto_item_append_text(ti, " reference %u", iIntegerValue >> 1); |
1615 | 0 | if (parent_ti != NULL) |
1616 | 0 | proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1); |
1617 | 0 | } |
1618 | 0 | break; |
1619 | 0 | case AMF3_BYTEARRAY: |
1620 | 0 | iIntegerValue = amf_get_u29(tvb, iValueOffset, &iValueLength); |
1621 | 0 | if (iIntegerValue & 0x00000001) { |
1622 | | /* |
1623 | | * The upper 28 bits of the integer value are |
1624 | | * a count of the number of bytes in the |
1625 | | * byte array. |
1626 | | */ |
1627 | 0 | iArrayLength = iIntegerValue >> 1; |
1628 | 0 | proto_tree_add_uint(val_tree, hf_amf_bytearraylength, tvb, iValueOffset, iValueLength, iArrayLength); |
1629 | 0 | iValueOffset += iValueLength; |
1630 | 0 | iByteArrayValue = (uint8_t *)tvb_memdup(pinfo->pool, tvb, iValueOffset, iArrayLength); |
1631 | 0 | proto_tree_add_bytes(val_tree, hf_amf_bytearray, tvb, iValueOffset, iArrayLength, iByteArrayValue); |
1632 | 0 | proto_item_append_text(ti, " %s", bytes_to_str(pinfo->pool, iByteArrayValue, iArrayLength)); |
1633 | 0 | if (parent_ti != NULL) |
1634 | 0 | proto_item_append_text(parent_ti, " %s", bytes_to_str(pinfo->pool, iByteArrayValue, iArrayLength)); |
1635 | 0 | } else { |
1636 | | /* the upper 28 bits of the integer value are a object reference index */ |
1637 | 0 | proto_tree_add_uint(val_tree, hf_amf_object_reference, tvb, iValueOffset, iValueLength, iIntegerValue >> 1); |
1638 | 0 | proto_item_append_text(ti, " reference %u", iIntegerValue >> 1); |
1639 | 0 | if (parent_ti != NULL) |
1640 | 0 | proto_item_append_text(parent_ti, " reference %u", iIntegerValue >> 1); |
1641 | 0 | } |
1642 | 0 | break; |
1643 | 0 | default: |
1644 | | /* |
1645 | | * If we can't determine the length, don't carry on; |
1646 | | * just skip to the end of the tvbuff. |
1647 | | */ |
1648 | 0 | iValueOffset = tvb_reported_length(tvb); |
1649 | 0 | break; |
1650 | 0 | } |
1651 | 0 | decrement_dissection_depth(pinfo); |
1652 | 0 | proto_item_set_end(ti, tvb, iValueOffset); |
1653 | 0 | return iValueOffset; |
1654 | 0 | } |
1655 | | |
1656 | | static int |
1657 | | dissect_rtmpt_body_command(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *rtmpt_tree, bool amf3) |
1658 | 2 | { |
1659 | 2 | bool amf3_encoding = false; |
1660 | | |
1661 | 2 | 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 | 1 | offset++; |
1668 | 1 | } |
1669 | | |
1670 | 6 | while (tvb_reported_length_remaining(tvb, offset) > 0) |
1671 | 4 | { |
1672 | 4 | if (amf3_encoding) |
1673 | 0 | offset = dissect_amf3_value_type(tvb, pinfo, offset, rtmpt_tree, NULL); |
1674 | 4 | else |
1675 | 4 | offset = dissect_amf0_value_type(tvb, pinfo, offset, rtmpt_tree, &amf3_encoding, NULL); |
1676 | 4 | } |
1677 | 2 | return offset; |
1678 | 2 | } |
1679 | | |
1680 | | static void |
1681 | | dissect_rtmpt_body_audio(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree) |
1682 | 15 | { |
1683 | 15 | uint8_t iCtl; |
1684 | 15 | uint8_t iAudioMultitrackCtl; |
1685 | 15 | uint8_t iAudioTrackId; |
1686 | 15 | uint32_t iAudioTrackLength; |
1687 | 15 | proto_item *ai; |
1688 | 15 | proto_tree *at; |
1689 | 15 | bool isAudioMultitrack = false; |
1690 | 15 | bool isOneTrack = false; |
1691 | 15 | bool isManyTracksManyCodecs = false; |
1692 | 15 | bool processAudioBody = true; |
1693 | | |
1694 | 15 | iCtl = tvb_get_uint8(tvb, offset); |
1695 | 15 | if ((iCtl & RTMPT_IS_EX_AUDIO_HEADER) == RTMPT_IS_EX_AUDIO_HEADER) { |
1696 | 11 | ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_packet_type, tvb, offset, 1, iCtl, |
1697 | 11 | "Control: 0x%02x (%s)", iCtl, |
1698 | 11 | val_to_str_const((iCtl & 0xf), rtmpt_audio_packet_types, "Reserved audio packet type")); |
1699 | 11 | at = proto_item_add_subtree(ai, ett_rtmpt_audio_control); |
1700 | | |
1701 | 11 | proto_tree_add_uint(at, hf_rtmpt_audio_is_ex_header, tvb, offset, 1, iCtl); |
1702 | 11 | proto_tree_add_uint(at, hf_rtmpt_audio_packet_type, tvb, offset, 1, iCtl); |
1703 | 11 | offset += 1; |
1704 | | |
1705 | 11 | isAudioMultitrack = (iCtl & 0xf) == RTMPT_IS_AUDIO_MULTITRACK; |
1706 | 11 | if (isAudioMultitrack) { |
1707 | 0 | iAudioMultitrackCtl = tvb_get_uint8(tvb, offset); |
1708 | 0 | ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_multitrack_control, tvb, offset, 1, iAudioMultitrackCtl, |
1709 | 0 | "Audio Multitrack Control: 0x%02x (%s %s)", iAudioMultitrackCtl, |
1710 | 0 | val_to_str_const((iAudioMultitrackCtl & 0x0f), rtmpt_av_multitrack_types, "Reserved av multitrack type"), |
1711 | 0 | val_to_str_const((iAudioMultitrackCtl & 0xf0) >> 4, rtmpt_audio_packet_types, "Reserved audio packet type")); |
1712 | 0 | at = proto_item_add_subtree(ai, ett_rtmpt_audio_multitrack_control); |
1713 | 0 | proto_tree_add_uint(at, hf_rtmpt_audio_multitrack_packet_type, tvb, offset, 1, iAudioMultitrackCtl); |
1714 | 0 | proto_tree_add_uint(at, hf_rtmpt_audio_multitrack_type, tvb, offset, 1, iAudioMultitrackCtl); |
1715 | 0 | offset += 1; |
1716 | |
|
1717 | 0 | isOneTrack = (iAudioMultitrackCtl & 0x0f) == RTMPT_IS_ONETRACK; |
1718 | 0 | isManyTracksManyCodecs = (iAudioMultitrackCtl & 0x0f) == RTMPT_IS_MANYTRACKSMANYCODECS; |
1719 | 0 | if (!isManyTracksManyCodecs) { |
1720 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII); |
1721 | 0 | offset += 4; |
1722 | 0 | } |
1723 | 11 | } else { |
1724 | 11 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII); |
1725 | 11 | offset += 4; |
1726 | 11 | } |
1727 | | |
1728 | 17 | while(processAudioBody && tvb_reported_length_remaining(tvb, offset) > 0) { |
1729 | 6 | if (isAudioMultitrack) { |
1730 | 0 | iAudioTrackId = tvb_get_uint8(tvb, offset); |
1731 | 0 | ai = proto_tree_add_uint(rtmpt_tree, hf_rtmpt_audio_track_id, tvb, offset, 1, iAudioTrackId); |
1732 | 0 | at = proto_item_add_subtree(ai, ett_rtmpt_audio_multitrack_track); |
1733 | 0 | offset += 1; |
1734 | 0 | if (isManyTracksManyCodecs) { |
1735 | 0 | proto_tree_add_item(at, hf_rtmpt_audio_fourcc, tvb, offset, 4, ENC_ASCII); |
1736 | 0 | offset += 4; |
1737 | 0 | } |
1738 | 0 | if (!isOneTrack) { |
1739 | 0 | iAudioTrackLength = tvb_get_uint24(tvb, offset, ENC_BIG_ENDIAN); |
1740 | 0 | proto_tree_add_uint(at, hf_rtmpt_audio_track_length, tvb, offset, 3, iAudioTrackLength); |
1741 | 0 | offset += 3; |
1742 | 0 | proto_tree_add_item(at, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA); |
1743 | 0 | offset += iAudioTrackLength; |
1744 | 0 | } else { |
1745 | 0 | proto_tree_add_item(at, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA); |
1746 | 0 | processAudioBody = false; |
1747 | 0 | } |
1748 | 6 | } else { |
1749 | 6 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_data, tvb, offset, -1, ENC_NA); |
1750 | 6 | processAudioBody = false; |
1751 | 6 | } |
1752 | 6 | } |
1753 | 11 | } else { |
1754 | 4 | ai = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_audio_control, tvb, offset, 1, iCtl, |
1755 | 4 | "Control: 0x%02x (%s %s %s %s)", iCtl, |
1756 | 4 | val_to_str_const((iCtl & 0xf0) >> 4, rtmpt_audio_codecs, "Unknown codec"), |
1757 | 4 | val_to_str_const((iCtl & 0x0c) >> 2, rtmpt_audio_rates, "Unknown rate"), |
1758 | 4 | val_to_str_const((iCtl & 0x02) >> 1, rtmpt_audio_sizes, "Unknown sample size"), |
1759 | 4 | val_to_str_const(iCtl & 0x01, rtmpt_audio_types, "Unknown channel count")); |
1760 | | |
1761 | 4 | at = proto_item_add_subtree(ai, ett_rtmpt_audio_control); |
1762 | 4 | proto_tree_add_uint(at, hf_rtmpt_audio_format, tvb, offset, 1, iCtl); |
1763 | 4 | proto_tree_add_uint(at, hf_rtmpt_audio_rate, tvb, offset, 1, iCtl); |
1764 | 4 | proto_tree_add_uint(at, hf_rtmpt_audio_size, tvb, offset, 1, iCtl); |
1765 | 4 | proto_tree_add_uint(at, hf_rtmpt_audio_type, tvb, offset, 1, iCtl); |
1766 | 4 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_audio_data, tvb, offset + 1, -1, ENC_NA); |
1767 | 4 | } |
1768 | 15 | } |
1769 | | |
1770 | | static void |
1771 | | dissect_rtmpt_body_video(tvbuff_t *tvb, int offset, proto_tree *rtmpt_tree) |
1772 | 3 | { |
1773 | 3 | uint8_t iCtl; |
1774 | 3 | uint8_t iMultitrackCtl; |
1775 | 3 | uint8_t iVideoFrameType; |
1776 | 3 | uint8_t iVideoPacketType; |
1777 | 3 | uint8_t iAvMultitrackType; |
1778 | 3 | uint32_t iVideoTrackLength; |
1779 | 3 | proto_item *vi; |
1780 | 3 | proto_tree *vt; |
1781 | 3 | bool isVideoMultitrack = false; |
1782 | 3 | bool isOneTrack = false; |
1783 | 3 | bool isManyTracksManyCodecs = false; |
1784 | 3 | bool processVideoBody = true; |
1785 | | |
1786 | 3 | iCtl = tvb_get_uint8(tvb, offset); |
1787 | 3 | iVideoFrameType = (iCtl & 0x70) >> 4; |
1788 | | |
1789 | | /* |
1790 | | * https://veovera.org/docs/enhanced/enhanced-rtmp-v2.pdf |
1791 | | */ |
1792 | 3 | if (iCtl & RTMPT_IS_EX_VIDEO_HEADER) { |
1793 | 1 | iVideoPacketType = iCtl & 0x0f; |
1794 | 1 | isVideoMultitrack = iVideoPacketType == RTMPT_IS_VIDEO_MULTITRACK; |
1795 | | |
1796 | 1 | vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_control, tvb, offset, 1, iCtl, |
1797 | 1 | "Control: 0x%02x (%s %s)", iCtl, |
1798 | 1 | val_to_str_const(iVideoFrameType, rtmpt_video_types, "Reserved frame type"), |
1799 | 1 | val_to_str_const(iVideoPacketType, rtmpt_video_packet_types, "Reserved packet type")); |
1800 | 1 | vt = proto_item_add_subtree(vi, ett_rtmpt_video_control); |
1801 | 1 | proto_tree_add_item(vt, hf_rtmpt_video_is_ex_header, tvb, offset, 1, ENC_BIG_ENDIAN); |
1802 | 1 | proto_tree_add_item(vt, hf_rtmpt_video_type, tvb, offset, 1, ENC_BIG_ENDIAN); |
1803 | 1 | proto_tree_add_item(vt, hf_rtmpt_video_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN); |
1804 | 1 | offset += 1; |
1805 | | |
1806 | 1 | if (iVideoPacketType != RTMPT_IS_PACKET_TYPE_METADATA && iVideoFrameType == RTMPT_IS_FRAME_TYPE_COMMAND) { |
1807 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_command, tvb, offset, 1, ENC_BIG_ENDIAN); |
1808 | 0 | offset += 1; |
1809 | 0 | processVideoBody = false; |
1810 | 1 | } else if (isVideoMultitrack) { |
1811 | 0 | iMultitrackCtl = tvb_get_uint8(tvb, offset); |
1812 | 0 | iAvMultitrackType = (iMultitrackCtl >> 4) & 0xf; |
1813 | 0 | vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_multitrack_control, tvb, offset, 1, iMultitrackCtl, |
1814 | 0 | "Video Multitrack Control: 0x%02x (%s %s)", iMultitrackCtl, |
1815 | 0 | val_to_str_const(iAvMultitrackType, rtmpt_av_multitrack_types, "Reserved av multitrack type"), |
1816 | 0 | val_to_str_const(iMultitrackCtl & 0xf, rtmpt_video_packet_types, "Reserved video packet type")); |
1817 | 0 | vt = proto_item_add_subtree(vi, ett_rtmpt_video_multitrack_control); |
1818 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_multitrack_type, tvb, offset, 1, ENC_BIG_ENDIAN); |
1819 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_multitrack_packet_type, tvb, offset, 1, ENC_BIG_ENDIAN); |
1820 | 0 | offset += 1; |
1821 | |
|
1822 | 0 | isOneTrack = (iAvMultitrackType & 0x0f) == RTMPT_IS_ONETRACK; |
1823 | 0 | isManyTracksManyCodecs = (iAvMultitrackType & 0x0f) == RTMPT_IS_MANYTRACKSMANYCODECS; |
1824 | 0 | if (!isManyTracksManyCodecs) { |
1825 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII); |
1826 | 0 | offset += 4; |
1827 | 0 | } |
1828 | 1 | } else { |
1829 | 1 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII); |
1830 | 1 | offset += 4; |
1831 | 1 | } |
1832 | | |
1833 | 2 | while (processVideoBody && tvb_reported_length_remaining(tvb, offset) > 0){ |
1834 | 1 | if (isVideoMultitrack) { |
1835 | 0 | vi = proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_track_id, tvb, offset, 1, ENC_BIG_ENDIAN); |
1836 | 0 | vt = proto_item_add_subtree(vi, ett_rtmpt_video_multitrack_track); |
1837 | 0 | if (isManyTracksManyCodecs) { |
1838 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_fourcc, tvb, offset, 4, ENC_ASCII); |
1839 | 0 | offset += 4; |
1840 | 0 | } |
1841 | 0 | offset += 1; |
1842 | 0 | if (!isOneTrack) { |
1843 | 0 | iVideoTrackLength = tvb_get_uint24(tvb, offset, ENC_BIG_ENDIAN); |
1844 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_track_length, tvb, offset, 3, ENC_BIG_ENDIAN); |
1845 | 0 | offset += 3; |
1846 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_data, tvb, offset, iVideoTrackLength, ENC_NA); |
1847 | 0 | offset += iVideoTrackLength; |
1848 | 0 | } else { |
1849 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA); |
1850 | 0 | processVideoBody = false; |
1851 | 0 | } |
1852 | 1 | } else { |
1853 | 1 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA); |
1854 | 1 | processVideoBody = false; |
1855 | 1 | } |
1856 | 1 | } |
1857 | 2 | } else { |
1858 | 2 | vi = proto_tree_add_uint_format(rtmpt_tree, hf_rtmpt_video_control, tvb, offset, 1, iCtl, |
1859 | 2 | "Control: 0x%02x (%s %s)", iCtl, |
1860 | 2 | val_to_str_const(iVideoFrameType, rtmpt_video_types, "Reserved frame type"), |
1861 | 2 | val_to_str_const(iCtl & 0x0f, rtmpt_video_codecs, "Unknown codec")); |
1862 | | |
1863 | 2 | vt = proto_item_add_subtree(vi, ett_rtmpt_video_control); |
1864 | 2 | proto_tree_add_item(vt, hf_rtmpt_video_type, tvb, offset, 1, ENC_BIG_ENDIAN); |
1865 | 2 | proto_tree_add_item(vt, hf_rtmpt_video_format, tvb, offset, 1, ENC_BIG_ENDIAN); |
1866 | 2 | offset += 1; |
1867 | | |
1868 | 2 | if (iVideoFrameType == RTMPT_IS_FRAME_TYPE_COMMAND) { |
1869 | 0 | proto_tree_add_item(vt, hf_rtmpt_video_command, tvb, offset, 1, ENC_BIG_ENDIAN); |
1870 | 2 | } else { |
1871 | 2 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_video_data, tvb, offset, -1, ENC_NA); |
1872 | 2 | } |
1873 | 2 | } |
1874 | 3 | } |
1875 | | |
1876 | | static void |
1877 | | dissect_rtmpt_body_aggregate(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *rtmpt_tree) |
1878 | 0 | { |
1879 | 0 | proto_tree *tag_tree; |
1880 | |
|
1881 | 0 | proto_tree *data_tree; |
1882 | |
|
1883 | 0 | while (tvb_reported_length_remaining(tvb, offset) > 0) { |
1884 | 0 | uint8_t iTagType; |
1885 | 0 | unsigned iDataSize; |
1886 | |
|
1887 | 0 | iTagType = tvb_get_uint8(tvb, offset + 0); |
1888 | 0 | iDataSize = tvb_get_ntoh24(tvb, offset + 1); |
1889 | |
|
1890 | 0 | tag_tree = proto_tree_add_subtree(rtmpt_tree, tvb, offset, 11+iDataSize+4, ett_rtmpt_tag, NULL, |
1891 | 0 | val_to_str_const(iTagType, rtmpt_tag_vals, "Unknown Tag")); |
1892 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_type, tvb, offset+0, 1, ENC_BIG_ENDIAN); |
1893 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_datasize, tvb, offset+1, 3, ENC_BIG_ENDIAN); |
1894 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_timestamp, tvb, offset+4, 3, ENC_BIG_ENDIAN); |
1895 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_ets, tvb, offset+7, 1, ENC_BIG_ENDIAN); |
1896 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_streamid, tvb, offset+8, 3, ENC_BIG_ENDIAN); |
1897 | |
|
1898 | 0 | data_tree = proto_tree_add_subtree(tag_tree, tvb, offset+11, iDataSize, ett_rtmpt_tag_data, NULL, "Data"); |
1899 | |
|
1900 | 0 | switch (iTagType) { |
1901 | 0 | case 8: |
1902 | 0 | dissect_rtmpt_body_audio(tvb, offset + 11, data_tree); |
1903 | 0 | break; |
1904 | 0 | case 9: |
1905 | 0 | dissect_rtmpt_body_video(tvb, offset + 11, data_tree); |
1906 | 0 | break; |
1907 | 0 | case 18: |
1908 | 0 | dissect_rtmpt_body_command(tvb, pinfo, offset + 11, data_tree, false); |
1909 | 0 | break; |
1910 | 0 | default: |
1911 | 0 | break; |
1912 | 0 | } |
1913 | | |
1914 | 0 | proto_tree_add_item(tag_tree, hf_rtmpt_tag_tagsize, tvb, offset+11+iDataSize, 4, ENC_BIG_ENDIAN); |
1915 | 0 | offset += 11 + iDataSize + 4; |
1916 | 0 | } |
1917 | 0 | } |
1918 | | |
1919 | | /* The main dissector for unchunked packets */ |
1920 | | |
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) |
1923 | 1.81k | { |
1924 | 1.81k | int offset = 0; |
1925 | | |
1926 | 1.81k | char *sDesc = NULL; |
1927 | 1.81k | bool deschasopcode = false; |
1928 | 1.81k | bool haveETS = false; |
1929 | 1.81k | uint32_t iBodyOffset = 0; |
1930 | 1.81k | uint32_t iBodyRemain = 0; |
1931 | | |
1932 | 1.81k | col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTMP"); |
1933 | | |
1934 | 1.81k | ws_debug("Dissect: frame=%u visited=%d len=%d tree=%p", |
1935 | 1.81k | pinfo->num, pinfo->fd->visited, |
1936 | 1.81k | tvb_reported_length_remaining(tvb, offset), tree); |
1937 | | |
1938 | | /* Clear any previous data in Info column (RTMP packets are protected by a "fence") */ |
1939 | 1.81k | col_clear(pinfo->cinfo, COL_INFO); |
1940 | | |
1941 | 1.81k | if (tvb_reported_length_remaining(tvb, offset) < 1) return; |
1942 | | |
1943 | 1.81k | if (tp->id <= RTMPT_ID_MAX) { |
1944 | 1.81k | if (tp->fmt < 3 |
1945 | 855 | && tvb_reported_length_remaining(tvb, offset) >= tp->bhlen+3U |
1946 | 855 | && tvb_get_ntoh24(tvb, offset+tp->bhlen) == 0xffffff) { |
1947 | 9 | haveETS = true; |
1948 | 9 | } |
1949 | | |
1950 | 1.81k | iBodyOffset = offset + tp->bhlen + tp->mhlen; |
1951 | 1.81k | iBodyRemain = tvb_reported_length_remaining(tvb, iBodyOffset); |
1952 | | |
1953 | 1.81k | if (tp->cmd == RTMPT_TYPE_CHUNK_SIZE && tp->len >= 4 && iBodyRemain >= 4) { |
1954 | 157 | int32_t newchunksize = tvb_get_ntohl(tvb, iBodyOffset); |
1955 | 157 | if (newchunksize > 0) { |
1956 | 57 | wmem_tree_insert32(rconv->chunksize[cdir], tp->lastseq, GINT_TO_POINTER(newchunksize)); |
1957 | 57 | } |
1958 | 157 | } |
1959 | | |
1960 | 1.81k | if (tp->cmd == RTMPT_TYPE_COMMAND_AMF0 || tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || |
1961 | 1.80k | tp->cmd == RTMPT_TYPE_DATA_AMF0 || tp->cmd == RTMPT_TYPE_DATA_AMF3) { |
1962 | 21 | uint32_t soff = 0; |
1963 | 21 | if (tp->cmd == RTMPT_TYPE_COMMAND_AMF3 || tp->cmd == RTMPT_TYPE_DATA_AMF3) { |
1964 | 17 | soff = 1; |
1965 | 17 | } |
1966 | 21 | tp->txid = rtmpt_get_amf_txid(tvb, iBodyOffset+soff, tree); |
1967 | 21 | if (tp->txid != 0 && !PINFO_FD_VISITED(pinfo)) { |
1968 | 0 | ws_debug("got txid=%d", tp->txid); |
1969 | 0 | wmem_tree_insert32(rconv->txids[cdir], tp->txid, GINT_TO_POINTER(pinfo->num)); |
1970 | 0 | } |
1971 | 21 | } |
1972 | 1.81k | } else if (tp->id == RTMPT_TYPE_HANDSHAKE_2 || tp->id == RTMPT_TYPE_HANDSHAKE_3) { |
1973 | 0 | uint32_t newchunksize = RTMPT_INITIAL_CHUNK_SIZE; |
1974 | 0 | wmem_tree_insert32(rconv->chunksize[cdir], tp->lastseq, GINT_TO_POINTER(newchunksize)); |
1975 | 0 | } |
1976 | | |
1977 | 1.81k | if (tp->id <= RTMPT_ID_MAX) |
1978 | 1.81k | { |
1979 | 1.81k | sDesc = rtmpt_get_packet_desc(pinfo->pool, tvb, iBodyOffset, tree, iBodyRemain, rconv, cdir, tp, &deschasopcode); |
1980 | 1.81k | } |
1981 | | |
1982 | 1.81k | if (tp->id>RTMPT_ID_MAX) { |
1983 | 0 | col_append_sep_str(pinfo->cinfo, COL_INFO, "|", |
1984 | 0 | val_to_str(pinfo->pool, tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)")); |
1985 | 0 | col_set_fence(pinfo->cinfo, COL_INFO); |
1986 | 1.81k | } else if (sDesc) { |
1987 | 217 | col_append_sep_str(pinfo->cinfo, COL_INFO, "|", sDesc); |
1988 | 217 | col_set_fence(pinfo->cinfo, COL_INFO); |
1989 | 1.60k | } else { |
1990 | 1.60k | col_append_sep_str(pinfo->cinfo, COL_INFO, "|", |
1991 | 1.60k | val_to_str(pinfo->pool, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); |
1992 | 1.60k | col_set_fence(pinfo->cinfo, COL_INFO); |
1993 | 1.60k | } |
1994 | | |
1995 | 1.81k | if (tree) |
1996 | 1.81k | { |
1997 | 1.81k | proto_tree *rtmpt_tree = NULL; |
1998 | 1.81k | proto_tree *rtmptroot_tree = NULL; |
1999 | 1.81k | proto_item *ti; |
2000 | 1.81k | ti = proto_tree_add_item(tree, proto_rtmpt, tvb, offset, -1, ENC_NA); |
2001 | | |
2002 | 1.81k | if (tp->id > RTMPT_ID_MAX) { |
2003 | 0 | char* str_handshake = val_to_str(pinfo->pool, tp->id, rtmpt_handshake_vals, "Unknown (0x%01x)"); |
2004 | | |
2005 | | /* Dissect handshake */ |
2006 | 0 | proto_item_append_text(ti, " (%s)", str_handshake); |
2007 | 0 | rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt); |
2008 | 0 | rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_handshake, NULL, str_handshake); |
2009 | |
|
2010 | 0 | if (tp->id == RTMPT_TYPE_HANDSHAKE_1) |
2011 | 0 | { |
2012 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c0, tvb, 0, 1, ENC_NA); |
2013 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c1, tvb, 1, 1536, ENC_NA); |
2014 | 0 | } |
2015 | 0 | else if (tp->id == RTMPT_TYPE_HANDSHAKE_2) |
2016 | 0 | { |
2017 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s0, tvb, 0, 1, ENC_NA); |
2018 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s1, tvb, 1, 1536, ENC_NA); |
2019 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_s2, tvb, 1537, 1536, ENC_NA); |
2020 | 0 | } |
2021 | 0 | else if (tp->id == RTMPT_TYPE_HANDSHAKE_3) |
2022 | 0 | { |
2023 | 0 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_handshake_c2, tvb, 0, 1536, ENC_NA); |
2024 | 0 | } |
2025 | |
|
2026 | 0 | return; |
2027 | 0 | } |
2028 | | |
2029 | 1.81k | if (sDesc && deschasopcode) { |
2030 | 213 | proto_item_append_text(ti, " (%s)", sDesc); |
2031 | 1.60k | } else if (sDesc) { |
2032 | 4 | proto_item_append_text(ti, " (%s %s)", |
2033 | 4 | val_to_str(pinfo->pool, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)"), sDesc); |
2034 | 1.59k | } else { |
2035 | 1.59k | proto_item_append_text(ti, " (%s)", |
2036 | 1.59k | val_to_str(pinfo->pool, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); |
2037 | 1.59k | } |
2038 | 1.81k | rtmptroot_tree = proto_item_add_subtree(ti, ett_rtmpt); |
2039 | | |
2040 | | /* Function call/response matching */ |
2041 | 1.81k | if (tp->otherframe != 0) { |
2042 | 0 | proto_tree_add_uint(rtmptroot_tree, |
2043 | 0 | tp->isresponse ? hf_rtmpt_function_response : hf_rtmpt_function_call, |
2044 | 0 | tvb, offset, tp->bhlen+tp->mhlen+tp->len, |
2045 | 0 | tp->otherframe); |
2046 | 0 | } |
2047 | | |
2048 | | /* Dissect header fields */ |
2049 | 1.81k | 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(pinfo->pool, tp->cmd, rtmpt_opcode_vals, "Unknown (0x%01x)")); */ |
2051 | | |
2052 | 1.81k | if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_format, tvb, offset + 0, 1, ENC_BIG_ENDIAN); |
2053 | 1.81k | if (tp->fmt <= 3) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_csid, tvb, offset + 0, tp->bhlen, ENC_BIG_ENDIAN); |
2054 | 1.81k | if (tp->fmt <= 2) { |
2055 | 853 | if (tp->fmt > 0) { |
2056 | 338 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp_delta, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN); |
2057 | 515 | } else { |
2058 | 515 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 3, ENC_BIG_ENDIAN); |
2059 | 515 | } |
2060 | 853 | if (haveETS) { |
2061 | 9 | proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_ets, tvb, offset + tp->bhlen + tp->mhlen - 4, 4, ENC_BIG_ENDIAN); |
2062 | 9 | } |
2063 | 853 | } |
2064 | 1.81k | if ((tp->fmt>0 && !haveETS) || tp->fmt == 3) { |
2065 | 1.29k | proto_tree_add_uint_format_value(rtmpt_tree, hf_rtmpt_header_timestamp, tvb, offset + tp->bhlen, 0, tp->ts, "%d (calculated)", tp->ts); |
2066 | 1.29k | } |
2067 | 1.81k | 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 | 1.81k | if (tp->fmt <= 1) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_typeid, tvb, offset + tp->bhlen + 6, 1, ENC_BIG_ENDIAN); |
2069 | 1.81k | if (tp->fmt <= 0) proto_tree_add_item(rtmpt_tree, hf_rtmpt_header_streamid, tvb, offset + tp->bhlen + 7, 4, ENC_LITTLE_ENDIAN); |
2070 | | |
2071 | | /* Dissect body */ |
2072 | 1.81k | if (tp->len == 0) return; |
2073 | 1.21k | offset = iBodyOffset; |
2074 | | |
2075 | 1.21k | rtmpt_tree = proto_tree_add_subtree(rtmptroot_tree, tvb, offset, -1, ett_rtmpt_body, NULL, RTMPT_TEXT_RTMP_BODY); |
2076 | | |
2077 | 1.21k | switch (tp->cmd) { |
2078 | 159 | case RTMPT_TYPE_CHUNK_SIZE: |
2079 | 160 | case RTMPT_TYPE_ABORT_MESSAGE: |
2080 | 168 | case RTMPT_TYPE_ACKNOWLEDGEMENT: |
2081 | 171 | case RTMPT_TYPE_UCM: |
2082 | 216 | case RTMPT_TYPE_WINDOW: |
2083 | 219 | case RTMPT_TYPE_PEER_BANDWIDTH: |
2084 | 219 | dissect_rtmpt_body_scm(tvb, offset, rtmpt_tree, tp->cmd); |
2085 | 219 | break; |
2086 | 0 | case RTMPT_TYPE_COMMAND_AMF0: |
2087 | 1 | case RTMPT_TYPE_DATA_AMF0: |
2088 | 1 | dissect_rtmpt_body_command(tvb, pinfo, offset, rtmpt_tree, false); |
2089 | 1 | break; |
2090 | 0 | case RTMPT_TYPE_COMMAND_AMF3: |
2091 | 1 | case RTMPT_TYPE_DATA_AMF3: |
2092 | 1 | dissect_rtmpt_body_command(tvb, pinfo, offset, rtmpt_tree, true); |
2093 | 1 | break; |
2094 | 15 | case RTMPT_TYPE_AUDIO_DATA: |
2095 | 15 | dissect_rtmpt_body_audio(tvb, offset, rtmpt_tree); |
2096 | 15 | break; |
2097 | 3 | case RTMPT_TYPE_VIDEO_DATA: |
2098 | 3 | dissect_rtmpt_body_video(tvb, offset, rtmpt_tree); |
2099 | 3 | break; |
2100 | 0 | case RTMPT_TYPE_AGGREGATE: |
2101 | 0 | dissect_rtmpt_body_aggregate(tvb, pinfo, offset, rtmpt_tree); |
2102 | 0 | break; |
2103 | 1.21k | } |
2104 | 1.21k | } |
2105 | 1.81k | } |
2106 | | |
2107 | | /* Unchunk a data stream into individual RTMP packets */ |
2108 | | |
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) |
2111 | 178 | { |
2112 | 178 | int offset = 0; |
2113 | 178 | int remain; |
2114 | 178 | int want; |
2115 | | |
2116 | 178 | uint8_t header_type; |
2117 | 178 | int basic_hlen; |
2118 | 178 | int message_hlen; |
2119 | | |
2120 | 178 | uint32_t id; |
2121 | 178 | uint32_t ts = 0; |
2122 | 178 | uint32_t tsd = 0; |
2123 | 178 | int body_len; |
2124 | 178 | uint8_t cmd; |
2125 | 178 | uint32_t src; |
2126 | 178 | int chunk_size; |
2127 | | |
2128 | 178 | rtmpt_frag_t *tf; |
2129 | 178 | rtmpt_id_t *ti; |
2130 | 178 | rtmpt_packet_t *tp; |
2131 | 178 | tvbuff_t *pktbuf; |
2132 | | |
2133 | 178 | remain = tvb_reported_length(tvb); |
2134 | 178 | if (!remain) |
2135 | 0 | return; |
2136 | | |
2137 | 178 | ws_debug("Segment: cdir=%d seq=%d-%d", cdir, seq, seq+remain-1); |
2138 | | |
2139 | 178 | 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. |
2145 | | */ |
2146 | 0 | wmem_stack_t *packets; |
2147 | | |
2148 | | /* List all RTMP packets terminating in this TCP segment, from end to beginning */ |
2149 | |
|
2150 | 0 | packets = wmem_stack_new(pinfo->pool); |
2151 | 0 | wmem_stack_push(packets, 0); |
2152 | |
|
2153 | 0 | tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], seq+remain-1); |
2154 | 0 | 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 |
2158 | | */ |
2159 | 0 | wmem_stack_push(packets, tp); |
2160 | 0 | 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. |
2165 | | */ |
2166 | 0 | break; |
2167 | 0 | } |
2168 | 0 | 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.) |
2179 | | */ |
2180 | 0 | break; |
2181 | 0 | } |
2182 | 0 | tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(rconv->packets[cdir], tp->seq-1); |
2183 | 0 | } |
2184 | | |
2185 | | /* Dissect the generated list in reverse order (beginning to end) */ |
2186 | |
|
2187 | 0 | while ((tp=(rtmpt_packet_t *)wmem_stack_pop(packets)) != NULL) { |
2188 | 0 | if (tp->resident) { |
2189 | 0 | pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have); |
2190 | 0 | add_new_data_source(pinfo, pktbuf, "Unchunked RTMP"); |
2191 | 0 | } else { |
2192 | 0 | pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have); |
2193 | 0 | } |
2194 | 0 | dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp); |
2195 | 0 | } |
2196 | |
|
2197 | 0 | return; |
2198 | 0 | } |
2199 | | |
2200 | 4.36k | while (remain>0) { |
2201 | 4.23k | tf = NULL; |
2202 | 4.23k | ti = NULL; |
2203 | 4.23k | tp = NULL; |
2204 | | |
2205 | | /* Check for outstanding fragmented headers/chunks first */ |
2206 | | |
2207 | 4.23k | if (offset == 0) { |
2208 | 178 | tf = (rtmpt_frag_t *)wmem_tree_lookup32_le(rconv->frags[cdir], seq+offset-1); |
2209 | | |
2210 | 178 | if (tf) { |
2211 | | /* May need to reassemble cross-TCP-segment fragments */ |
2212 | 71 | ws_noisy(" tf seq=%d lseq=%d h=%d l=%d", tf->seq, tf->lastseq, tf->have, tf->len); |
2213 | 71 | if (tf->have >= tf->len || seq+offset < tf->seq || seq+offset > tf->lastseq+tf->len-tf->have) { |
2214 | 58 | tf = NULL; |
2215 | 58 | } else if (!tf->ishdr) { |
2216 | 10 | ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], tf->saved.id); |
2217 | 10 | if (ti) { |
2218 | 10 | tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1); |
2219 | 10 | } |
2220 | 10 | if (tp && tp->chunkwant) { |
2221 | 10 | goto unchunk; |
2222 | 10 | } |
2223 | 0 | tf = NULL; |
2224 | 0 | ti = NULL; |
2225 | 0 | tp = NULL; |
2226 | 0 | } |
2227 | | |
2228 | 61 | if (tf) { |
2229 | | /* The preceding segment contained an incomplete chunk header */ |
2230 | | |
2231 | 3 | want = tf->len - tf->have; |
2232 | 3 | if (remain<want) |
2233 | 0 | want = remain; |
2234 | | |
2235 | 3 | tvb_memcpy(tvb, tf->saved.d+tf->have, offset, want); |
2236 | | |
2237 | 3 | id = tf->saved.d[0]; |
2238 | 3 | header_type = (id>>6) & 3; |
2239 | 3 | basic_hlen = rtmpt_basic_header_length(id); |
2240 | | |
2241 | 3 | if ((header_type < 3) && (tf->have < (basic_hlen+3)) && (tf->have+want >= (basic_hlen+3))) { |
2242 | 0 | if (pntohu24(tf->saved.d+basic_hlen) == 0xffffff) { |
2243 | 0 | tf->len += 4; |
2244 | 0 | } |
2245 | 0 | } |
2246 | | |
2247 | 3 | tf->have += want; |
2248 | 3 | tf->lastseq = seq+want-1; |
2249 | 3 | remain -= want; |
2250 | 3 | offset += want; |
2251 | | |
2252 | 3 | if (tf->have < tf->len) { |
2253 | 0 | return; |
2254 | 0 | } |
2255 | 3 | } |
2256 | 61 | } |
2257 | 178 | } |
2258 | | |
2259 | 4.22k | if (!tf) { |
2260 | | /* No preceding data, get header data starting at current position */ |
2261 | 4.22k | id = tvb_get_uint8(tvb, offset); |
2262 | | |
2263 | 4.22k | if (id == RTMPT_MAGIC && seq+offset == RTMPT_HANDSHAKE_OFFSET_1) { |
2264 | 2 | header_type = 4; |
2265 | 2 | basic_hlen = 1; |
2266 | 2 | message_hlen = 0; |
2267 | 2 | id = lastackseq == 1 ? RTMPT_TYPE_HANDSHAKE_1 : RTMPT_TYPE_HANDSHAKE_2; |
2268 | 4.22k | } else if (seq+offset == RTMPT_HANDSHAKE_OFFSET_2) { |
2269 | 0 | header_type = 4; |
2270 | 0 | basic_hlen = 0; |
2271 | 0 | message_hlen = 0; |
2272 | 0 | id = RTMPT_TYPE_HANDSHAKE_3; |
2273 | 4.22k | } else { |
2274 | 4.22k | header_type = (id>>6) & 3; |
2275 | 4.22k | basic_hlen = rtmpt_basic_header_length(id); |
2276 | 4.22k | message_hlen = rtmpt_message_header_length(id); |
2277 | | |
2278 | 4.22k | if ((header_type < 3) && (remain >= (basic_hlen+3))) { |
2279 | 2.04k | if (tvb_get_ntoh24(tvb, offset+basic_hlen) == 0xffffff) { |
2280 | 34 | message_hlen += 4; |
2281 | 34 | } |
2282 | 2.04k | } |
2283 | | |
2284 | 4.22k | if (remain < (basic_hlen+message_hlen)) { |
2285 | | /* Ran out of packet mid-header, save and try again next time */ |
2286 | 46 | tf = wmem_new(wmem_file_scope(), rtmpt_frag_t); |
2287 | 46 | tf->ishdr = 1; |
2288 | 46 | tf->seq = seq + offset; |
2289 | 46 | tf->lastseq = tf->seq + remain - 1; |
2290 | 46 | tf->len = basic_hlen + message_hlen; |
2291 | 46 | tvb_memcpy(tvb, tf->saved.d, offset, remain); |
2292 | 46 | tf->have = remain; |
2293 | 46 | wmem_tree_insert32(rconv->frags[cdir], seq+offset, tf); |
2294 | 46 | return; |
2295 | 46 | } |
2296 | | |
2297 | 4.17k | id = id & 0x3f; |
2298 | 4.17k | if (id == 0) |
2299 | 729 | id = tvb_get_uint8(tvb, offset+1) + 64; |
2300 | 3.44k | else if (id == 1) |
2301 | 75 | id = tvb_get_letohs(tvb, offset+1) + 64; |
2302 | 4.17k | } |
2303 | | |
2304 | 4.22k | } else { |
2305 | | /* Use reassembled header data */ |
2306 | 3 | id = tf->saved.d[0]; |
2307 | 3 | header_type = (id>>6) & 3; |
2308 | 3 | basic_hlen = rtmpt_basic_header_length(id); |
2309 | 3 | message_hlen = tf->len - basic_hlen; |
2310 | | |
2311 | 3 | id = id & 0x3f; |
2312 | 3 | if (id == 0) |
2313 | 1 | id = tf->saved.d[1] + 64; |
2314 | 2 | else if (id == 1) |
2315 | 0 | id = pletohu16(tf->saved.d+1) + 64; |
2316 | 3 | } |
2317 | | |
2318 | | /* Calculate header values, defaulting from previous packets with same id */ |
2319 | | |
2320 | 4.17k | if (id <= RTMPT_ID_MAX) |
2321 | 4.17k | ti = (rtmpt_id_t *)wmem_tree_lookup32(rconv->ids[cdir], id); |
2322 | 4.17k | if (ti) |
2323 | 3.52k | tp = (rtmpt_packet_t *)wmem_tree_lookup32_le(ti->packets, seq+offset-1); |
2324 | | |
2325 | 4.17k | if (header_type == 0) |
2326 | 1.15k | src = tf ? pntohu32(tf->saved.d+basic_hlen+7) : tvb_get_ntohl(tvb, offset+basic_hlen+7); |
2327 | 3.02k | else if (ti) |
2328 | 2.71k | src = ti->src; |
2329 | 311 | else src = 0; |
2330 | | |
2331 | 4.17k | if (header_type < 2) |
2332 | 1.45k | cmd = tf ? tf->saved.d[basic_hlen+6] : tvb_get_uint8(tvb, offset+basic_hlen+6); |
2333 | 2.72k | else if (ti) |
2334 | 2.52k | cmd = ti->cmd; |
2335 | 198 | else |
2336 | 198 | cmd = 0; |
2337 | | |
2338 | | /* Calculate chunk_size now as a last-resort default payload length */ |
2339 | 4.17k | if (id > RTMPT_ID_MAX) { |
2340 | 2 | if (id == RTMPT_TYPE_HANDSHAKE_1) |
2341 | 1 | chunk_size = body_len = 1536; |
2342 | 1 | else if (id == RTMPT_TYPE_HANDSHAKE_2) |
2343 | 1 | chunk_size = body_len = 3072; |
2344 | 0 | else /* if (id == RTMPT_TYPE_HANDSHAKE_3) */ |
2345 | 0 | chunk_size = body_len = 1536; |
2346 | 4.17k | } else { |
2347 | 4.17k | chunk_size = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->chunksize[cdir], seq+offset-1)); |
2348 | 4.17k | if (!chunk_size) { |
2349 | 632 | chunk_size = ((int)rtmpt_default_chunk_size > 0) ? rtmpt_default_chunk_size : INT_MAX; |
2350 | 632 | } |
2351 | | |
2352 | 4.17k | if (header_type < 2) |
2353 | 1.45k | body_len = tf ? pntohu24(tf->saved.d+basic_hlen+3) : tvb_get_ntoh24(tvb, offset+basic_hlen+3); |
2354 | 2.72k | else if (ti) |
2355 | 2.52k | body_len = ti->len; |
2356 | 196 | else |
2357 | 196 | body_len = chunk_size; |
2358 | 4.17k | } |
2359 | | |
2360 | 4.17k | 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 |
2366 | | */ |
2367 | 3.07k | 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 | 3.07k | cdir, seq+offset, |
2369 | 3.07k | ti, tp, header_type, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size); |
2370 | | |
2371 | 3.07k | if (!ti) { |
2372 | 658 | ti = wmem_new(wmem_file_scope(), rtmpt_id_t); |
2373 | 658 | ti->packets = wmem_tree_new(wmem_file_scope()); |
2374 | 658 | ti->ts = 0; |
2375 | 658 | ti->tsd = 0; |
2376 | 658 | wmem_tree_insert32(rconv->ids[cdir], id, ti); |
2377 | 658 | } |
2378 | | |
2379 | 3.07k | if (header_type == 0) { |
2380 | 1.15k | ts = tf ? pntohu24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen); |
2381 | 1.15k | if (ts == 0xffffff) { |
2382 | 7 | ts = tf ? pntohu32(tf->saved.d+basic_hlen+11) : tvb_get_ntohl(tvb, offset+basic_hlen+11); |
2383 | 7 | } |
2384 | 1.15k | tsd = ts - ti->ts; |
2385 | 1.91k | } else if (header_type < 3) { |
2386 | 856 | tsd = tf ? pntohu24(tf->saved.d+basic_hlen) : tvb_get_ntoh24(tvb, offset+basic_hlen); |
2387 | 856 | if (tsd == 0xffffff) { |
2388 | 25 | ts = tf ? pntohu32(tf->saved.d+basic_hlen+message_hlen-4) : tvb_get_ntohl(tvb, offset+basic_hlen+message_hlen-4); |
2389 | 25 | tsd = ti->tsd; /* questionable */ |
2390 | 831 | } else { |
2391 | 831 | ts = ti->ts + tsd; |
2392 | 831 | } |
2393 | 1.06k | } else { |
2394 | 1.06k | ts = ti->ts + ti->tsd; |
2395 | 1.06k | tsd = ti->tsd; |
2396 | 1.06k | } |
2397 | | |
2398 | | /* create a new packet structure */ |
2399 | 3.07k | tp = wmem_new(wmem_file_scope(), rtmpt_packet_t); |
2400 | 3.07k | tp->seq = tp->lastseq = tf ? tf->seq : seq+offset; |
2401 | 3.07k | tp->have = 0; |
2402 | 3.07k | tp->want = basic_hlen + message_hlen + body_len; |
2403 | 3.07k | tp->chunkwant = 0; |
2404 | 3.07k | tp->chunkhave = 0; |
2405 | 3.07k | tp->bhlen = basic_hlen; |
2406 | 3.07k | tp->mhlen = message_hlen; |
2407 | 3.07k | tp->fmt = header_type; |
2408 | 3.07k | tp->id = id; |
2409 | 3.07k | tp->ts = ts; |
2410 | 3.07k | tp->len = body_len; |
2411 | 3.07k | if (id > RTMPT_ID_MAX) |
2412 | 2 | tp->cmd = id; |
2413 | 3.07k | else |
2414 | 3.07k | tp->cmd = cmd & 0x7f; |
2415 | 3.07k | tp->src = src; |
2416 | 3.07k | tp->txid = 0; |
2417 | 3.07k | tp->isresponse = false; |
2418 | 3.07k | tp->otherframe = 0; |
2419 | | |
2420 | 3.07k | tp->frames = wmem_list_new(wmem_file_scope()); |
2421 | 3.07k | wmem_list_prepend(tp->frames, GUINT_TO_POINTER(pinfo->num)); |
2422 | | |
2423 | | /* Save the header information for future defaulting needs */ |
2424 | 3.07k | ti->ts = ts; |
2425 | 3.07k | ti->tsd = tsd; |
2426 | 3.07k | ti->len = body_len; |
2427 | 3.07k | ti->cmd = cmd; |
2428 | 3.07k | ti->src = src; |
2429 | | |
2430 | | /* store against the id only until unchunking is complete */ |
2431 | 3.07k | wmem_tree_insert32(ti->packets, tp->seq, tp); |
2432 | | |
2433 | 3.07k | if (!tf && body_len <= chunk_size && tp->want <= remain) { |
2434 | | /* The easy case - a whole packet contiguous and fully within this segment */ |
2435 | 1.76k | tp->resident = false; |
2436 | 1.76k | tp->data.offset = offset; |
2437 | 1.76k | tp->lastseq = seq+offset+tp->want-1; |
2438 | 1.76k | tp->have = tp->want; |
2439 | | |
2440 | 1.76k | wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp); |
2441 | | |
2442 | 1.76k | pktbuf = tvb_new_subset_length(tvb, tp->data.offset, tp->have); |
2443 | 1.76k | dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp); |
2444 | | |
2445 | 1.76k | offset += tp->want; |
2446 | 1.76k | remain -= tp->want; |
2447 | 1.76k | continue; |
2448 | | |
2449 | 1.76k | } else { |
2450 | | /* Some more reassembly required */ |
2451 | 1.31k | 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. |
2458 | | */ |
2459 | 1.31k | tp->alloc = MIN(tp->want, RTMPT_INIT_ALLOC_SIZE); |
2460 | 1.31k | tp->data.p = (uint8_t *)wmem_alloc(wmem_file_scope(), tp->alloc); |
2461 | | |
2462 | 1.31k | if (tf && tf->ishdr) { |
2463 | 3 | memcpy(tp->data.p, tf->saved.d, tf->len); |
2464 | 1.30k | } else { |
2465 | 1.30k | tvb_memcpy(tvb, tp->data.p, offset, basic_hlen+message_hlen); |
2466 | 1.30k | offset += basic_hlen + message_hlen; |
2467 | 1.30k | remain -= basic_hlen + message_hlen; |
2468 | 1.30k | } |
2469 | | |
2470 | 1.31k | tp->lastseq = seq+offset-1; |
2471 | 1.31k | tp->have = basic_hlen + message_hlen; |
2472 | | |
2473 | 1.31k | if (tp->have == tp->want) { |
2474 | 3 | wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp); |
2475 | | |
2476 | 3 | pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have); |
2477 | 3 | add_new_data_source(pinfo, pktbuf, "Unchunked RTMP"); |
2478 | 3 | dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp); |
2479 | 3 | continue; |
2480 | 3 | } |
2481 | | |
2482 | 1.30k | tp->chunkwant = chunk_size; |
2483 | 1.30k | if (tp->chunkwant > tp->want-tp->have) |
2484 | 20 | tp->chunkwant = tp->want - tp->have; |
2485 | 1.30k | } |
2486 | 3.07k | } else { |
2487 | 1.10k | if (header_type == 3 && tp->resident && tp->have > tp->bhlen + 3 |
2488 | 830 | && pntohu24(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 |
2492 | | */ |
2493 | 318 | message_hlen += 4; |
2494 | 318 | } |
2495 | 1.10k | 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 | 1.10k | cdir, seq+offset, |
2497 | 1.10k | ti, tp, basic_hlen+message_hlen, id, tp?tp->have:0, tp?tp->want:0, body_len, chunk_size); |
2498 | | |
2499 | 1.10k | tp->chunkwant = chunk_size; |
2500 | 1.10k | if (tp->chunkwant > tp->want-tp->have) |
2501 | 4 | tp->chunkwant = tp->want - tp->have; |
2502 | | |
2503 | 1.10k | offset += basic_hlen + message_hlen; |
2504 | 1.10k | remain -= basic_hlen + message_hlen; |
2505 | 1.10k | } |
2506 | | |
2507 | 2.41k | tf = NULL; |
2508 | | |
2509 | | /* Last case to deal with is unchunking the packet body */ |
2510 | 2.42k | unchunk: |
2511 | 2.42k | want = tp->chunkwant - tp->chunkhave; |
2512 | 2.42k | if (want > remain) |
2513 | 122 | want = remain; |
2514 | 2.42k | ws_noisy(" cw=%d ch=%d r=%d w=%d", tp->chunkwant, tp->chunkhave, remain, want); |
2515 | | |
2516 | | /* message length is a 3 byte number, never overflows an int */ |
2517 | 2.42k | 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. |
2527 | | * |
2528 | | * We should have calculated those values so that the |
2529 | | * following assertion is true. |
2530 | | */ |
2531 | 0 | DISSECTOR_ASSERT_CMPINT(tp->have + want, <=, tp->want); |
2532 | 0 | tp->alloc = MAX(tp->alloc*2, tp->have + want); |
2533 | 0 | tp->alloc = MIN(tp->alloc, tp->want); |
2534 | 0 | tp->data.p = wmem_realloc(wmem_file_scope(), tp->data.p, tp->alloc); |
2535 | 0 | } |
2536 | 2.42k | tvb_memcpy(tvb, tp->data.p+tp->have, offset, want); |
2537 | 2.42k | wmem_list_frame_t *frame_head = wmem_list_head(tp->frames); |
2538 | 2.42k | if (wmem_list_frame_data(frame_head) != GUINT_TO_POINTER(pinfo->num)) { |
2539 | 47 | wmem_list_prepend(tp->frames, GUINT_TO_POINTER(pinfo->num)); |
2540 | 47 | } |
2541 | | |
2542 | 2.42k | if (tf) { |
2543 | 10 | tf->have += want; |
2544 | 10 | tf->lastseq = seq+offset+want-1; |
2545 | 10 | } |
2546 | 2.42k | tp->lastseq = seq+offset+want-1; |
2547 | 2.42k | tp->have += want; |
2548 | 2.42k | tp->chunkhave += want; |
2549 | | |
2550 | 2.42k | offset += want; |
2551 | 2.42k | remain -= want; |
2552 | | |
2553 | 2.42k | if (tp->chunkhave == tp->chunkwant) { |
2554 | | /* Chunk is complete - wait for next header */ |
2555 | 2.30k | tp->chunkhave = 0; |
2556 | 2.30k | tp->chunkwant = 0; |
2557 | 2.30k | } |
2558 | | |
2559 | 2.42k | if (tp->have == tp->want) { |
2560 | | /* Whole packet is complete */ |
2561 | 54 | wmem_tree_insert32(rconv->packets[cdir], tp->lastseq, tp); |
2562 | 54 | wmem_list_foreach(tp->frames, rtmpt_packet_mark_depended, pinfo->fd); |
2563 | | |
2564 | 54 | pktbuf = tvb_new_child_real_data(tvb, tp->data.p, tp->have, tp->have); |
2565 | 54 | add_new_data_source(pinfo, pktbuf, "Unchunked RTMP"); |
2566 | 54 | dissect_rtmpt(pktbuf, pinfo, tree, rconv, cdir, tp); |
2567 | 2.37k | } else if (tp->chunkhave < tp->chunkwant) { |
2568 | | /* Chunk is split across segment boundary */ |
2569 | 121 | rtmpt_frag_t *tf2 = wmem_new(wmem_file_scope(), rtmpt_frag_t); |
2570 | 121 | tf2->ishdr = 0; |
2571 | 121 | tf2->seq = seq + offset - want; |
2572 | 121 | tf2->lastseq = tf2->seq + remain - 1 + want; |
2573 | 121 | tf2->have = tp->chunkhave; |
2574 | 121 | tf2->len = tp->chunkwant; |
2575 | 121 | tf2->saved.id = tp->id; |
2576 | 121 | ws_noisy(" inserting tf @ %d", seq+offset-want-1); |
2577 | 121 | wmem_tree_insert32(rconv->frags[cdir], seq+offset-want-1, tf2); |
2578 | 121 | } |
2579 | 2.42k | } |
2580 | 178 | } |
2581 | | |
2582 | | static rtmpt_conv_t * |
2583 | | rtmpt_init_rconv(conversation_t *conv) |
2584 | 69 | { |
2585 | 69 | rtmpt_conv_t *rconv = wmem_new(wmem_file_scope(), rtmpt_conv_t); |
2586 | 69 | conversation_add_proto_data(conv, proto_rtmpt, rconv); |
2587 | | |
2588 | 69 | rconv->seqs[0] = wmem_tree_new(wmem_file_scope()); |
2589 | 69 | rconv->seqs[1] = wmem_tree_new(wmem_file_scope()); |
2590 | 69 | rconv->frags[0] = wmem_tree_new(wmem_file_scope()); |
2591 | 69 | rconv->frags[1] = wmem_tree_new(wmem_file_scope()); |
2592 | 69 | rconv->ids[0] = wmem_tree_new(wmem_file_scope()); |
2593 | 69 | rconv->ids[1] = wmem_tree_new(wmem_file_scope()); |
2594 | 69 | rconv->packets[0] = wmem_tree_new(wmem_file_scope()); |
2595 | 69 | rconv->packets[1] = wmem_tree_new(wmem_file_scope()); |
2596 | 69 | rconv->chunksize[0] = wmem_tree_new(wmem_file_scope()); |
2597 | 69 | rconv->chunksize[1] = wmem_tree_new(wmem_file_scope()); |
2598 | 69 | rconv->txids[0] = wmem_tree_new(wmem_file_scope()); |
2599 | 69 | rconv->txids[1] = wmem_tree_new(wmem_file_scope()); |
2600 | | |
2601 | 69 | return rconv; |
2602 | 69 | } |
2603 | | |
2604 | | static int |
2605 | | dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) |
2606 | 178 | { |
2607 | 178 | conversation_t *conv; |
2608 | 178 | rtmpt_conv_t *rconv; |
2609 | 178 | int cdir; |
2610 | 178 | struct tcpinfo *tcpinfo; |
2611 | | |
2612 | | /* Reject the packet if data is NULL */ |
2613 | 178 | if (data == NULL) { |
2614 | 0 | return 0; |
2615 | 0 | } |
2616 | 178 | tcpinfo = (struct tcpinfo*)data; |
2617 | | |
2618 | 178 | conv = find_or_create_conversation(pinfo); |
2619 | | |
2620 | 178 | rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt); |
2621 | 178 | if (!rconv) { |
2622 | 69 | rconv = rtmpt_init_rconv(conv); |
2623 | 69 | } |
2624 | | |
2625 | 178 | cdir = (addresses_equal(conversation_key_addr1(conv->key_ptr), &pinfo->src) && |
2626 | 178 | addresses_equal(conversation_key_addr2(conv->key_ptr), &pinfo->dst) && |
2627 | 178 | conversation_key_port1(conv->key_ptr) == pinfo->srcport && |
2628 | 178 | conversation_key_port2(conv->key_ptr) == pinfo->destport) ? 0 : 1; |
2629 | | |
2630 | 178 | dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, tcpinfo->seq, tcpinfo->lastackseq); |
2631 | 178 | return tvb_reported_length(tvb); |
2632 | 178 | } |
2633 | | |
2634 | | static int |
2635 | | dissect_rtmpt_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
2636 | 0 | { |
2637 | 0 | conversation_t *conv; |
2638 | 0 | rtmpt_conv_t *rconv; |
2639 | 0 | int cdir; |
2640 | 0 | uint32_t seq; |
2641 | 0 | uint32_t lastackseq; |
2642 | 0 | uint32_t offset; |
2643 | 0 | int remain; |
2644 | |
|
2645 | 0 | offset = 0; |
2646 | 0 | remain = tvb_reported_length_remaining(tvb, 0); |
2647 | | |
2648 | | /* |
2649 | | * Request flow: |
2650 | | * |
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 |
2664 | | * |
2665 | | * Ideally here we'd know: |
2666 | | * |
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.) |
2677 | | * |
2678 | | * As it is, we currently have to just guess, and are |
2679 | | * likely easily confused. |
2680 | | */ |
2681 | |
|
2682 | 0 | cdir = pinfo->srcport == pinfo->match_uint; |
2683 | |
|
2684 | 0 | if (cdir) { |
2685 | 0 | conv = find_conversation(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->srcport, 0); |
2686 | 0 | if (!conv) { |
2687 | 0 | ws_debug("RTMPT new conversation"); |
2688 | 0 | conv = conversation_new(pinfo->num, &pinfo->dst, &pinfo->src, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->srcport, 0); |
2689 | 0 | } |
2690 | 0 | } else { |
2691 | 0 | conv = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->destport, 0); |
2692 | 0 | if (!conv) { |
2693 | 0 | ws_debug("RTMPT new conversation"); |
2694 | 0 | conv = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, conversation_pt_to_conversation_type(pinfo->ptype), 0, pinfo->destport, 0); |
2695 | 0 | } |
2696 | 0 | } |
2697 | |
|
2698 | 0 | rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt); |
2699 | 0 | if (!rconv) { |
2700 | 0 | rconv = rtmpt_init_rconv(conv); |
2701 | 0 | } |
2702 | | |
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.) */ |
2710 | |
|
2711 | 0 | lastackseq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir ^ 1], pinfo->num))+1; |
2712 | |
|
2713 | 0 | 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 | 0 | offset += 17; |
2718 | 0 | remain -= 17; |
2719 | 0 | } 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 | 0 | offset++; |
2725 | 0 | remain--; |
2726 | 0 | } |
2727 | |
|
2728 | 0 | seq = GPOINTER_TO_INT(wmem_tree_lookup32(rconv->seqs[cdir], pinfo->num)); |
2729 | |
|
2730 | 0 | if (seq == 0) { |
2731 | 0 | seq = GPOINTER_TO_INT(wmem_tree_lookup32_le(rconv->seqs[cdir], pinfo->num)); |
2732 | 0 | seq += remain; |
2733 | 0 | wmem_tree_insert32(rconv->seqs[cdir], pinfo->num, GINT_TO_POINTER(seq)); |
2734 | 0 | } |
2735 | |
|
2736 | 0 | seq -= remain-1; |
2737 | |
|
2738 | 0 | ws_debug("RTMPT f=%d cdir=%d seq=%d lastackseq=%d len=%d", pinfo->num, cdir, seq, lastackseq, remain); |
2739 | |
|
2740 | 0 | if (remain < 1) |
2741 | 0 | return offset; |
2742 | | |
2743 | 0 | if (offset > 0) { |
2744 | 0 | tvbuff_t *tvbrtmp = tvb_new_subset_length(tvb, offset, remain); |
2745 | 0 | dissect_rtmpt_common(tvbrtmp, pinfo, tree, rconv, cdir, seq, lastackseq); |
2746 | 0 | } else { |
2747 | 0 | dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, seq, lastackseq); |
2748 | 0 | } |
2749 | 0 | return tvb_captured_length(tvb); |
2750 | 0 | } |
2751 | | |
2752 | | static bool |
2753 | | dissect_rtmpt_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
2754 | 0 | { |
2755 | 0 | conversation_t *conversation; |
2756 | 0 | if (tvb_reported_length(tvb) >= 12) |
2757 | 0 | { |
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 | 0 | struct tcpinfo *tcpinfo = (struct tcpinfo *)data; |
2762 | 0 | if (tcpinfo->lastackseq == RTMPT_HANDSHAKE_OFFSET_2 |
2763 | 0 | && tcpinfo->seq == RTMPT_HANDSHAKE_OFFSET_1 |
2764 | 0 | && tvb_get_uint8(tvb, 0) == RTMPT_MAGIC) |
2765 | 0 | { |
2766 | | /* Register this dissector for this conversation */ |
2767 | 0 | conversation = find_or_create_conversation(pinfo); |
2768 | 0 | conversation_set_dissector(conversation, rtmpt_tcp_handle); |
2769 | | |
2770 | | /* Dissect the packet */ |
2771 | 0 | dissect_rtmpt_tcp(tvb, pinfo, tree, data); |
2772 | 0 | return true; |
2773 | 0 | } |
2774 | 0 | } |
2775 | 0 | return false; |
2776 | 0 | } |
2777 | | |
2778 | | static int |
2779 | | dissect_amf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
2780 | 0 | { |
2781 | 0 | proto_item *ti; |
2782 | 0 | proto_tree *amf_tree, *headers_tree, *messages_tree; |
2783 | 0 | int offset; |
2784 | 0 | unsigned header_count, message_count, i; |
2785 | 0 | unsigned string_length; |
2786 | 0 | unsigned header_length, message_length; |
2787 | 0 | bool amf3_encoding = false; |
2788 | | |
2789 | | /* |
2790 | | * XXX - is "application/x-amf" just AMF3? |
2791 | | */ |
2792 | 0 | ti = proto_tree_add_item(tree, proto_amf, tvb, 0, -1, ENC_NA); |
2793 | 0 | amf_tree = proto_item_add_subtree(ti, ett_amf); |
2794 | 0 | offset = 0; |
2795 | 0 | proto_tree_add_item(amf_tree, hf_amf_version, tvb, offset, 2, ENC_BIG_ENDIAN); |
2796 | 0 | offset += 2; |
2797 | 0 | proto_tree_add_item_ret_uint(amf_tree, hf_amf_header_count, tvb, offset, 2, ENC_BIG_ENDIAN, &header_count); |
2798 | 0 | offset += 2; |
2799 | 0 | if (header_count != 0) { |
2800 | 0 | headers_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_headers, NULL, "Headers"); |
2801 | 0 | for (i = 0; i < header_count; i++) { |
2802 | 0 | string_length = tvb_get_ntohs(tvb, offset); |
2803 | 0 | proto_tree_add_item(headers_tree, hf_amf_header_name, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN); |
2804 | 0 | offset += 2 + string_length; |
2805 | 0 | proto_tree_add_item(headers_tree, hf_amf_header_must_understand, tvb, offset, 1, ENC_NA); |
2806 | 0 | offset += 1; |
2807 | 0 | header_length = tvb_get_ntohl(tvb, offset); |
2808 | 0 | if (header_length == 0xFFFFFFFF) |
2809 | 0 | proto_tree_add_uint_format_value(headers_tree, hf_amf_header_length, tvb, offset, 4, header_length, "Unknown"); |
2810 | 0 | else |
2811 | 0 | proto_tree_add_item(headers_tree, hf_amf_header_length, tvb, offset, 4, ENC_BIG_ENDIAN); |
2812 | 0 | offset += 4; |
2813 | 0 | if (amf3_encoding) |
2814 | 0 | offset = dissect_amf3_value_type(tvb, pinfo, offset, headers_tree, NULL); |
2815 | 0 | else |
2816 | 0 | offset = dissect_amf0_value_type(tvb, pinfo, offset, headers_tree, &amf3_encoding, NULL); |
2817 | 0 | } |
2818 | 0 | } |
2819 | 0 | proto_tree_add_item_ret_uint(amf_tree, hf_amf_message_count, tvb, offset, 2, ENC_BIG_ENDIAN, &message_count); |
2820 | 0 | offset += 2; |
2821 | 0 | if (message_count != 0) { |
2822 | 0 | messages_tree = proto_tree_add_subtree(amf_tree, tvb, offset, -1, ett_amf_messages, NULL, "Messages"); |
2823 | 0 | for (i = 0; i < message_count; i++) { |
2824 | 0 | string_length = tvb_get_ntohs(tvb, offset); |
2825 | 0 | proto_tree_add_item(messages_tree, hf_amf_message_target_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN); |
2826 | 0 | offset += 2 + string_length; |
2827 | 0 | string_length = tvb_get_ntohs(tvb, offset); |
2828 | 0 | proto_tree_add_item(messages_tree, hf_amf_message_response_uri, tvb, offset, 2, ENC_UTF_8|ENC_BIG_ENDIAN); |
2829 | 0 | offset += 2 + string_length; |
2830 | 0 | message_length = tvb_get_ntohl(tvb, offset); |
2831 | 0 | if (message_length == 0xFFFFFFFF) |
2832 | 0 | proto_tree_add_uint_format_value(messages_tree, hf_amf_message_length, tvb, offset, 4, message_length, "Unknown"); |
2833 | 0 | else |
2834 | 0 | proto_tree_add_item(messages_tree, hf_amf_message_length, tvb, offset, 4, ENC_BIG_ENDIAN); |
2835 | 0 | offset += 4; |
2836 | 0 | offset = dissect_rtmpt_body_command(tvb, pinfo, offset, messages_tree, false); |
2837 | 0 | } |
2838 | 0 | } |
2839 | 0 | return tvb_captured_length(tvb); |
2840 | 0 | } |
2841 | | |
2842 | | void |
2843 | | proto_register_rtmpt(void) |
2844 | 15 | { |
2845 | 15 | static hf_register_info hf[] = { |
2846 | | /* RTMP Handshake data */ |
2847 | 15 | { &hf_rtmpt_handshake_c0, |
2848 | 15 | { "Protocol version", "rtmpt.handshake.c0", FT_BYTES, BASE_NONE, |
2849 | 15 | NULL, 0x0, "RTMPT Handshake C0", HFILL }}, |
2850 | | |
2851 | 15 | { &hf_rtmpt_handshake_s0, |
2852 | 15 | { "Protocol version", "rtmpt.handshake.s0", FT_BYTES, BASE_NONE, |
2853 | 15 | NULL, 0x0, "RTMPT Handshake S0", HFILL }}, |
2854 | | |
2855 | 15 | { &hf_rtmpt_handshake_c1, |
2856 | 15 | { "Handshake data", "rtmpt.handshake.c1", FT_BYTES, BASE_NONE, |
2857 | 15 | NULL, 0x0, "RTMPT Handshake C1", HFILL }}, |
2858 | | |
2859 | 15 | { &hf_rtmpt_handshake_s1, |
2860 | 15 | { "Handshake data", "rtmpt.handshake.s1", FT_BYTES, BASE_NONE, |
2861 | 15 | NULL, 0x0, "RTMPT Handshake S1", HFILL }}, |
2862 | | |
2863 | 15 | { &hf_rtmpt_handshake_c2, |
2864 | 15 | { "Handshake data", "rtmpt.handshake.c2", FT_BYTES, BASE_NONE, |
2865 | 15 | NULL, 0x0, "RTMPT Handshake C2", HFILL }}, |
2866 | | |
2867 | 15 | { &hf_rtmpt_handshake_s2, |
2868 | 15 | { "Handshake data", "rtmpt.handshake.s2", FT_BYTES, BASE_NONE, |
2869 | 15 | NULL, 0x0, "RTMPT Handshake S2", HFILL }}, |
2870 | | |
2871 | | /* RTMP chunk/packet header */ |
2872 | 15 | { &hf_rtmpt_header_format, |
2873 | 15 | { "Format", "rtmpt.header.format", FT_UINT8, BASE_DEC, |
2874 | 15 | NULL, 0xC0, "RTMPT Basic Header format", HFILL }}, |
2875 | | |
2876 | 15 | { &hf_rtmpt_header_csid, |
2877 | 15 | { "Chunk Stream ID", "rtmpt.header.csid", FT_UINT8, BASE_DEC, |
2878 | 15 | NULL, 0x3F, "RTMPT Basic Header chunk stream ID", HFILL }}, |
2879 | | |
2880 | 15 | { &hf_rtmpt_header_timestamp, |
2881 | 15 | { "Timestamp", "rtmpt.header.timestamp", FT_UINT24, BASE_DEC, |
2882 | 15 | NULL, 0x0, "RTMPT Message Header timestamp", HFILL }}, |
2883 | | |
2884 | 15 | { &hf_rtmpt_header_timestamp_delta, |
2885 | 15 | { "Timestamp delta", "rtmpt.header.timestampdelta", FT_UINT24, BASE_DEC, |
2886 | 15 | NULL, 0x0, "RTMPT Message Header timestamp delta", HFILL }}, |
2887 | | |
2888 | 15 | { &hf_rtmpt_header_body_size, |
2889 | 15 | { "Body size", "rtmpt.header.bodysize", FT_UINT24, BASE_DEC, |
2890 | 15 | NULL, 0x0, "RTMPT Message Header body size", HFILL }}, |
2891 | | |
2892 | 15 | { &hf_rtmpt_header_typeid, |
2893 | 15 | { "Type ID", "rtmpt.header.typeid", FT_UINT8, BASE_HEX, |
2894 | 15 | VALS(rtmpt_opcode_vals), 0x0, "RTMPT Message Header type ID", HFILL }}, |
2895 | | |
2896 | 15 | { &hf_rtmpt_header_streamid, |
2897 | 15 | { "Stream ID", "rtmpt.header.streamid", FT_UINT32, BASE_DEC, |
2898 | 15 | NULL, 0x0, "RTMPT Header stream ID", HFILL }}, |
2899 | | |
2900 | 15 | { &hf_rtmpt_header_ets, |
2901 | 15 | { "Extended timestamp", "rtmpt.header.ets", FT_UINT32, BASE_DEC, |
2902 | 15 | NULL, 0x0, "RTMPT Message Header extended timestamp", HFILL }}, |
2903 | | |
2904 | | /* Stream Control Messages */ |
2905 | | |
2906 | 15 | { &hf_rtmpt_scm_chunksize, |
2907 | 15 | { "Chunk size", "rtmpt.scm.chunksize", FT_UINT32, BASE_DEC, |
2908 | 15 | NULL, 0x0, "RTMPT SCM chunk size", HFILL }}, |
2909 | | |
2910 | 15 | { &hf_rtmpt_scm_csid, |
2911 | 15 | { "Chunk stream ID", "rtmpt.scm.csid", FT_UINT32, BASE_DEC, |
2912 | 15 | NULL, 0x0, "RTMPT SCM chunk stream ID", HFILL }}, |
2913 | | |
2914 | 15 | { &hf_rtmpt_scm_seq, |
2915 | 15 | { "Sequence number", "rtmpt.scm.seq", FT_UINT32, BASE_DEC, |
2916 | 15 | NULL, 0x0, "RTMPT SCM acknowledgement sequence number", HFILL }}, |
2917 | | |
2918 | 15 | { &hf_rtmpt_scm_was, |
2919 | 15 | { "Window acknowledgement size", "rtmpt.scm.was", FT_UINT32, BASE_DEC, |
2920 | 15 | NULL, 0x0, "RTMPT SCM window acknowledgement size", HFILL }}, |
2921 | | |
2922 | 15 | { &hf_rtmpt_scm_limittype, |
2923 | 15 | { "Limit type", "rtmpt.scm.limittype", FT_UINT8, BASE_DEC, |
2924 | 15 | VALS(rtmpt_limit_vals), 0x0, "RTMPT SCM window acknowledgement size", HFILL }}, |
2925 | | |
2926 | | /* User Control Messages */ |
2927 | 15 | { &hf_rtmpt_ucm_eventtype, |
2928 | 15 | { "Event type", "rtmpt.ucm.eventtype", FT_UINT16, BASE_DEC, |
2929 | 15 | VALS(rtmpt_ucm_vals), 0x0, "RTMPT UCM event type", HFILL }}, |
2930 | | |
2931 | | /* Frame links */ |
2932 | | |
2933 | 15 | { &hf_rtmpt_function_call, |
2934 | 15 | { "Response to this call in frame", "rtmpt.function.call", FT_FRAMENUM, BASE_NONE, |
2935 | 15 | FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0, "RTMPT function call", HFILL }}, |
2936 | | |
2937 | 15 | { &hf_rtmpt_function_response, |
2938 | 15 | { "Call for this response in frame", "rtmpt.function.response", FT_FRAMENUM, BASE_NONE, |
2939 | 15 | FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0, "RTMPT function response", HFILL }}, |
2940 | | |
2941 | | /* Audio packets */ |
2942 | 15 | { &hf_rtmpt_audio_control, |
2943 | 15 | { "Control", "rtmpt.audio.control", FT_UINT8, BASE_HEX, |
2944 | 15 | NULL, 0x0, NULL, HFILL }}, |
2945 | | |
2946 | 15 | { &hf_rtmpt_audio_is_ex_header, |
2947 | 15 | { "IsExAudioHeader", "rtmpt.audio.is_ex_header", FT_UINT8, BASE_DEC, |
2948 | 15 | NULL, 0x90, "RTMPT IsExHeader flag introduced in enhanced RTMP", HFILL } }, |
2949 | | |
2950 | 15 | { &hf_rtmpt_audio_multitrack_control, |
2951 | 15 | { "Audio multitrack control", "rtmpt.audio.multitrack.control", FT_UINT8, BASE_HEX, |
2952 | 15 | NULL, 0x0, NULL, HFILL } }, |
2953 | | |
2954 | 15 | { &hf_rtmpt_audio_multitrack_type, |
2955 | 15 | { "Audio multitrack type", "rtmpt.audio.multitrack.type", FT_UINT8, BASE_HEX, |
2956 | 15 | VALS(rtmpt_av_multitrack_types), 0x0f, NULL, HFILL } }, |
2957 | | |
2958 | 15 | { &hf_rtmpt_audio_multitrack_packet_type, |
2959 | 15 | { "Audio multitrack packet type", "rtmpt.audio.multitrack.track.packet_type", FT_UINT8, BASE_HEX, |
2960 | 15 | VALS(rtmpt_audio_packet_types), 0xf0, NULL, HFILL } }, |
2961 | | |
2962 | 15 | { &hf_rtmpt_audio_packet_type, |
2963 | 15 | { "Audio packet type", "rtmpt.audio.packet_type", FT_UINT8, BASE_HEX, |
2964 | 15 | VALS(rtmpt_audio_packet_types), 0x0f, NULL, HFILL } }, |
2965 | | |
2966 | 15 | { &hf_rtmpt_audio_fourcc, |
2967 | 15 | { "FourCC", "rtmpt.audio.fourcc", FT_STRING, BASE_NONE, |
2968 | 15 | NULL, 0x0, NULL, HFILL } }, |
2969 | | |
2970 | 15 | { &hf_rtmpt_audio_track_id, |
2971 | 15 | { "Track ID", "rtmpt.audio.multitrack.track.id", FT_UINT8, BASE_DEC, |
2972 | 15 | NULL, 0x0, NULL, HFILL } }, |
2973 | | |
2974 | 15 | { &hf_rtmpt_audio_track_length, |
2975 | 15 | { "Track length", "rtmpt.audio.multitrack.track.length", FT_UINT24, BASE_DEC, |
2976 | 15 | NULL, 0x0, NULL, HFILL } }, |
2977 | | |
2978 | 15 | { &hf_rtmpt_audio_format, |
2979 | 15 | { "Format", "rtmpt.audio.format", FT_UINT8, BASE_DEC, |
2980 | 15 | VALS(rtmpt_audio_codecs), 0xf0, NULL, HFILL }}, |
2981 | | |
2982 | 15 | { &hf_rtmpt_audio_rate, |
2983 | 15 | { "Sample rate", "rtmpt.audio.rate", FT_UINT8, BASE_DEC, |
2984 | 15 | VALS(rtmpt_audio_rates), 0x0c, NULL, HFILL }}, |
2985 | | |
2986 | 15 | { &hf_rtmpt_audio_size, |
2987 | 15 | { "Sample size", "rtmpt.audio.size", FT_UINT8, BASE_DEC, |
2988 | 15 | VALS(rtmpt_audio_sizes), 0x02, NULL, HFILL }}, |
2989 | | |
2990 | 15 | { &hf_rtmpt_audio_type, |
2991 | 15 | { "Channels", "rtmpt.audio.type", FT_UINT8, BASE_DEC, |
2992 | 15 | VALS(rtmpt_audio_types), 0x01, NULL, HFILL }}, |
2993 | | |
2994 | 15 | { &hf_rtmpt_audio_data, |
2995 | 15 | { "Audio data", "rtmpt.audio.data", FT_BYTES, BASE_NONE, |
2996 | 15 | NULL, 0x0, NULL, HFILL }}, |
2997 | | |
2998 | | /* Video packets */ |
2999 | 15 | { &hf_rtmpt_video_control, |
3000 | 15 | { "Control", "rtmpt.video.control", FT_UINT8, BASE_HEX, |
3001 | 15 | NULL, 0x0, NULL, HFILL }}, |
3002 | | |
3003 | 15 | { &hf_rtmpt_video_multitrack_control, |
3004 | 15 | { "Video multitrack control", "rtmpt.video.multitrack.control", FT_UINT8, BASE_HEX, |
3005 | 15 | NULL, 0x0, NULL, HFILL }}, |
3006 | | |
3007 | 15 | { &hf_rtmpt_video_is_ex_header, |
3008 | 15 | { "IsExVideoHeader", "rtmpt.video.is_ex_header", FT_UINT8, BASE_DEC, |
3009 | 15 | NULL, 0x80, "RTMPT IsExHeader flag introduced in enhanced RTMP", HFILL }}, |
3010 | | |
3011 | 15 | { &hf_rtmpt_video_type, |
3012 | 15 | { "Video type", "rtmpt.video.type", FT_UINT8, BASE_DEC, |
3013 | 15 | VALS(rtmpt_video_types), 0x70, NULL, HFILL }}, |
3014 | | |
3015 | 15 | { &hf_rtmpt_video_command, |
3016 | 15 | { "Video command", "rtmpt.video.command", FT_UINT8, BASE_DEC, |
3017 | 15 | VALS(rtmpt_video_commands), 0x0, NULL, HFILL}}, |
3018 | | |
3019 | 15 | { &hf_rtmpt_video_format, |
3020 | 15 | { "Format", "rtmpt.video.format", FT_UINT8, BASE_DEC, |
3021 | 15 | VALS(rtmpt_video_codecs), 0x0f, NULL, HFILL }}, |
3022 | | |
3023 | 15 | { &hf_rtmpt_video_packet_type, |
3024 | 15 | { "Video packet type", "rtmpt.video.packet_type", FT_UINT8, BASE_DEC, |
3025 | 15 | VALS(rtmpt_video_packet_types), 0x0f, NULL, HFILL }}, |
3026 | | |
3027 | 15 | { &hf_rtmpt_video_multitrack_type, |
3028 | 15 | { "Video multitrack type", "rtmpt.video.multitrack.type", FT_UINT8, BASE_DEC, |
3029 | 15 | VALS(rtmpt_av_multitrack_types), 0xf0, NULL, HFILL } }, |
3030 | | |
3031 | 15 | { &hf_rtmpt_video_multitrack_packet_type, |
3032 | 15 | { "Video multitrack packet type", "rtmpt.video.multitrack.packet_type", FT_UINT8, BASE_DEC, |
3033 | 15 | VALS(rtmpt_video_packet_types), 0x0f, NULL, HFILL } }, |
3034 | | |
3035 | 15 | { &hf_rtmpt_video_fourcc, |
3036 | 15 | { "FourCC", "rtmpt.video.fourcc", FT_STRING, BASE_NONE, |
3037 | 15 | NULL, 0x0, NULL, HFILL }}, |
3038 | | |
3039 | 15 | { &hf_rtmpt_video_track_id, |
3040 | 15 | { "Track ID", "rtmpt.video.multitrack.track.id", FT_UINT8, BASE_DEC, |
3041 | 15 | NULL, 0x0, NULL, HFILL } }, |
3042 | | |
3043 | 15 | { &hf_rtmpt_video_track_length, |
3044 | 15 | { "Track length", "rtmpt.video.multitrack.track.length", FT_UINT24, BASE_DEC, |
3045 | 15 | NULL, 0x0, NULL, HFILL } }, |
3046 | | |
3047 | 15 | { &hf_rtmpt_video_data, |
3048 | 15 | { "Video data", "rtmpt.video.data", FT_BYTES, BASE_NONE, |
3049 | 15 | NULL, 0x0, NULL, HFILL }}, |
3050 | | |
3051 | | /* Aggregate packets */ |
3052 | 15 | { &hf_rtmpt_tag_type, |
3053 | 15 | { "Type", "rtmpt.tag.type", FT_UINT8, BASE_DEC, |
3054 | 15 | VALS(rtmpt_tag_vals), 0x0, "RTMPT Aggregate tag type", HFILL }}, |
3055 | | |
3056 | 15 | { &hf_rtmpt_tag_datasize, |
3057 | 15 | { "Data size", "rtmpt.tag.datasize", FT_UINT24, BASE_DEC, |
3058 | 15 | NULL, 0x0, "RTMPT Aggregate tag data size", HFILL }}, |
3059 | | |
3060 | 15 | { &hf_rtmpt_tag_timestamp, |
3061 | 15 | { "Timestamp", "rtmpt.tag.timestamp", FT_UINT24, BASE_DEC, |
3062 | 15 | NULL, 0x0, "RTMPT Aggregate tag timestamp", HFILL }}, |
3063 | | |
3064 | 15 | { &hf_rtmpt_tag_ets, |
3065 | 15 | { "Timestamp Extended", "rtmpt.tag.ets", FT_UINT8, BASE_DEC, |
3066 | 15 | NULL, 0x0, "RTMPT Aggregate tag timestamp extended", HFILL }}, |
3067 | | |
3068 | 15 | { &hf_rtmpt_tag_streamid, |
3069 | 15 | { "Stream ID", "rtmpt.tag.streamid", FT_UINT24, BASE_DEC, |
3070 | 15 | NULL, 0x0, "RTMPT Aggregate tag stream ID", HFILL }}, |
3071 | | |
3072 | 15 | { &hf_rtmpt_tag_tagsize, |
3073 | 15 | { "Previous tag size", "rtmpt.tag.tagsize", FT_UINT32, BASE_DEC, |
3074 | 15 | NULL, 0x0, "RTMPT Aggregate previous tag size", HFILL }} |
3075 | 15 | }; |
3076 | 15 | static int *ett[] = { |
3077 | 15 | &ett_rtmpt, |
3078 | 15 | &ett_rtmpt_handshake, |
3079 | 15 | &ett_rtmpt_header, |
3080 | 15 | &ett_rtmpt_body, |
3081 | 15 | &ett_rtmpt_ucm, |
3082 | 15 | &ett_rtmpt_audio_control, |
3083 | 15 | &ett_rtmpt_video_control, |
3084 | 15 | &ett_rtmpt_audio_multitrack_control, |
3085 | 15 | &ett_rtmpt_audio_multitrack_track, |
3086 | 15 | &ett_rtmpt_video_multitrack_control, |
3087 | 15 | &ett_rtmpt_video_multitrack_track, |
3088 | 15 | &ett_rtmpt_tag, |
3089 | 15 | &ett_rtmpt_tag_data |
3090 | 15 | }; |
3091 | | |
3092 | 15 | module_t *rtmpt_module; |
3093 | | |
3094 | 15 | proto_rtmpt = proto_register_protocol("Real Time Messaging Protocol", "RTMPT", "rtmpt"); |
3095 | 15 | proto_register_field_array(proto_rtmpt, hf, array_length(hf)); |
3096 | 15 | proto_register_subtree_array(ett, array_length(ett)); |
3097 | | |
3098 | 15 | rtmpt_tcp_handle = register_dissector("rtmpt.tcp", dissect_rtmpt_tcp, proto_rtmpt); |
3099 | 15 | rtmpt_http_handle = register_dissector("rtmpt.http", dissect_rtmpt_http, proto_rtmpt); |
3100 | | |
3101 | 15 | rtmpt_module = prefs_register_protocol(proto_rtmpt, NULL); |
3102 | | /* XXX: This desegment preference doesn't do anything */ |
3103 | 15 | prefs_register_bool_preference(rtmpt_module, "desegment", |
3104 | 15 | "Reassemble RTMPT messages spanning multiple TCP segments", |
3105 | 15 | "Whether the RTMPT dissector should reassemble messages spanning multiple TCP segments." |
3106 | 15 | " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\"" |
3107 | 15 | " in the TCP protocol settings.", |
3108 | 15 | &rtmpt_desegment); |
3109 | | |
3110 | 15 | prefs_register_obsolete_preference(rtmpt_module, "max_packet_size"); |
3111 | | |
3112 | 15 | prefs_register_uint_preference(rtmpt_module, "default_chunk_size", |
3113 | 15 | "Default chunk size", |
3114 | 15 | "Chunk size to use for connections where the initial handshake is missing," |
3115 | 15 | " i.e. are already in progress at the beginning of the capture file.", |
3116 | 15 | 10, &rtmpt_default_chunk_size); |
3117 | 15 | } |
3118 | | |
3119 | | void |
3120 | | proto_register_amf(void) |
3121 | 15 | { |
3122 | 15 | static hf_register_info hf[] = { |
3123 | 15 | { &hf_amf_version, |
3124 | 15 | { "AMF version", "amf.version", FT_UINT16, BASE_DEC, |
3125 | 15 | NULL, 0x0, NULL, HFILL }}, |
3126 | | |
3127 | 15 | { &hf_amf_header_count, |
3128 | 15 | { "Header count", "amf.header_count", FT_UINT16, BASE_DEC, |
3129 | 15 | NULL, 0x0, NULL, HFILL }}, |
3130 | | |
3131 | 15 | { &hf_amf_header_name, |
3132 | 15 | { "Name", "amf.header.name", FT_UINT_STRING, BASE_NONE, |
3133 | 15 | NULL, 0x0, NULL, HFILL }}, |
3134 | | |
3135 | 15 | { &hf_amf_header_must_understand, |
3136 | 15 | { "Must understand", "amf.header.must_understand", FT_BOOLEAN, BASE_NONE, |
3137 | 15 | NULL, 0x0, NULL, HFILL }}, |
3138 | | |
3139 | 15 | { &hf_amf_header_length, |
3140 | 15 | { "Length", "amf.header.length", FT_UINT32, BASE_DEC, |
3141 | 15 | NULL, 0x0, NULL, HFILL }}, |
3142 | | |
3143 | | #if 0 |
3144 | | { &hf_amf_header_value_type, |
3145 | | { "Value type", "amf.header.value_type", FT_UINT32, BASE_HEX, |
3146 | | VALS(rtmpt_type_vals), 0x0, NULL, HFILL }}, |
3147 | | #endif |
3148 | | |
3149 | 15 | { &hf_amf_message_count, |
3150 | 15 | { "Message count", "amf.message_count", FT_UINT16, BASE_DEC, |
3151 | 15 | NULL, 0x0, NULL, HFILL }}, |
3152 | | |
3153 | 15 | { &hf_amf_message_target_uri, |
3154 | 15 | { "Target URI", "amf.message.target_uri", FT_UINT_STRING, BASE_NONE, |
3155 | 15 | NULL, 0x0, NULL, HFILL }}, |
3156 | | |
3157 | 15 | { &hf_amf_message_response_uri, |
3158 | 15 | { "Response URI", "amf.message.response_uri", FT_UINT_STRING, BASE_NONE, |
3159 | 15 | NULL, 0x0, NULL, HFILL }}, |
3160 | | |
3161 | 15 | { &hf_amf_message_length, |
3162 | 15 | { "Length", "amf.message.length", FT_UINT32, BASE_DEC, |
3163 | 15 | NULL, 0x0, NULL, HFILL }}, |
3164 | | |
3165 | | |
3166 | | /* AMF basic types */ |
3167 | 15 | { &hf_amf_amf0_type, |
3168 | 15 | { "AMF0 type", "amf.amf0_type", FT_UINT8, BASE_HEX, |
3169 | 15 | VALS(amf0_type_vals), 0x0, NULL, HFILL }}, |
3170 | | |
3171 | 15 | { &hf_amf_amf3_type, |
3172 | 15 | { "AMF3 type", "amf.amf3_type", FT_UINT8, BASE_HEX, |
3173 | 15 | VALS(amf3_type_vals), 0x0, NULL, HFILL }}, |
3174 | | |
3175 | 15 | { &hf_amf_number, |
3176 | 15 | { "Number", "amf.number", FT_DOUBLE, BASE_NONE, |
3177 | 15 | NULL, 0x0, "AMF number", HFILL }}, |
3178 | | |
3179 | 15 | { &hf_amf_integer, |
3180 | 15 | { "Integer", "amf.integer", FT_UINT32, BASE_DEC, |
3181 | 15 | NULL, 0x0, "RTMPT AMF3 integer", HFILL }}, |
3182 | | |
3183 | 15 | { &hf_amf_boolean, |
3184 | 15 | { "Boolean", "amf.boolean", FT_BOOLEAN, BASE_NONE, |
3185 | 15 | NULL, 0x0, "AMF boolean", HFILL }}, |
3186 | | |
3187 | 15 | { &hf_amf_stringlength, |
3188 | 15 | { "String length", "amf.stringlength", FT_UINT32, BASE_DEC, |
3189 | 15 | NULL, 0x0, "AMF string length", HFILL }}, |
3190 | | |
3191 | 15 | { &hf_amf_string, |
3192 | 15 | { "String", "amf.string", FT_STRING, BASE_NONE, |
3193 | 15 | NULL, 0x0, "AMF string", HFILL }}, |
3194 | | |
3195 | 15 | { &hf_amf_string_reference, |
3196 | 15 | { "String reference", "amf.string_reference", FT_UINT32, BASE_DEC, |
3197 | 15 | NULL, 0x0, "RTMPT AMF3 string reference", HFILL }}, |
3198 | | |
3199 | 15 | { &hf_amf_object_reference, |
3200 | 15 | { "Object reference", "amf.object_reference", FT_UINT32, BASE_DEC, |
3201 | 15 | NULL, 0x0, "AMF object reference", HFILL }}, |
3202 | | |
3203 | 15 | { &hf_amf_date, |
3204 | 15 | { "Date", "amf.date", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, |
3205 | 15 | NULL, 0x0, "AMF date", HFILL }}, |
3206 | | |
3207 | | #if 0 |
3208 | | { &hf_amf_longstringlength, |
3209 | | { "String length", "amf.longstringlength", FT_UINT32, BASE_DEC, |
3210 | | NULL, 0x0, "AMF long string length", HFILL }}, |
3211 | | #endif |
3212 | | |
3213 | 15 | { &hf_amf_longstring, |
3214 | 15 | { "Long string", "amf.longstring", FT_STRING, BASE_NONE, |
3215 | 15 | NULL, 0x0, "AMF long string", HFILL }}, |
3216 | | |
3217 | 15 | { &hf_amf_xml_doc, |
3218 | 15 | { "XML document", "amf.xml_doc", FT_STRING, BASE_NONE, |
3219 | 15 | NULL, 0x0, "AMF XML document", HFILL }}, |
3220 | | |
3221 | 15 | { &hf_amf_xmllength, |
3222 | 15 | { "XML text length", "amf.xmllength", FT_UINT32, BASE_DEC, |
3223 | 15 | NULL, 0x0, "AMF E4X XML length", HFILL }}, |
3224 | | |
3225 | 15 | { &hf_amf_xml, |
3226 | 15 | { "XML", "amf.xml", FT_STRING, BASE_NONE, |
3227 | 15 | NULL, 0x0, "AMF E4X XML", HFILL }}, |
3228 | | |
3229 | 15 | { &hf_amf_int64, |
3230 | 15 | { "Int64", "amf.int64", FT_INT64, BASE_DEC, |
3231 | 15 | NULL, 0x0, "AMF int64", HFILL }}, |
3232 | | |
3233 | 15 | { &hf_amf_bytearraylength, |
3234 | 15 | { "ByteArray length", "amf.bytearraylength", FT_UINT32, BASE_DEC, |
3235 | 15 | NULL, 0x0, "RTMPT AMF3 ByteArray length", HFILL }}, |
3236 | | |
3237 | 15 | { &hf_amf_bytearray, |
3238 | 15 | { "ByteArray", "amf.bytearray", FT_BYTES, BASE_NONE, |
3239 | 15 | NULL, 0x0, "RTMPT AMF3 ByteArray", HFILL }}, |
3240 | | |
3241 | | /* AMF object types and subfields of the object types */ |
3242 | 15 | { &hf_amf_object, |
3243 | 15 | { "Object", "amf.object", FT_NONE, BASE_NONE, |
3244 | 15 | NULL, 0x0, "AMF object", HFILL }}, |
3245 | | |
3246 | 15 | { &hf_amf_traitcount, |
3247 | 15 | { "Trait count", "amf.traitcount", FT_UINT32, BASE_DEC, |
3248 | 15 | NULL, 0x0, "AMF count of traits for an object", HFILL }}, |
3249 | | |
3250 | 15 | { &hf_amf_classnamelength, |
3251 | 15 | { "Class name length", "amf.classnamelength", FT_UINT32, BASE_DEC, |
3252 | 15 | NULL, 0x0, "AMF class name length", HFILL }}, |
3253 | | |
3254 | 15 | { &hf_amf_classname, |
3255 | 15 | { "Class name", "amf.classname", FT_STRING, BASE_NONE, |
3256 | 15 | NULL, 0x0, "AMF class name", HFILL }}, |
3257 | | |
3258 | 15 | { &hf_amf_membernamelength, |
3259 | 15 | { "Member name length", "amf.membernamelength", FT_UINT32, BASE_DEC, |
3260 | 15 | NULL, 0x0, "AMF member name length", HFILL }}, |
3261 | | |
3262 | 15 | { &hf_amf_membername, |
3263 | 15 | { "Member name", "amf.membername", FT_STRING, BASE_NONE, |
3264 | 15 | NULL, 0x0, "AMF member name", HFILL }}, |
3265 | | |
3266 | 15 | { &hf_amf_trait_reference, |
3267 | 15 | { "Trait reference", "amf.trait_reference", FT_UINT32, BASE_DEC, |
3268 | 15 | NULL, 0x0, "AMF trait reference", HFILL }}, |
3269 | | |
3270 | 15 | { &hf_amf_ecmaarray, |
3271 | 15 | { "ECMA array", "amf.ecmaarray", FT_NONE, BASE_NONE, |
3272 | 15 | NULL, 0x0, "AMF ECMA array", HFILL }}, |
3273 | | |
3274 | 15 | { &hf_amf_strictarray, |
3275 | 15 | { "Strict array", "amf.strictarray", FT_NONE, BASE_NONE, |
3276 | 15 | NULL, 0x0, "AMF strict array", HFILL }}, |
3277 | | |
3278 | 15 | { &hf_amf_array, |
3279 | 15 | { "Array", "amf.array", FT_NONE, BASE_NONE, |
3280 | 15 | NULL, 0x0, "RTMPT AMF3 array", HFILL }}, |
3281 | | |
3282 | 15 | { &hf_amf_arraylength, |
3283 | 15 | { "Array length", "amf.arraylength", FT_UINT32, BASE_DEC, |
3284 | 15 | NULL, 0x0, "AMF array length", HFILL }}, |
3285 | | |
3286 | 15 | { &hf_amf_arraydenselength, |
3287 | 15 | { "Length of dense portion", "amf.arraydenselength", FT_UINT32, BASE_DEC, |
3288 | 15 | NULL, 0x0, "AMF length of dense portion of array", HFILL }}, |
3289 | | |
3290 | 15 | { &hf_amf_end_of_object_marker, |
3291 | 15 | { "End Of Object Marker", "amf.end_of_object_marker", FT_NONE, BASE_NONE, |
3292 | 15 | NULL, 0x0, NULL, HFILL }}, |
3293 | | |
3294 | 15 | { &hf_amf_end_of_associative_part, |
3295 | 15 | { "End of associative part", "amf.end_of_associative_part", FT_NONE, BASE_NONE, |
3296 | 15 | NULL, 0x0, NULL, HFILL }}, |
3297 | | |
3298 | 15 | { &hf_amf_end_of_dynamic_members, |
3299 | 15 | { "End Of dynamic members", "amf.end_of_dynamic_members", FT_NONE, BASE_NONE, |
3300 | 15 | NULL, 0x0, NULL, HFILL }}, |
3301 | 15 | }; |
3302 | | |
3303 | 15 | static ei_register_info ei[] = { |
3304 | 15 | { &ei_amf_loop, { "amf.loop", PI_MALFORMED, PI_ERROR, "Loop in AMF dissection", EXPFILL }} |
3305 | 15 | }; |
3306 | | |
3307 | | |
3308 | 15 | static int *ett[] = { |
3309 | 15 | &ett_amf, |
3310 | 15 | &ett_amf_headers, |
3311 | 15 | &ett_amf_messages, |
3312 | 15 | &ett_amf_value, |
3313 | 15 | &ett_amf_property, |
3314 | 15 | &ett_amf_string, |
3315 | 15 | &ett_amf_array_element, |
3316 | 15 | &ett_amf_traits, |
3317 | 15 | &ett_amf_trait_member, |
3318 | 15 | }; |
3319 | | |
3320 | 15 | expert_module_t* expert_amf; |
3321 | | |
3322 | 15 | proto_amf = proto_register_protocol("Action Message Format", "AMF", "amf"); |
3323 | 15 | proto_register_field_array(proto_amf, hf, array_length(hf)); |
3324 | 15 | proto_register_subtree_array(ett, array_length(ett)); |
3325 | 15 | expert_amf = expert_register_protocol(proto_amf); |
3326 | 15 | expert_register_field_array(expert_amf, ei, array_length(ei)); |
3327 | | |
3328 | 15 | amf_handle = register_dissector("amf", dissect_amf, proto_amf); |
3329 | 15 | } |
3330 | | |
3331 | | void |
3332 | | proto_reg_handoff_rtmpt(void) |
3333 | 15 | { |
3334 | 15 | heur_dissector_add("tcp", dissect_rtmpt_heur, "RTMPT over TCP", "rtmpt_tcp", proto_rtmpt, HEURISTIC_DISABLE); |
3335 | 15 | dissector_add_uint_with_preference("tcp.port", RTMP_PORT, rtmpt_tcp_handle); |
3336 | | |
3337 | 15 | dissector_add_string("media_type", "application/x-fcs", rtmpt_http_handle); |
3338 | 15 | dissector_add_string("media_type", "application/x-amf", amf_handle); |
3339 | 15 | } |
3340 | | |
3341 | | /* |
3342 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
3343 | | * |
3344 | | * Local variables: |
3345 | | * c-basic-offset: 8 |
3346 | | * tab-width: 8 |
3347 | | * indent-tabs-mode: nil |
3348 | | * End: |
3349 | | * |
3350 | | * vi: set shiftwidth=8 tabstop=8 expandtab: |
3351 | | * :indentSize=8:tabSize=8:noTabs=true: |
3352 | | */ |