Skip to main content

packet_dissector_rtp/
lib.rs

1//! RTP (Real-time Transport Protocol) dissector.
2//!
3//! ## References
4//! - RFC 3550, Section 5.1 — RTP Fixed Header Fields:
5//!   <https://www.rfc-editor.org/rfc/rfc3550#section-5.1>
6//! - RFC 3550, Section 5.3.1 — RTP Header Extension:
7//!   <https://www.rfc-editor.org/rfc/rfc3550#section-5.3.1>
8
9#![deny(missing_docs)]
10
11use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
12use packet_dissector_core::error::PacketError;
13use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue};
14use packet_dissector_core::packet::DissectBuffer;
15use packet_dissector_core::util::{read_be_u16, read_be_u32};
16
17/// Minimum RTP header size in bytes (fixed header without CSRC list or extension).
18/// RFC 3550, Section 5.1 — "The first twelve octets are present in every RTP packet"
19/// <https://www.rfc-editor.org/rfc/rfc3550#section-5.1>
20const MIN_HEADER_SIZE: usize = 12;
21
22/// RTP version defined by RFC 3550.
23/// RFC 3550, Section 5.1 — "The version defined by this specification is two (2)."
24/// <https://www.rfc-editor.org/rfc/rfc3550#section-5.1>
25const RTP_VERSION: u8 = 2;
26
27/// Field descriptor indices for [`FIELD_DESCRIPTORS`].
28const FD_VERSION: usize = 0;
29const FD_PADDING: usize = 1;
30const FD_EXTENSION: usize = 2;
31const FD_CSRC_COUNT: usize = 3;
32const FD_MARKER: usize = 4;
33const FD_PAYLOAD_TYPE: usize = 5;
34const FD_SEQUENCE_NUMBER: usize = 6;
35const FD_TIMESTAMP: usize = 7;
36const FD_SSRC: usize = 8;
37const FD_CSRC_LIST: usize = 9;
38const FD_PAYLOAD: usize = 10;
39const FD_PADDING_LENGTH: usize = 11;
40const FD_EXT_PROFILE: usize = 12;
41const FD_EXT_LENGTH: usize = 13;
42const FD_EXT_DATA: usize = 14;
43
44static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
45    FieldDescriptor::new("version", "Version", FieldType::U8),
46    FieldDescriptor::new("padding", "Padding", FieldType::U8),
47    FieldDescriptor::new("extension", "Extension", FieldType::U8),
48    FieldDescriptor::new("csrc_count", "CSRC Count", FieldType::U8),
49    FieldDescriptor::new("marker", "Marker", FieldType::U8),
50    FieldDescriptor::new("payload_type", "Payload Type", FieldType::U8),
51    FieldDescriptor::new("sequence_number", "Sequence Number", FieldType::U16),
52    FieldDescriptor::new("timestamp", "Timestamp", FieldType::U32),
53    FieldDescriptor::new("ssrc", "SSRC", FieldType::U32),
54    FieldDescriptor::new("csrc_list", "CSRC List", FieldType::Array).optional(),
55    FieldDescriptor::new("payload", "Payload", FieldType::Bytes).optional(),
56    FieldDescriptor::new("padding_length", "Padding Length", FieldType::U8).optional(),
57    FieldDescriptor::new("ext_profile", "Extension Profile", FieldType::U16).optional(),
58    FieldDescriptor::new("ext_length", "Extension Length", FieldType::U16).optional(),
59    FieldDescriptor::new("ext_data", "Extension Data", FieldType::Bytes).optional(),
60];
61
62/// RTP dissector.
63pub struct RtpDissector;
64
65impl Dissector for RtpDissector {
66    fn name(&self) -> &'static str {
67        "Real-time Transport Protocol"
68    }
69
70    fn short_name(&self) -> &'static str {
71        "RTP"
72    }
73
74    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
75        FIELD_DESCRIPTORS
76    }
77
78    fn dissect<'pkt>(
79        &self,
80        data: &'pkt [u8],
81        buf: &mut DissectBuffer<'pkt>,
82        offset: usize,
83    ) -> Result<DissectResult, PacketError> {
84        // RFC 3550, Section 5.1 — minimum 12-byte fixed header
85        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
86        if data.len() < MIN_HEADER_SIZE {
87            return Err(PacketError::Truncated {
88                expected: MIN_HEADER_SIZE,
89                actual: data.len(),
90            });
91        }
92
93        // RFC 3550, Section 5.1 — Fixed header fields
94        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
95        let byte0 = data[0];
96        let version = (byte0 >> 6) & 0x03;
97        let padding = (byte0 >> 5) & 0x01;
98        let extension_bit = (byte0 >> 4) & 0x01;
99        let cc = byte0 & 0x0F;
100
101        // RFC 3550, Section 5.1 — "The version defined by this specification is two (2)."
102        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
103        if version != RTP_VERSION {
104            return Err(PacketError::InvalidFieldValue {
105                field: "version",
106                value: version as u32,
107            });
108        }
109
110        let byte1 = data[1];
111        let marker = (byte1 >> 7) & 0x01;
112        let payload_type = byte1 & 0x7F;
113        let sequence_number = read_be_u16(data, 2)?;
114        let timestamp = read_be_u32(data, 4)?;
115        let ssrc = read_be_u32(data, 8)?;
116
117        let csrc_end = MIN_HEADER_SIZE + (cc as usize) * 4;
118        if data.len() < csrc_end {
119            return Err(PacketError::Truncated {
120                expected: csrc_end,
121                actual: data.len(),
122            });
123        }
124
125        // Compute header_end before begin_layer so we can set the correct range.
126        // We need to check extension and padding sizes first.
127        let mut header_end = csrc_end;
128
129        // RFC 3550, Section 5.3.1 — Header Extension
130        // https://www.rfc-editor.org/rfc/rfc3550#section-5.3.1
131        let ext_info = if extension_bit == 1 {
132            let ext_header_start = csrc_end;
133
134            // Need at least 4 bytes for extension header (profile + length)
135            if data.len() < ext_header_start + 4 {
136                return Err(PacketError::Truncated {
137                    expected: ext_header_start + 4,
138                    actual: data.len(),
139                });
140            }
141
142            let ext_profile = read_be_u16(data, ext_header_start)?;
143
144            // RFC 3550, Section 5.3.1 — length counts 32-bit words, excluding
145            // the 4-byte extension header itself (zero is valid).
146            // https://www.rfc-editor.org/rfc/rfc3550#section-5.3.1
147            let ext_length = read_be_u16(data, ext_header_start + 2)?;
148
149            let ext_data_bytes = (ext_length as usize) * 4;
150            let ext_total = 4 + ext_data_bytes;
151
152            if data.len() < ext_header_start + ext_total {
153                return Err(PacketError::Truncated {
154                    expected: ext_header_start + ext_total,
155                    actual: data.len(),
156                });
157            }
158
159            header_end = ext_header_start + ext_total;
160            Some((
161                ext_header_start,
162                ext_profile,
163                ext_length,
164                ext_data_bytes,
165                ext_total,
166            ))
167        } else {
168            None
169        };
170
171        // RFC 3550, Section 5.1 — "If the padding bit is set, the packet
172        // contains one or more additional padding octets at the end which are
173        // not part of the payload. The last octet of the padding contains a
174        // count of how many padding octets should be ignored, including itself."
175        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
176        let pad_count = if padding == 1 {
177            if data.len() <= header_end {
178                return Err(PacketError::InvalidHeader(
179                    "RTP padding bit set but no payload/padding bytes present",
180                ));
181            }
182            let pc = data[data.len() - 1];
183            if pc == 0 {
184                return Err(PacketError::InvalidHeader(
185                    "RTP padding count must be >= 1 (includes the count byte itself)",
186                ));
187            }
188            if (pc as usize) > data.len() - header_end {
189                return Err(PacketError::InvalidHeader(
190                    "RTP padding count exceeds available payload bytes",
191                ));
192            }
193            Some(pc)
194        } else {
195            None
196        };
197
198        // RFC 3550, Section 5.1 — the RTP packet is [fixed header | CSRC list |
199        // optional extension | payload | optional padding]. Because the
200        // payload is opaque to the dissector and no next layer follows
201        // (DispatchHint::End), the RTP layer claims the entire packet.
202        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
203        buf.begin_layer(
204            self.short_name(),
205            None,
206            FIELD_DESCRIPTORS,
207            offset..offset + data.len(),
208        );
209
210        buf.push_field(
211            &FIELD_DESCRIPTORS[FD_VERSION],
212            FieldValue::U8(version),
213            offset..offset + 1,
214        );
215        buf.push_field(
216            &FIELD_DESCRIPTORS[FD_PADDING],
217            FieldValue::U8(padding),
218            offset..offset + 1,
219        );
220        buf.push_field(
221            &FIELD_DESCRIPTORS[FD_EXTENSION],
222            FieldValue::U8(extension_bit),
223            offset..offset + 1,
224        );
225        buf.push_field(
226            &FIELD_DESCRIPTORS[FD_CSRC_COUNT],
227            FieldValue::U8(cc),
228            offset..offset + 1,
229        );
230        buf.push_field(
231            &FIELD_DESCRIPTORS[FD_MARKER],
232            FieldValue::U8(marker),
233            offset + 1..offset + 2,
234        );
235        buf.push_field(
236            &FIELD_DESCRIPTORS[FD_PAYLOAD_TYPE],
237            FieldValue::U8(payload_type),
238            offset + 1..offset + 2,
239        );
240        buf.push_field(
241            &FIELD_DESCRIPTORS[FD_SEQUENCE_NUMBER],
242            FieldValue::U16(sequence_number),
243            offset + 2..offset + 4,
244        );
245        buf.push_field(
246            &FIELD_DESCRIPTORS[FD_TIMESTAMP],
247            FieldValue::U32(timestamp),
248            offset + 4..offset + 8,
249        );
250        buf.push_field(
251            &FIELD_DESCRIPTORS[FD_SSRC],
252            FieldValue::U32(ssrc),
253            offset + 8..offset + 12,
254        );
255
256        if cc > 0 {
257            let array_idx = buf.begin_container(
258                &FIELD_DESCRIPTORS[FD_CSRC_LIST],
259                FieldValue::Array(0..0),
260                (offset + MIN_HEADER_SIZE)..(offset + csrc_end),
261            );
262            for i in 0..cc as usize {
263                let csrc_offset = MIN_HEADER_SIZE + i * 4;
264                let csrc_val = read_be_u32(data, csrc_offset)?;
265                buf.push_field(
266                    &FIELD_DESCRIPTORS[FD_CSRC_LIST],
267                    FieldValue::U32(csrc_val),
268                    (offset + csrc_offset)..(offset + csrc_offset + 4),
269                );
270            }
271            buf.end_container(array_idx);
272        }
273
274        if let Some((ext_header_start, ext_profile, ext_length, ext_data_bytes, ext_total)) =
275            ext_info
276        {
277            buf.push_field(
278                &FIELD_DESCRIPTORS[FD_EXT_PROFILE],
279                FieldValue::U16(ext_profile),
280                (offset + ext_header_start)..(offset + ext_header_start + 2),
281            );
282
283            buf.push_field(
284                &FIELD_DESCRIPTORS[FD_EXT_LENGTH],
285                FieldValue::U16(ext_length),
286                (offset + ext_header_start + 2)..(offset + ext_header_start + 4),
287            );
288
289            if ext_data_bytes > 0 {
290                buf.push_field(
291                    &FIELD_DESCRIPTORS[FD_EXT_DATA],
292                    FieldValue::Bytes(&data[ext_header_start + 4..ext_header_start + ext_total]),
293                    (offset + ext_header_start + 4)..(offset + ext_header_start + ext_total),
294                );
295            }
296        }
297
298        // RFC 3550, Section 5.1 — payload follows the fixed header, CSRC list
299        // and optional extension, and precedes any padding at the end of the
300        // packet. Treat the payload as opaque bytes.
301        // https://www.rfc-editor.org/rfc/rfc3550#section-5.1
302        let payload_end = data.len() - pad_count.map(|pc| pc as usize).unwrap_or(0);
303        if payload_end > header_end {
304            buf.push_field(
305                &FIELD_DESCRIPTORS[FD_PAYLOAD],
306                FieldValue::Bytes(&data[header_end..payload_end]),
307                (offset + header_end)..(offset + payload_end),
308            );
309        }
310
311        if let Some(pc) = pad_count {
312            buf.push_field(
313                &FIELD_DESCRIPTORS[FD_PADDING_LENGTH],
314                FieldValue::U8(pc),
315                (offset + data.len() - 1)..(offset + data.len()),
316            );
317        }
318
319        buf.end_layer();
320
321        // RTP payload is audio/video data — no further protocol dissection.
322        // The whole RTP packet (header + payload + padding) is consumed.
323        Ok(DissectResult::new(data.len(), DispatchHint::End))
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    // # RFC 3550 (RTP) Coverage
332    //
333    // | RFC Section | Description                | Test                                   |
334    // |-------------|----------------------------|----------------------------------------|
335    // | 5.1         | Fixed Header Fields        | parse_rtp_basic                        |
336    // | 5.1         | Version validation         | parse_rtp_invalid_version              |
337    // | 5.1         | Payload extraction         | parse_rtp_with_payload                 |
338    // | 5.1         | Padding bit                | parse_rtp_with_padding                 |
339    // | 5.1         | Padding — no payload       | parse_rtp_padding_no_payload           |
340    // | 5.1         | Padding — count zero       | parse_rtp_padding_count_zero           |
341    // | 5.1         | Padding — count overflow   | parse_rtp_padding_count_exceeds_payload|
342    // | 5.1         | Padding — only padding     | parse_rtp_padding_only_no_payload_data |
343    // | 5.1         | Marker bit                 | parse_rtp_marker_set                   |
344    // | 5.1         | CSRC list                  | parse_rtp_with_csrc                    |
345    // | 5.1         | CSRC max (15)              | parse_rtp_with_max_csrc                |
346    // | 5.1         | Truncated header           | parse_rtp_truncated                    |
347    // | 5.1         | Truncated CSRC             | parse_rtp_truncated_csrc               |
348    // | 5.3.1       | Header Extension           | parse_rtp_with_extension               |
349    // | 5.3.1       | Zero-length extension      | parse_rtp_zero_length_extension        |
350    // | 5.1 + 5.3.1 | CSRC + Extension           | parse_rtp_with_csrc_and_extension      |
351    // | 5.3.1       | Truncated extension header | parse_rtp_truncated_extension_header   |
352    // | 5.3.1       | Truncated extension data   | parse_rtp_truncated_extension_data     |
353
354    /// Build a minimal RTP header (12 bytes): V=2, P=0, X=0, CC=0, M=0, PT=0.
355    fn minimal_rtp_header(pt: u8, seq: u16, ts: u32, ssrc: u32) -> Vec<u8> {
356        let mut buf = Vec::with_capacity(12);
357        // byte 0: V=2, P=0, X=0, CC=0
358        buf.push(0x80);
359        // byte 1: M=0, PT
360        buf.push(pt & 0x7F);
361        buf.extend_from_slice(&seq.to_be_bytes());
362        buf.extend_from_slice(&ts.to_be_bytes());
363        buf.extend_from_slice(&ssrc.to_be_bytes());
364        buf
365    }
366
367    #[test]
368    fn parse_rtp_basic() {
369        let data = minimal_rtp_header(111, 1000, 160_000, 0x12345678);
370        let mut buf = DissectBuffer::new();
371        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
372
373        // RFC 3550, Section 5.1 — the RTP layer claims the full packet (no
374        // payload here, so bytes_consumed equals the fixed header size).
375        assert_eq!(result.bytes_consumed, 12);
376        assert_eq!(result.next, DispatchHint::End);
377        assert_eq!(buf.layers().len(), 1);
378
379        let layer = &buf.layers()[0];
380        assert_eq!(layer.name, "RTP");
381        assert_eq!(layer.range, 0..12);
382        assert_eq!(
383            buf.field_by_name(layer, "version").unwrap().value,
384            FieldValue::U8(2)
385        );
386        assert_eq!(
387            buf.field_by_name(layer, "padding").unwrap().value,
388            FieldValue::U8(0)
389        );
390        assert_eq!(
391            buf.field_by_name(layer, "extension").unwrap().value,
392            FieldValue::U8(0)
393        );
394        assert_eq!(
395            buf.field_by_name(layer, "csrc_count").unwrap().value,
396            FieldValue::U8(0)
397        );
398        assert_eq!(
399            buf.field_by_name(layer, "marker").unwrap().value,
400            FieldValue::U8(0)
401        );
402        assert_eq!(
403            buf.field_by_name(layer, "payload_type").unwrap().value,
404            FieldValue::U8(111)
405        );
406        assert_eq!(
407            buf.field_by_name(layer, "sequence_number").unwrap().value,
408            FieldValue::U16(1000)
409        );
410        assert_eq!(
411            buf.field_by_name(layer, "timestamp").unwrap().value,
412            FieldValue::U32(160_000)
413        );
414        assert_eq!(
415            buf.field_by_name(layer, "ssrc").unwrap().value,
416            FieldValue::U32(0x12345678)
417        );
418        assert!(buf.field_by_name(layer, "csrc_list").is_none());
419    }
420
421    #[test]
422    fn parse_rtp_with_padding() {
423        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
424        // Set P=1
425        data[0] |= 0x20;
426        // Append payload + padding: 4 bytes audio data + 4 bytes padding (last byte = count)
427        data.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]); // payload
428        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); // 4 bytes padding, count=4
429
430        let mut buf = DissectBuffer::new();
431        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
432
433        // RFC 3550, Section 5.1 — layer covers the entire RTP packet (header,
434        // payload, and padding). The padding_length field sits inside it.
435        assert_eq!(result.bytes_consumed, 20);
436        let layer = &buf.layers()[0];
437        assert_eq!(layer.range, 0..20);
438        assert_eq!(
439            buf.field_by_name(layer, "padding").unwrap().value,
440            FieldValue::U8(1)
441        );
442        assert_eq!(
443            buf.field_by_name(layer, "payload").unwrap().value,
444            FieldValue::Bytes(&[0xAA, 0xBB, 0xCC, 0xDD])
445        );
446        assert_eq!(buf.field_by_name(layer, "payload").unwrap().range, 12..16);
447        assert_eq!(
448            buf.field_by_name(layer, "padding_length").unwrap().value,
449            FieldValue::U8(4)
450        );
451        assert_eq!(
452            buf.field_by_name(layer, "padding_length").unwrap().range,
453            19..20
454        );
455    }
456
457    #[test]
458    fn parse_rtp_with_payload() {
459        // Fixed header + payload (no padding, no CSRC, no extension).
460        let mut data = minimal_rtp_header(96, 1, 100, 0xDEADBEEF);
461        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05]);
462
463        let mut buf = DissectBuffer::new();
464        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
465
466        // RFC 3550, Section 5.1 — the RTP packet comprises header + payload;
467        // the dissector consumes the whole packet.
468        assert_eq!(result.bytes_consumed, 17);
469        let layer = &buf.layers()[0];
470        assert_eq!(layer.range, 0..17);
471        assert_eq!(
472            buf.field_by_name(layer, "payload").unwrap().value,
473            FieldValue::Bytes(&[0x01, 0x02, 0x03, 0x04, 0x05])
474        );
475        assert_eq!(buf.field_by_name(layer, "payload").unwrap().range, 12..17);
476        assert!(buf.field_by_name(layer, "padding_length").is_none());
477    }
478
479    #[test]
480    fn parse_rtp_padding_only_no_payload_data() {
481        // P=1 with padding bytes only (no real payload). Valid per RFC 3550.
482        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
483        data[0] |= 0x20;
484        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); // 4-byte padding, count=4
485
486        let mut buf = DissectBuffer::new();
487        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
488
489        assert_eq!(result.bytes_consumed, 16);
490        let layer = &buf.layers()[0];
491        assert_eq!(layer.range, 0..16);
492        // No payload bytes between the header and the padding.
493        assert!(buf.field_by_name(layer, "payload").is_none());
494        assert_eq!(
495            buf.field_by_name(layer, "padding_length").unwrap().value,
496            FieldValue::U8(4)
497        );
498    }
499
500    #[test]
501    fn parse_rtp_padding_no_payload() {
502        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
503        // Set P=1 but no trailing bytes
504        data[0] |= 0x20;
505        let mut buf = DissectBuffer::new();
506        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
507        assert!(
508            matches!(err, PacketError::InvalidHeader(_)),
509            "expected InvalidHeader for P=1 with no payload, got {err:?}"
510        );
511    }
512
513    #[test]
514    fn parse_rtp_padding_count_zero() {
515        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
516        data[0] |= 0x20;
517        // Last byte = 0 is invalid (count must include itself, so >= 1)
518        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
519        let mut buf = DissectBuffer::new();
520        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
521        assert!(
522            matches!(err, PacketError::InvalidHeader(_)),
523            "expected InvalidHeader for padding count 0, got {err:?}"
524        );
525    }
526
527    #[test]
528    fn parse_rtp_padding_count_exceeds_payload() {
529        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
530        data[0] |= 0x20;
531        // Only 2 bytes of payload but padding count says 10
532        data.extend_from_slice(&[0x00, 0x0A]);
533        let mut buf = DissectBuffer::new();
534        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
535        assert!(
536            matches!(err, PacketError::InvalidHeader(_)),
537            "expected InvalidHeader for excessive padding count, got {err:?}"
538        );
539    }
540
541    #[test]
542    fn parse_rtp_marker_set() {
543        let mut data = minimal_rtp_header(96, 500, 8000, 0x11223344);
544        // Set M=1
545        data[1] |= 0x80;
546        let mut buf = DissectBuffer::new();
547        RtpDissector.dissect(&data, &mut buf, 0).unwrap();
548
549        let layer = &buf.layers()[0];
550        assert_eq!(
551            buf.field_by_name(layer, "marker").unwrap().value,
552            FieldValue::U8(1)
553        );
554        assert_eq!(
555            buf.field_by_name(layer, "payload_type").unwrap().value,
556            FieldValue::U8(96)
557        );
558    }
559
560    #[test]
561    fn parse_rtp_with_csrc() {
562        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
563        // Set CC=2
564        data[0] = (data[0] & 0xF0) | 0x02;
565        // Append 2 CSRC entries
566        data.extend_from_slice(&0x11111111u32.to_be_bytes());
567        data.extend_from_slice(&0x22222222u32.to_be_bytes());
568
569        let mut buf = DissectBuffer::new();
570        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
571
572        assert_eq!(result.bytes_consumed, 20); // 12 + 2*4
573        let layer = &buf.layers()[0];
574        assert_eq!(layer.range, 0..20);
575        assert_eq!(
576            buf.field_by_name(layer, "csrc_count").unwrap().value,
577            FieldValue::U8(2)
578        );
579
580        let csrc_list = buf.field_by_name(layer, "csrc_list").unwrap();
581        let range = match &csrc_list.value {
582            FieldValue::Array(r) => r.clone(),
583            _ => panic!("expected Array"),
584        };
585        let elements = buf.nested_fields(&range);
586        assert_eq!(elements.len(), 2);
587        assert_eq!(elements[0].value, FieldValue::U32(0x11111111));
588        assert_eq!(elements[1].value, FieldValue::U32(0x22222222));
589    }
590
591    #[test]
592    fn parse_rtp_with_extension() {
593        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
594        // Set X=1
595        data[0] |= 0x10;
596        // Extension header: profile=0xBEDE, length=1 (1 × 32-bit word = 4 bytes)
597        data.extend_from_slice(&0xBEDEu16.to_be_bytes());
598        data.extend_from_slice(&1u16.to_be_bytes());
599        // Extension data: 4 bytes
600        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
601
602        let mut buf = DissectBuffer::new();
603        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
604
605        assert_eq!(result.bytes_consumed, 20); // 12 + 4 (ext header) + 4 (ext data)
606        let layer = &buf.layers()[0];
607        assert_eq!(layer.range, 0..20);
608        assert_eq!(
609            buf.field_by_name(layer, "extension").unwrap().value,
610            FieldValue::U8(1)
611        );
612        assert_eq!(
613            buf.field_by_name(layer, "ext_profile").unwrap().value,
614            FieldValue::U16(0xBEDE)
615        );
616        assert_eq!(
617            buf.field_by_name(layer, "ext_length").unwrap().value,
618            FieldValue::U16(1)
619        );
620        assert_eq!(
621            buf.field_by_name(layer, "ext_data").unwrap().value,
622            FieldValue::Bytes(&[0x01, 0x02, 0x03, 0x04])
623        );
624    }
625
626    #[test]
627    fn parse_rtp_zero_length_extension() {
628        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
629        // Set X=1
630        data[0] |= 0x10;
631        // Extension header: profile=0x1234, length=0 (zero is valid per RFC 3550)
632        data.extend_from_slice(&0x1234u16.to_be_bytes());
633        data.extend_from_slice(&0u16.to_be_bytes());
634
635        let mut buf = DissectBuffer::new();
636        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
637
638        assert_eq!(result.bytes_consumed, 16); // 12 + 4 (ext header only)
639        let layer = &buf.layers()[0];
640        assert_eq!(
641            buf.field_by_name(layer, "ext_profile").unwrap().value,
642            FieldValue::U16(0x1234)
643        );
644        assert_eq!(
645            buf.field_by_name(layer, "ext_length").unwrap().value,
646            FieldValue::U16(0)
647        );
648        assert!(buf.field_by_name(layer, "ext_data").is_none());
649    }
650
651    #[test]
652    fn parse_rtp_with_csrc_and_extension() {
653        let mut data = minimal_rtp_header(8, 42, 320_000, 0xDEADBEEF);
654        // Set CC=1, X=1
655        data[0] = (data[0] & 0xE0) | 0x11; // V=2, P=0, X=1, CC=1
656        // CSRC entry
657        data.extend_from_slice(&0xCAFEBABEu32.to_be_bytes());
658        // Extension header: profile=0xABCD, length=2
659        data.extend_from_slice(&0xABCDu16.to_be_bytes());
660        data.extend_from_slice(&2u16.to_be_bytes());
661        // Extension data: 8 bytes
662        data.extend_from_slice(&[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80]);
663
664        let mut buf = DissectBuffer::new();
665        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
666
667        // 12 (fixed) + 4 (1 CSRC) + 4 (ext header) + 8 (ext data) = 28
668        assert_eq!(result.bytes_consumed, 28);
669
670        let layer = &buf.layers()[0];
671        assert_eq!(layer.range, 0..28);
672        assert_eq!(
673            buf.field_by_name(layer, "csrc_count").unwrap().value,
674            FieldValue::U8(1)
675        );
676        let csrc_list = buf.field_by_name(layer, "csrc_list").unwrap();
677        let range = match &csrc_list.value {
678            FieldValue::Array(r) => r.clone(),
679            _ => panic!("expected Array"),
680        };
681        let elements = buf.nested_fields(&range);
682        assert_eq!(elements.len(), 1);
683        assert_eq!(elements[0].value, FieldValue::U32(0xCAFEBABE));
684
685        assert_eq!(
686            buf.field_by_name(layer, "ext_profile").unwrap().value,
687            FieldValue::U16(0xABCD)
688        );
689        assert_eq!(
690            buf.field_by_name(layer, "ext_length").unwrap().value,
691            FieldValue::U16(2)
692        );
693        assert_eq!(
694            buf.field_by_name(layer, "ext_data").unwrap().value,
695            FieldValue::Bytes(&[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80])
696        );
697    }
698
699    #[test]
700    fn parse_rtp_truncated() {
701        let data = [0x80, 0x00, 0x00]; // Only 3 bytes
702        let mut buf = DissectBuffer::new();
703        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
704        assert!(
705            matches!(
706                err,
707                PacketError::Truncated {
708                    expected: 12,
709                    actual: 3
710                }
711            ),
712            "expected Truncated, got {err:?}"
713        );
714    }
715
716    #[test]
717    fn parse_rtp_invalid_version() {
718        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
719        // Set version to 3
720        data[0] = (3 << 6) | (data[0] & 0x3F);
721        let mut buf = DissectBuffer::new();
722        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
723        assert!(
724            matches!(
725                err,
726                PacketError::InvalidFieldValue {
727                    field: "version",
728                    value: 3
729                }
730            ),
731            "expected InvalidFieldValue for version, got {err:?}"
732        );
733    }
734
735    #[test]
736    fn parse_rtp_truncated_csrc() {
737        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
738        // Set CC=3 but don't add any CSRC data
739        data[0] = (data[0] & 0xF0) | 0x03;
740        let mut buf = DissectBuffer::new();
741        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
742        assert!(
743            matches!(
744                err,
745                PacketError::Truncated {
746                    expected: 24,
747                    actual: 12
748                }
749            ),
750            "expected Truncated(24, 12), got {err:?}"
751        );
752    }
753
754    #[test]
755    fn parse_rtp_truncated_extension_header() {
756        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
757        // Set X=1 but don't add extension header bytes
758        data[0] |= 0x10;
759        let mut buf = DissectBuffer::new();
760        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
761        assert!(
762            matches!(
763                err,
764                PacketError::Truncated {
765                    expected: 16,
766                    actual: 12
767                }
768            ),
769            "expected Truncated(16, 12), got {err:?}"
770        );
771    }
772
773    #[test]
774    fn parse_rtp_truncated_extension_data() {
775        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
776        // Set X=1
777        data[0] |= 0x10;
778        // Extension header: profile=0x0000, length=2 (needs 8 bytes of data)
779        data.extend_from_slice(&0u16.to_be_bytes());
780        data.extend_from_slice(&2u16.to_be_bytes());
781        // Only add 4 bytes instead of 8
782        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
783
784        let mut buf = DissectBuffer::new();
785        let err = RtpDissector.dissect(&data, &mut buf, 0).unwrap_err();
786        assert!(
787            matches!(
788                err,
789                PacketError::Truncated {
790                    expected: 24,
791                    actual: 20
792                }
793            ),
794            "expected Truncated(24, 20), got {err:?}"
795        );
796    }
797
798    #[test]
799    fn parse_rtp_with_offset() {
800        let mut data = vec![0xFF; 10]; // 10 bytes of prefix
801        data.extend_from_slice(&minimal_rtp_header(96, 100, 3200, 0xABCDEF01));
802        let mut buf = DissectBuffer::new();
803        let result = RtpDissector.dissect(&data[10..], &mut buf, 10).unwrap();
804
805        assert_eq!(result.bytes_consumed, 12);
806        let layer = &buf.layers()[0];
807        assert_eq!(layer.range, 10..22);
808        assert_eq!(buf.field_by_name(layer, "ssrc").unwrap().range, 18..22);
809    }
810
811    #[test]
812    fn parse_rtp_with_max_csrc() {
813        // RFC 3550, Section 5.1 — CC is 4 bits, max 15 contributing sources.
814        let mut data = minimal_rtp_header(0, 1, 100, 0xAABBCCDD);
815        data[0] = (data[0] & 0xF0) | 0x0F; // CC=15
816        for i in 0..15u32 {
817            data.extend_from_slice(&(0x10_00_00_00 + i).to_be_bytes());
818        }
819
820        let mut buf = DissectBuffer::new();
821        let result = RtpDissector.dissect(&data, &mut buf, 0).unwrap();
822        assert_eq!(result.bytes_consumed, 12 + 15 * 4);
823
824        let layer = &buf.layers()[0];
825        let csrc_list = buf.field_by_name(layer, "csrc_list").unwrap();
826        let range = match &csrc_list.value {
827            FieldValue::Array(r) => r.clone(),
828            _ => panic!("expected Array"),
829        };
830        assert_eq!(buf.nested_fields(&range).len(), 15);
831    }
832
833    #[test]
834    fn field_descriptors_complete() {
835        let descriptors = RtpDissector.field_descriptors();
836        assert_eq!(descriptors.len(), 15);
837        assert_eq!(descriptors[0].name, "version");
838        assert_eq!(descriptors[9].name, "csrc_list");
839        assert!(descriptors[9].optional);
840        assert_eq!(descriptors[10].name, "payload");
841        assert!(descriptors[10].optional);
842        assert_eq!(descriptors[11].name, "padding_length");
843        assert!(descriptors[11].optional);
844        assert_eq!(descriptors[12].name, "ext_profile");
845        assert!(descriptors[12].optional);
846    }
847
848    #[test]
849    fn name_and_short_name() {
850        assert_eq!(RtpDissector.name(), "Real-time Transport Protocol");
851        assert_eq!(RtpDissector.short_name(), "RTP");
852    }
853}