Skip to main content

rtp_packet/
header.rs

1//! The RTP fixed header, CSRC list, and generic header extension — RFC 3550
2//! §5.1 / §5.3.1. See `rtp-packet/docs/rtp-header.md` for the curated spec
3//! transcription this module implements field-for-field.
4
5use alloc::vec::Vec;
6
7use broadcast_common::{Parse, Serialize};
8
9use crate::error::{Error, Result};
10
11// ---------------------------------------------------------------------------
12// Named constants (no magic numbers) — RFC 3550 §5.1 / §5.3.1
13// ---------------------------------------------------------------------------
14
15/// RTP version — "the version defined by this specification is two (2)"
16/// (docs/rtp-header.md §5.1, `version (V), 2 bits`).
17pub const RTP_VERSION: u8 = 2;
18
19/// Length of the fixed header (12 bytes: byte0 + byte1 + seq(2) + ts(4) +
20/// ssrc(4)) before any CSRC identifiers, per the §5.1 bit diagram.
21pub const FIXED_HEADER_LEN: usize = 12;
22
23/// Byte width of one CSRC identifier (§5.1: "32 bits" each).
24const CSRC_ITEM_LEN: usize = 4;
25
26/// Maximum CSRC count — the `CC` field is 4 bits (§5.1).
27pub const MAX_CSRC_COUNT: usize = 15;
28
29/// `version` field shift within byte 0 (top 2 bits).
30const VERSION_SHIFT: u8 = 6;
31/// `padding (P)` bit within byte 0 (bit 5).
32const PADDING_BIT_MASK: u8 = 0x20;
33/// `extension (X)` bit within byte 0 (bit 4).
34const EXTENSION_BIT_MASK: u8 = 0x10;
35/// `CC` field mask within byte 0 (low 4 bits).
36const CC_MASK: u8 = 0x0F;
37/// `marker (M)` bit within byte 1 (bit 7).
38const MARKER_BIT_MASK: u8 = 0x80;
39/// `payload type (PT)` field mask within byte 1 (low 7 bits, §5.1).
40const PAYLOAD_TYPE_MASK: u8 = 0x7F;
41/// Maximum `payload type` value — `PT` is a 7-bit field (§5.1).
42pub const MAX_PAYLOAD_TYPE: u8 = 0x7F;
43
44/// Length of the header-extension prefix — `defined by profile`(16 bits) +
45/// `length`(16 bits) = 4 bytes (§5.3.1 bit diagram).
46const EXTENSION_HEADER_LEN: usize = 4;
47/// Byte width of one extension "word" — `length` counts 32-bit words,
48/// "excluding the four-octet extension header" (§5.3.1).
49const EXTENSION_WORD_LEN: usize = 4;
50/// Maximum extension length in words — `length` is a 16-bit field (§5.3.1).
51const MAX_EXTENSION_WORDS: usize = u16::MAX as usize;
52
53/// Maximum padding-octet count — the trailing count byte is 8 bits (§5.1:
54/// "the last octet of the padding contains a count of how many padding
55/// octets should be ignored, including itself").
56pub const MAX_PADDING_COUNT: usize = u8::MAX as usize;
57
58// ---------------------------------------------------------------------------
59// HeaderExtension — RFC 3550 §5.3.1
60// ---------------------------------------------------------------------------
61
62/// The RTP generic header extension (§5.3.1): a 16-bit profile-specific
63/// identifier + opaque profile-specific data.
64///
65/// The `data` bytes are genuinely opaque at this layer — RFC 3550 itself
66/// defines no further structure ("the actual format of the extension is
67/// specified by the profile"). This mirrors the project's precedent for
68/// spec-opaque payloads (e.g. `smpte2038`'s undecoded ST 291-1
69/// checksum/parity): a raw `&[u8]` here is not a "raw-byte API" violation
70/// because there is no further spec structure to type.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize))]
73pub struct HeaderExtension<'a> {
74    /// `defined by profile` — a 16-bit identifier whose meaning is entirely
75    /// profile-specific (§5.3.1).
76    pub profile_id: u16,
77    /// The extension data. Its on-the-wire `length` (in 32-bit words) is
78    /// always derived from `data.len() / 4` on serialize — never stored as an
79    /// independent field that could disagree with the slice. `data.len()`
80    /// MUST be a multiple of 4 (§5.3.1: `length` counts whole 32-bit words).
81    pub data: &'a [u8],
82}
83
84impl HeaderExtension<'_> {
85    /// The `length` field value that will be written on serialize: the number
86    /// of 32-bit words in `data` (§5.3.1, "excluding the four-octet extension
87    /// header").
88    #[must_use]
89    pub fn length_words(&self) -> usize {
90        self.data.len() / EXTENSION_WORD_LEN
91    }
92}
93
94// ---------------------------------------------------------------------------
95// RtpPacket — RFC 3550 §5.1 fixed header + CSRC list + extension + payload
96// ---------------------------------------------------------------------------
97
98/// A parsed (or to-be-serialized) RTP packet: the §5.1 fixed header, the CSRC
99/// list, the optional §5.3.1 header extension, an optional padding region, and
100/// the payload.
101///
102/// `version` is not a stored field: RFC 3550 fixes it at 2, so [`parse`]
103/// rejects any other value and [`serialize_into`] always writes 2 — storing a
104/// field that can only ever legally hold one value would just be another way
105/// for caller state to disagree with the wire (see [`RTP_VERSION`]).
106///
107/// `P` (padding) and `X` (extension) are likewise never stored directly: they
108/// are derived from `padding.is_some()` / `extension.is_some()` on serialize,
109/// and `CC` is derived from `csrc.len()` — the same "derive from the typed
110/// data, never trust an independent flag" discipline used throughout this
111/// project (see the module doc's citation of docs/rtp-header.md).
112///
113/// [`parse`]: Parse::parse
114/// [`serialize_into`]: Serialize::serialize_into
115#[derive(Debug, Clone, PartialEq, Eq)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize))]
117pub struct RtpPacket<'a> {
118    /// `marker (M)` — profile-specific, opaque at this layer (§5.1).
119    pub marker: bool,
120    /// `payload type (PT)`, 7 bits (§5.1).
121    pub payload_type: u8,
122    /// `sequence number`, 16 bits (§5.1).
123    pub sequence_number: u16,
124    /// `timestamp`, 32 bits (§5.1).
125    pub timestamp: u32,
126    /// `SSRC` — synchronization source identifier, 32 bits (§5.1).
127    pub ssrc: u32,
128    /// The CSRC identifier list, 0–15 entries (§5.1 `CC`/CSRC list). `CC` is
129    /// always `csrc.len()` — there is no separate stored count.
130    pub csrc: Vec<u32>,
131    /// The §5.3.1 header extension, if `X=1`.
132    pub extension: Option<HeaderExtension<'a>>,
133    /// The trailing padding region, if `P=1`: the raw octets as they appear on
134    /// the wire, whose **last byte** is the padding count "including itself"
135    /// (§5.1). Storing the whole slice (rather than just a count) preserves
136    /// whatever content precedes the count byte byte-exactly on round-trip,
137    /// since RFC 3550 does not constrain it.
138    pub padding: Option<&'a [u8]>,
139    /// The payload: whatever bytes remain after the fixed header, CSRC list,
140    /// and extension, with the trailing `padding` (if any) already excluded.
141    pub payload: &'a [u8],
142}
143
144impl RtpPacket<'_> {
145    /// `CC` — the CSRC count that will be written on serialize: always
146    /// `csrc.len()` (§5.1).
147    #[must_use]
148    pub fn csrc_count(&self) -> usize {
149        self.csrc.len()
150    }
151}
152
153impl<'a> Parse<'a> for RtpPacket<'a> {
154    type Error = Error;
155
156    fn parse(bytes: &'a [u8]) -> Result<Self> {
157        if bytes.len() < FIXED_HEADER_LEN {
158            return Err(Error::BufferTooShort {
159                need: FIXED_HEADER_LEN,
160                have: bytes.len(),
161                what: "RTP fixed header",
162            });
163        }
164
165        let byte0 = bytes[0];
166        let version = byte0 >> VERSION_SHIFT;
167        if version != RTP_VERSION {
168            return Err(Error::InvalidVersion(version));
169        }
170        let padding_flag = byte0 & PADDING_BIT_MASK != 0;
171        let extension_flag = byte0 & EXTENSION_BIT_MASK != 0;
172        let cc = usize::from(byte0 & CC_MASK);
173
174        let byte1 = bytes[1];
175        let marker = byte1 & MARKER_BIT_MASK != 0;
176        let payload_type = byte1 & PAYLOAD_TYPE_MASK;
177
178        let sequence_number = u16::from_be_bytes([bytes[2], bytes[3]]);
179        let timestamp = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
180        let ssrc = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
181
182        let mut pos = FIXED_HEADER_LEN;
183
184        // --- CSRC list (§5.1) ---
185        let csrc_bytes_len = cc * CSRC_ITEM_LEN;
186        if bytes.len() < pos + csrc_bytes_len {
187            return Err(Error::BufferTooShort {
188                need: pos + csrc_bytes_len,
189                have: bytes.len(),
190                what: "CSRC list",
191            });
192        }
193        let mut csrc = Vec::with_capacity(cc);
194        for i in 0..cc {
195            let off = pos + i * CSRC_ITEM_LEN;
196            csrc.push(u32::from_be_bytes([
197                bytes[off],
198                bytes[off + 1],
199                bytes[off + 2],
200                bytes[off + 3],
201            ]));
202        }
203        pos += csrc_bytes_len;
204
205        // --- Header extension (§5.3.1) ---
206        let extension = if extension_flag {
207            if bytes.len() < pos + EXTENSION_HEADER_LEN {
208                return Err(Error::BufferTooShort {
209                    need: pos + EXTENSION_HEADER_LEN,
210                    have: bytes.len(),
211                    what: "header extension profile-id/length",
212                });
213            }
214            let profile_id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
215            let length_words = usize::from(u16::from_be_bytes([bytes[pos + 2], bytes[pos + 3]]));
216            let data_len = length_words * EXTENSION_WORD_LEN;
217            let data_start = pos + EXTENSION_HEADER_LEN;
218            let data_end = data_start + data_len;
219            if bytes.len() < data_end {
220                return Err(Error::BufferTooShort {
221                    need: data_end,
222                    have: bytes.len(),
223                    what: "header extension data",
224                });
225            }
226            pos = data_end;
227            Some(HeaderExtension {
228                profile_id,
229                data: &bytes[data_start..data_end],
230            })
231        } else {
232            None
233        };
234
235        // --- Padding (§5.1) + payload ---
236        let (payload, padding) = if padding_flag {
237            if bytes.len() <= pos {
238                return Err(Error::InvalidPadding {
239                    count: 0,
240                    reason: "padding bit set but no bytes remain for the count octet",
241                });
242            }
243            // bytes.len() > pos (checked above) implies bytes.len() >= 1.
244            let count = bytes[bytes.len() - 1];
245            if count == 0 {
246                return Err(Error::InvalidPadding {
247                    count,
248                    reason: "padding count must be >= 1 (it counts itself)",
249                });
250            }
251            let remaining = bytes.len() - pos;
252            if usize::from(count) > remaining {
253                return Err(Error::InvalidPadding {
254                    count,
255                    reason: "padding count exceeds the bytes remaining after the header",
256                });
257            }
258            let split = bytes.len() - usize::from(count);
259            (&bytes[pos..split], Some(&bytes[split..]))
260        } else {
261            (&bytes[pos..], None)
262        };
263
264        Ok(Self {
265            marker,
266            payload_type,
267            sequence_number,
268            timestamp,
269            ssrc,
270            csrc,
271            extension,
272            padding,
273            payload,
274        })
275    }
276}
277
278impl Serialize for RtpPacket<'_> {
279    type Error = Error;
280
281    fn serialized_len(&self) -> usize {
282        FIXED_HEADER_LEN
283            + self.csrc.len() * CSRC_ITEM_LEN
284            + self
285                .extension
286                .map(|e| EXTENSION_HEADER_LEN + e.data.len())
287                .unwrap_or(0)
288            + self.payload.len()
289            + self.padding.map(<[u8]>::len).unwrap_or(0)
290    }
291
292    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
293        let len = self.serialized_len();
294        if buf.len() < len {
295            return Err(Error::BufferTooShort {
296                need: len,
297                have: buf.len(),
298                what: "RTP packet serialize output",
299            });
300        }
301        if self.csrc.len() > MAX_CSRC_COUNT {
302            return Err(Error::InvalidValue {
303                field: "csrc.len()",
304                value: self.csrc.len() as u64,
305                reason: "exceeds the 4-bit CC field maximum (15)",
306            });
307        }
308        if self.payload_type > MAX_PAYLOAD_TYPE {
309            return Err(Error::InvalidValue {
310                field: "payload_type",
311                value: u64::from(self.payload_type),
312                reason: "exceeds the 7-bit PT field maximum (127)",
313            });
314        }
315        if let Some(ext) = &self.extension {
316            if ext.data.len() % EXTENSION_WORD_LEN != 0 {
317                return Err(Error::ExtensionNotWordAligned {
318                    data_len: ext.data.len(),
319                });
320            }
321            let words = ext.length_words();
322            if words > MAX_EXTENSION_WORDS {
323                return Err(Error::InvalidValue {
324                    field: "extension.data.len()/4",
325                    value: words as u64,
326                    reason: "exceeds the 16-bit length field maximum",
327                });
328            }
329        }
330        if let Some(pad) = self.padding {
331            if pad.is_empty() {
332                return Err(Error::InvalidPadding {
333                    count: 0,
334                    reason: "a Some(padding) slice must be non-empty",
335                });
336            }
337            if pad.len() > MAX_PADDING_COUNT {
338                return Err(Error::InvalidValue {
339                    field: "padding.len()",
340                    value: pad.len() as u64,
341                    reason: "exceeds the 8-bit padding-count field maximum (255)",
342                });
343            }
344            // pad.is_empty() was rejected above, so pad.len() >= 1.
345            let last = pad[pad.len() - 1];
346            if usize::from(last) != pad.len() {
347                return Err(Error::InvalidPadding {
348                    count: last,
349                    reason: "the last padding byte must equal the padding slice's own length",
350                });
351            }
352        }
353
354        let mut byte0 = RTP_VERSION << VERSION_SHIFT;
355        if self.padding.is_some() {
356            byte0 |= PADDING_BIT_MASK;
357        }
358        if self.extension.is_some() {
359            byte0 |= EXTENSION_BIT_MASK;
360        }
361        byte0 |= (self.csrc.len() as u8) & CC_MASK;
362        buf[0] = byte0;
363
364        let mut byte1 = self.payload_type & PAYLOAD_TYPE_MASK;
365        if self.marker {
366            byte1 |= MARKER_BIT_MASK;
367        }
368        buf[1] = byte1;
369
370        buf[2..4].copy_from_slice(&self.sequence_number.to_be_bytes());
371        buf[4..8].copy_from_slice(&self.timestamp.to_be_bytes());
372        buf[8..12].copy_from_slice(&self.ssrc.to_be_bytes());
373
374        let mut pos = FIXED_HEADER_LEN;
375        for &c in &self.csrc {
376            buf[pos..pos + CSRC_ITEM_LEN].copy_from_slice(&c.to_be_bytes());
377            pos += CSRC_ITEM_LEN;
378        }
379
380        if let Some(ext) = &self.extension {
381            buf[pos..pos + 2].copy_from_slice(&ext.profile_id.to_be_bytes());
382            let words = ext.length_words() as u16;
383            buf[pos + 2..pos + EXTENSION_HEADER_LEN].copy_from_slice(&words.to_be_bytes());
384            pos += EXTENSION_HEADER_LEN;
385            buf[pos..pos + ext.data.len()].copy_from_slice(ext.data);
386            pos += ext.data.len();
387        }
388
389        buf[pos..pos + self.payload.len()].copy_from_slice(self.payload);
390        pos += self.payload.len();
391
392        if let Some(pad) = self.padding {
393            buf[pos..pos + pad.len()].copy_from_slice(pad);
394            pos += pad.len();
395        }
396
397        Ok(pos)
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use alloc::vec;
405
406    fn simple_packet() -> RtpPacket<'static> {
407        RtpPacket {
408            marker: true,
409            payload_type: 97,
410            sequence_number: 5,
411            timestamp: 0x0000_1400,
412            ssrc: 0x1234_5678,
413            csrc: Vec::new(),
414            extension: None,
415            padding: None,
416            payload: &[0xAA, 0xBB, 0xCC, 0xDD],
417        }
418    }
419
420    #[test]
421    fn simple_round_trip() {
422        let p = simple_packet();
423        let mut out = vec![0u8; p.serialized_len()];
424        p.serialize_into(&mut out).unwrap();
425        assert_eq!(RtpPacket::parse(&out).unwrap(), p);
426        assert_eq!(out[0], 0x80); // V=2 P=0 X=0 CC=0
427        assert_eq!(out[1], 0x80 | 97); // M=1 PT=97
428    }
429
430    #[test]
431    fn rejects_bad_version() {
432        let p = simple_packet();
433        let mut out = vec![0u8; p.serialized_len()];
434        p.serialize_into(&mut out).unwrap();
435        out[0] = (1 << VERSION_SHIFT) | (out[0] & 0x3F); // version = 1
436        assert!(matches!(
437            RtpPacket::parse(&out),
438            Err(Error::InvalidVersion(1))
439        ));
440    }
441
442    #[test]
443    fn csrc_round_trip() {
444        let mut p = simple_packet();
445        p.csrc = vec![0x1111_1111, 0x2222_2222, 0x3333_3333];
446        let mut out = vec![0u8; p.serialized_len()];
447        p.serialize_into(&mut out).unwrap();
448        assert_eq!(out[0] & 0x0F, 3, "CC derived from csrc.len()");
449        let reparsed = RtpPacket::parse(&out).unwrap();
450        assert_eq!(reparsed, p);
451    }
452
453    #[test]
454    fn rejects_csrc_over_15() {
455        let mut p = simple_packet();
456        p.csrc = vec![0; 16];
457        let mut out = vec![0u8; p.serialized_len()];
458        assert!(matches!(
459            p.serialize_into(&mut out),
460            Err(Error::InvalidValue {
461                field: "csrc.len()",
462                ..
463            })
464        ));
465    }
466
467    #[test]
468    fn extension_round_trip() {
469        let mut p = simple_packet();
470        p.extension = Some(HeaderExtension {
471            profile_id: 0xBEDE,
472            data: &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
473        });
474        let mut out = vec![0u8; p.serialized_len()];
475        p.serialize_into(&mut out).unwrap();
476        assert_eq!(out[0] & 0x10, 0x10, "X bit set");
477        let reparsed = RtpPacket::parse(&out).unwrap();
478        assert_eq!(reparsed, p);
479        assert_eq!(reparsed.extension.unwrap().length_words(), 2);
480    }
481
482    #[test]
483    fn extension_zero_length_is_valid() {
484        // §5.3.1: "therefore zero is a valid length".
485        let mut p = simple_packet();
486        p.extension = Some(HeaderExtension {
487            profile_id: 0x0001,
488            data: &[],
489        });
490        let mut out = vec![0u8; p.serialized_len()];
491        p.serialize_into(&mut out).unwrap();
492        let reparsed = RtpPacket::parse(&out).unwrap();
493        assert_eq!(reparsed, p);
494    }
495
496    #[test]
497    fn rejects_extension_not_word_aligned() {
498        let mut p = simple_packet();
499        p.extension = Some(HeaderExtension {
500            profile_id: 1,
501            data: &[0x01, 0x02, 0x03], // 3 bytes, not a multiple of 4
502        });
503        let mut out = vec![0u8; FIXED_HEADER_LEN + EXTENSION_HEADER_LEN + 3 + p.payload.len()];
504        assert!(matches!(
505            p.serialize_into(&mut out),
506            Err(Error::ExtensionNotWordAligned { data_len: 3 })
507        ));
508    }
509
510    #[test]
511    fn padding_round_trip() {
512        let mut p = simple_packet();
513        // 4 pad octets; the last byte (the count, "including itself") is 4.
514        p.padding = Some(&[0x00, 0x00, 0x00, 0x04]);
515        let mut out = vec![0u8; p.serialized_len()];
516        p.serialize_into(&mut out).unwrap();
517        assert_eq!(out[0] & 0x20, 0x20, "P bit set");
518        assert_eq!(*out.last().unwrap(), 4, "trailing pad-count byte");
519        let reparsed = RtpPacket::parse(&out).unwrap();
520        assert_eq!(reparsed, p);
521        assert_eq!(reparsed.payload, p.payload, "payload stripped of padding");
522    }
523
524    #[test]
525    fn rejects_padding_count_mismatch() {
526        let mut p = simple_packet();
527        p.padding = Some(&[0x00, 0x00, 0x00, 0x03]); // len=4 but last byte says 3
528        let mut out = vec![0u8; p.serialized_len()];
529        assert!(matches!(
530            p.serialize_into(&mut out),
531            Err(Error::InvalidPadding { count: 3, .. })
532        ));
533    }
534
535    #[test]
536    fn rejects_padding_count_exceeding_available() {
537        // Hand-build a packet with P=1 and a count byte bigger than the bytes
538        // actually present after the fixed header.
539        let mut bytes = vec![0u8; FIXED_HEADER_LEN + 2];
540        bytes[0] = 0x80 | 0x20; // V=2 P=1
541        bytes[1] = 97;
542        *bytes.last_mut().unwrap() = 0xFF; // count says 255, only 2 bytes present
543        assert!(matches!(
544            RtpPacket::parse(&bytes),
545            Err(Error::InvalidPadding { count: 0xFF, .. })
546        ));
547    }
548
549    #[test]
550    fn field_mutation_changes_bytes() {
551        let a = simple_packet();
552        let mut b = a.clone();
553        b.sequence_number = a.sequence_number.wrapping_add(1);
554        let mut oa = vec![0u8; a.serialized_len()];
555        let mut ob = vec![0u8; b.serialized_len()];
556        a.serialize_into(&mut oa).unwrap();
557        b.serialize_into(&mut ob).unwrap();
558        assert_ne!(oa, ob);
559        // The change is localized to exactly the 2-byte sequence-number field.
560        assert_eq!(oa[0], ob[0]);
561        assert_eq!(oa[1], ob[1]);
562        assert_ne!(&oa[2..4], &ob[2..4]);
563        assert_eq!(&oa[4..], &ob[4..]);
564    }
565}