Skip to main content

rtp_packet/
rfc8285.rs

1//! RFC 8285 one-byte/two-byte RTP header-extension element multiplexing.
2//!
3//! This is a profile-specific interpretation of the RFC 3550 §5.3.1 opaque
4//! [`HeaderExtension`] `data` — see `rtp-packet/docs/rfc8285_header_ext.md`
5//! for the curated spec transcription this module implements field-for-field
6//! (cite that file, not this doc comment, as the field-semantics oracle).
7//!
8//! Entry point: [`parse_extensions`], given a [`HeaderExtension`] borrowed
9//! out of a parsed [`RtpPacket`](crate::RtpPacket), inspects `profile_id` and
10//! dispatches to the one-byte ([`OneByteElements`]) or two-byte
11//! ([`TwoByteElements`]) form, returning [`Error::NotRfc8285Extension`] for
12//! any other `profile_id` (not a malformed-packet error — RFC 8285
13//! interpretation is opt-in and profile-scoped).
14
15use alloc::vec::Vec;
16
17use broadcast_common::{Parse, Serialize};
18
19use crate::error::{Error, Result};
20use crate::header::HeaderExtension;
21
22// ---------------------------------------------------------------------------
23// Named constants (no magic numbers) — RFC 8285 §4.1.2 / §4.2 / §4.3
24// ---------------------------------------------------------------------------
25
26/// The fixed `profile_id` bit pattern identifying the one-byte header form
27/// (§4.2: "MUST have the fixed bit pattern `0xBEDE`").
28pub const ONE_BYTE_PROFILE_ID: u16 = 0xBEDE;
29
30/// Mask isolating the top 12 bits of `profile_id` that identify the two-byte
31/// header form (§4.3 bit diagram: `0x100` in the top 12 bits, `appbits` in
32/// the bottom 4).
33const TWO_BYTE_PROFILE_ID_MASK: u16 = 0xFFF0;
34/// The fixed top-12-bit pattern (`0x100`, shifted into position) identifying
35/// the two-byte header form (§4.3).
36const TWO_BYTE_PROFILE_ID_PREFIX: u16 = 0x1000;
37
38/// The reserved one-byte-form local identifier that halts extension parsing
39/// (§4.1.2/§4.2: "the reserved value of 15").
40const ONE_BYTE_STOP_ID: u8 = 15;
41/// A literal zero byte is always a single padding byte in the byte-by-byte
42/// scan (§4.1.2: "padding bytes have the value of 0 (zero)").
43const PADDING_BYTE: u8 = 0x00;
44
45/// Minimum valid one-byte-form local identifier (§4.2: "range 1-14
46/// inclusive"; 0 is reserved for padding).
47const ONE_BYTE_ID_MIN: u8 = 1;
48/// Maximum valid one-byte-form local identifier (§4.2; 15 is reserved).
49const ONE_BYTE_ID_MAX: u8 = 14;
50/// Minimum one-byte-form element data length in bytes (§4.2: the `len`
51/// nibble encodes `data.len() - 1`, so the shortest representable element is
52/// 1 byte).
53const ONE_BYTE_DATA_MIN: usize = 1;
54/// Maximum one-byte-form element data length in bytes (§4.2: `len` nibble
55/// `15` -> 16 bytes).
56const ONE_BYTE_DATA_MAX: usize = 16;
57
58/// Minimum valid two-byte-form local identifier. Per RFC 8285 §4.1.2/§5,
59/// "0 is reserved for padding in **both** forms" (not just the one-byte
60/// form, despite §4.3 in isolation reading as "range 1-255 inclusive" —
61/// see `docs/rfc8285_header_ext.md`'s "Judgment calls" section).
62const TWO_BYTE_ID_MIN: u8 = 1;
63/// Maximum two-byte-form element data length in bytes (§4.3: an 8-bit
64/// length field, stored directly with no `-1` bias, unlike the one-byte
65/// form).
66const TWO_BYTE_DATA_MAX: usize = u8::MAX as usize;
67
68/// The alignment (in bytes) the overall RFC 3550 §5.3.1 extension `data`
69/// must be padded to (a whole number of 32-bit words).
70const EXT_ALIGN: usize = 4;
71
72// ---------------------------------------------------------------------------
73// OneByteId / TwoByteId — validating local-identifier newtypes
74// ---------------------------------------------------------------------------
75
76/// A validated RFC 8285 one-byte-form local identifier (§4.2: range `1..=14`
77/// inclusive). `0` (padding) and `15` (reserved/"stop") are not representable
78/// — [`OneByteId::new`] rejects them, so an [`OneByteElement`] can never hold
79/// a reserved ID value.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize))]
82#[cfg_attr(feature = "serde", serde(transparent))]
83pub struct OneByteId(u8);
84
85impl OneByteId {
86    /// Construct a validated one-byte-form identifier. Returns
87    /// [`Error::InvalidOneByteExtensionId`] for `0`, `15`, or any value
88    /// outside the 4-bit field (`> 15`).
89    pub fn new(id: u8) -> Result<Self> {
90        if (ONE_BYTE_ID_MIN..=ONE_BYTE_ID_MAX).contains(&id) {
91            Ok(Self(id))
92        } else {
93            Err(Error::InvalidOneByteExtensionId(id))
94        }
95    }
96
97    /// The raw `1..=14` identifier value.
98    #[must_use]
99    pub fn get(self) -> u8 {
100        self.0
101    }
102}
103
104/// A validated RFC 8285 two-byte-form local identifier (§4.1.2/§5: `0` is
105/// reserved for padding "in both forms"; §4.3's own field is otherwise the
106/// full 8-bit range, `1..=255`). [`TwoByteId::new`] rejects `0`, so a
107/// [`TwoByteElement`] can never hold the reserved padding ID.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize))]
110#[cfg_attr(feature = "serde", serde(transparent))]
111pub struct TwoByteId(u8);
112
113impl TwoByteId {
114    /// Construct a validated two-byte-form identifier. Returns
115    /// [`Error::InvalidTwoByteExtensionId`] for `0`.
116    pub fn new(id: u8) -> Result<Self> {
117        if id >= TWO_BYTE_ID_MIN {
118            Ok(Self(id))
119        } else {
120            Err(Error::InvalidTwoByteExtensionId)
121        }
122    }
123
124    /// The raw `1..=255` identifier value.
125    #[must_use]
126    pub fn get(self) -> u8 {
127        self.0
128    }
129}
130
131// ---------------------------------------------------------------------------
132// OneByteElement / OneByteElements — RFC 8285 §4.2
133// ---------------------------------------------------------------------------
134
135/// A single RFC 8285 one-byte-form extension element (§4.2): a validated
136/// `1..=14` local identifier plus `1..=16` bytes of element data.
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize))]
139pub struct OneByteElement<'a> {
140    /// The element's local identifier.
141    pub id: OneByteId,
142    /// The element's data, `1..=16` bytes (§4.2: `len` nibble = `data.len()
143    /// - 1`).
144    pub data: &'a [u8],
145}
146
147/// A parsed (or to-be-serialized) sequence of RFC 8285 one-byte-form
148/// extension elements — the full contents of a [`HeaderExtension`] whose
149/// `profile_id == `[`ONE_BYTE_PROFILE_ID`], excluding padding bytes and
150/// anything after a stop point (§4.1.2/§4.2).
151#[derive(Debug, Clone, PartialEq, Eq, Default)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize))]
153pub struct OneByteElements<'a>(pub Vec<OneByteElement<'a>>);
154
155impl<'a> OneByteElements<'a> {
156    /// The parsed elements, in wire order.
157    #[must_use]
158    pub fn elements(&self) -> &[OneByteElement<'a>] {
159        &self.0
160    }
161}
162
163impl<'a> IntoIterator for OneByteElements<'a> {
164    type Item = OneByteElement<'a>;
165    type IntoIter = alloc::vec::IntoIter<OneByteElement<'a>>;
166
167    fn into_iter(self) -> Self::IntoIter {
168        self.0.into_iter()
169    }
170}
171
172impl<'a> Parse<'a> for OneByteElements<'a> {
173    type Error = Error;
174
175    fn parse(bytes: &'a [u8]) -> Result<Self> {
176        let mut elements = Vec::new();
177        let mut pos = 0;
178        while pos < bytes.len() {
179            let b = bytes[pos];
180            if b == PADDING_BYTE {
181                // A literal 0x00 byte is always one padding byte (§4.1.2).
182                pos += 1;
183                continue;
184            }
185            let id_nibble = b >> 4;
186            if id_nibble == ONE_BYTE_STOP_ID || id_nibble == 0 {
187                // id_nibble == 15: the reserved "stop" marker (§4.2).
188                // id_nibble == 0 (but b != 0, so the length nibble is
189                // nonzero): the malformed "ID 0 with length > 0" case
190                // (§4.1.2), which must also terminate parsing. Both cases:
191                // ignore the length field, stop, keep prior elements.
192                break;
193            }
194            let len = usize::from(b & 0x0F) + 1; // len nibble = data.len() - 1
195            let data_start = pos + 1;
196            let data_end = data_start + len;
197            if bytes.len() < data_end {
198                return Err(Error::BufferTooShort {
199                    need: data_end,
200                    have: bytes.len(),
201                    what: "RFC 8285 one-byte extension element data",
202                });
203            }
204            // id_nibble is 1..=14 here: 0 and 15 were handled above, so
205            // constructing directly (bypassing the fallible `new`) cannot
206            // violate OneByteId's invariant.
207            let id = OneByteId(id_nibble);
208            elements.push(OneByteElement {
209                id,
210                data: &bytes[data_start..data_end],
211            });
212            pos = data_end;
213        }
214        Ok(Self(elements))
215    }
216}
217
218impl Serialize for OneByteElements<'_> {
219    type Error = Error;
220
221    fn serialized_len(&self) -> usize {
222        let raw: usize = self.0.iter().map(|e| 1 + e.data.len()).sum();
223        raw.div_ceil(EXT_ALIGN) * EXT_ALIGN
224    }
225
226    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
227        let len = self.serialized_len();
228        if buf.len() < len {
229            return Err(Error::BufferTooShort {
230                need: len,
231                have: buf.len(),
232                what: "RFC 8285 one-byte extension elements serialize output",
233            });
234        }
235        let mut pos = 0;
236        for e in &self.0 {
237            if !(ONE_BYTE_DATA_MIN..=ONE_BYTE_DATA_MAX).contains(&e.data.len()) {
238                return Err(Error::InvalidValue {
239                    field: "OneByteElement::data.len()",
240                    value: e.data.len() as u64,
241                    reason: "must be 1..=16 bytes (RFC 8285 §4.2: len nibble = data.len() - 1)",
242                });
243            }
244            let len_nibble = (e.data.len() - 1) as u8;
245            buf[pos] = (e.id.get() << 4) | len_nibble;
246            pos += 1;
247            buf[pos..pos + e.data.len()].copy_from_slice(e.data);
248            pos += e.data.len();
249        }
250        for b in &mut buf[pos..len] {
251            *b = PADDING_BYTE;
252        }
253        Ok(len)
254    }
255}
256
257// ---------------------------------------------------------------------------
258// TwoByteElement / TwoByteElements — RFC 8285 §4.3
259// ---------------------------------------------------------------------------
260
261/// A single RFC 8285 two-byte-form extension element (§4.3): a validated
262/// `1..=255` local identifier plus `0..=255` bytes of element data (stored
263/// directly, with no `-1` length bias — unlike the one-byte form).
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize))]
266pub struct TwoByteElement<'a> {
267    /// The element's local identifier.
268    pub id: TwoByteId,
269    /// The element's data, `0..=255` bytes (§4.3: "The value zero (0)
270    /// indicates that there is no subsequent data").
271    pub data: &'a [u8],
272}
273
274/// A parsed (or to-be-serialized) sequence of RFC 8285 two-byte-form
275/// extension elements — the full contents of a [`HeaderExtension`] whose
276/// `profile_id & 0xFFF0 == 0x1000` (§4.3), excluding padding bytes.
277#[derive(Debug, Clone, PartialEq, Eq, Default)]
278#[cfg_attr(feature = "serde", derive(serde::Serialize))]
279pub struct TwoByteElements<'a>(pub Vec<TwoByteElement<'a>>);
280
281impl<'a> TwoByteElements<'a> {
282    /// The parsed elements, in wire order.
283    #[must_use]
284    pub fn elements(&self) -> &[TwoByteElement<'a>] {
285        &self.0
286    }
287}
288
289impl<'a> IntoIterator for TwoByteElements<'a> {
290    type Item = TwoByteElement<'a>;
291    type IntoIter = alloc::vec::IntoIter<TwoByteElement<'a>>;
292
293    fn into_iter(self) -> Self::IntoIter {
294        self.0.into_iter()
295    }
296}
297
298impl<'a> Parse<'a> for TwoByteElements<'a> {
299    type Error = Error;
300
301    fn parse(bytes: &'a [u8]) -> Result<Self> {
302        let mut elements = Vec::new();
303        let mut pos = 0;
304        while pos < bytes.len() {
305            let id_byte = bytes[pos];
306            if id_byte == PADDING_BYTE {
307                // A literal 0x00 byte is always one padding byte (§4.1.2/§5:
308                // "0 is reserved for padding in both forms"), consumed
309                // before ever looking for a following length byte.
310                pos += 1;
311                continue;
312            }
313            let len_pos = pos + 1;
314            if bytes.len() <= len_pos {
315                return Err(Error::BufferTooShort {
316                    need: len_pos + 1,
317                    have: bytes.len(),
318                    what: "RFC 8285 two-byte extension element length byte",
319                });
320            }
321            let len = usize::from(bytes[len_pos]);
322            let data_start = len_pos + 1;
323            let data_end = data_start + len;
324            if bytes.len() < data_end {
325                return Err(Error::BufferTooShort {
326                    need: data_end,
327                    have: bytes.len(),
328                    what: "RFC 8285 two-byte extension element data",
329                });
330            }
331            // id_byte != 0 here: the padding case was handled above, so
332            // constructing directly (bypassing the fallible `new`) cannot
333            // violate TwoByteId's invariant.
334            let id = TwoByteId(id_byte);
335            elements.push(TwoByteElement {
336                id,
337                data: &bytes[data_start..data_end],
338            });
339            pos = data_end;
340        }
341        Ok(Self(elements))
342    }
343}
344
345impl Serialize for TwoByteElements<'_> {
346    type Error = Error;
347
348    fn serialized_len(&self) -> usize {
349        let raw: usize = self.0.iter().map(|e| 2 + e.data.len()).sum();
350        raw.div_ceil(EXT_ALIGN) * EXT_ALIGN
351    }
352
353    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
354        let len = self.serialized_len();
355        if buf.len() < len {
356            return Err(Error::BufferTooShort {
357                need: len,
358                have: buf.len(),
359                what: "RFC 8285 two-byte extension elements serialize output",
360            });
361        }
362        let mut pos = 0;
363        for e in &self.0 {
364            if e.data.len() > TWO_BYTE_DATA_MAX {
365                return Err(Error::InvalidValue {
366                    field: "TwoByteElement::data.len()",
367                    value: e.data.len() as u64,
368                    reason: "exceeds the 8-bit length field maximum (255)",
369                });
370            }
371            buf[pos] = e.id.get();
372            buf[pos + 1] = e.data.len() as u8;
373            pos += 2;
374            buf[pos..pos + e.data.len()].copy_from_slice(e.data);
375            pos += e.data.len();
376        }
377        for b in &mut buf[pos..len] {
378            *b = PADDING_BYTE;
379        }
380        Ok(len)
381    }
382}
383
384// ---------------------------------------------------------------------------
385// ExtensionElements — top-level profile_id dispatch
386// ---------------------------------------------------------------------------
387
388/// The result of dispatching a [`HeaderExtension`] to its RFC 8285 form by
389/// `profile_id` (see [`parse_extensions`]). A data-carrying dispatch wrapper
390/// (in the same spirit as this workspace's `AnyTableSection`/`AnyDescriptor`
391/// dispatch enums) — not a spec/field label, so it is exempt from the #204
392/// `name()`/`Display` convention (see `tests/label_coverage.rs`'s SKIP list).
393#[derive(Debug, Clone, PartialEq, Eq)]
394#[cfg_attr(feature = "serde", derive(serde::Serialize))]
395#[non_exhaustive]
396pub enum ExtensionElements<'a> {
397    /// §4.2 one-byte-form elements (`profile_id == 0xBEDE`).
398    OneByte(OneByteElements<'a>),
399    /// §4.3 two-byte-form elements (`profile_id & 0xFFF0 == 0x1000`).
400    TwoByte(TwoByteElements<'a>),
401}
402
403impl Serialize for ExtensionElements<'_> {
404    type Error = Error;
405
406    fn serialized_len(&self) -> usize {
407        match self {
408            Self::OneByte(e) => e.serialized_len(),
409            Self::TwoByte(e) => e.serialized_len(),
410        }
411    }
412
413    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
414        match self {
415            Self::OneByte(e) => e.serialize_into(buf),
416            Self::TwoByte(e) => e.serialize_into(buf),
417        }
418    }
419}
420
421/// Decode a [`HeaderExtension`]'s opaque `data` as RFC 8285 multiplexed
422/// extension elements, dispatching on `profile_id` (§4.1.2):
423///
424/// - `0xBEDE` -> one-byte form ([`OneByteElements`], via [`ONE_BYTE_PROFILE_ID`])
425/// - `& 0xFFF0 == 0x1000` -> two-byte form ([`TwoByteElements`], §4.3)
426/// - anything else -> [`Error::NotRfc8285Extension`] — **not** a
427///   malformed-packet error, since RFC 8285 interpretation of the RFC 3550
428///   §5.3.1 opaque extension is profile-scoped and opt-in.
429pub fn parse_extensions<'a>(ext: &HeaderExtension<'a>) -> Result<ExtensionElements<'a>> {
430    if ext.profile_id == ONE_BYTE_PROFILE_ID {
431        Ok(ExtensionElements::OneByte(OneByteElements::parse(
432            ext.data,
433        )?))
434    } else if ext.profile_id & TWO_BYTE_PROFILE_ID_MASK == TWO_BYTE_PROFILE_ID_PREFIX {
435        Ok(ExtensionElements::TwoByte(TwoByteElements::parse(
436            ext.data,
437        )?))
438    } else {
439        Err(Error::NotRfc8285Extension {
440            profile_id: ext.profile_id,
441        })
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use alloc::vec;
449
450    // -- OneByteId / TwoByteId validation -----------------------------------
451
452    #[test]
453    fn one_byte_id_rejects_padding_and_stop() {
454        assert!(matches!(
455            OneByteId::new(0),
456            Err(Error::InvalidOneByteExtensionId(0))
457        ));
458        assert!(matches!(
459            OneByteId::new(15),
460            Err(Error::InvalidOneByteExtensionId(15))
461        ));
462        assert!(OneByteId::new(1).is_ok());
463        assert!(OneByteId::new(14).is_ok());
464    }
465
466    #[test]
467    fn two_byte_id_rejects_zero() {
468        assert!(matches!(
469            TwoByteId::new(0),
470            Err(Error::InvalidTwoByteExtensionId)
471        ));
472        assert!(TwoByteId::new(1).is_ok());
473        assert!(TwoByteId::new(255).is_ok());
474    }
475
476    // -- One-byte form round trips ------------------------------------------
477
478    #[test]
479    fn one_byte_round_trip_single_element() {
480        let elements = OneByteElements(vec![OneByteElement {
481            id: OneByteId::new(3).unwrap(),
482            data: &[0xAA, 0xBB],
483        }]);
484        let mut out = vec![0u8; elements.serialized_len()];
485        elements.serialize_into(&mut out).unwrap();
486        // header byte: id=3, len nibble = 2-1=1
487        assert_eq!(out[0], (3 << 4) | 1);
488        assert_eq!(&out[1..3], &[0xAA, 0xBB]);
489        // padded to a 4-byte multiple (3 bytes of content -> 4)
490        assert_eq!(out.len(), 4);
491        assert_eq!(out[3], 0x00);
492        let reparsed = OneByteElements::parse(&out).unwrap();
493        assert_eq!(reparsed, elements);
494    }
495
496    #[test]
497    fn one_byte_spec_worked_example_structure() {
498        // RFC 8285 §4.2 worked example structure: elem(L=0 -> 1 byte),
499        // elem(L=1 -> 2 bytes), elem(L=3 -> 4 bytes). The RFC gives concrete
500        // hex only for the profile id + length; IDs and data bytes here are
501        // concrete values we chose to instantiate that structure (see
502        // docs/rfc8285_header_ext.md). Padding is placed at the tail here
503        // (this crate's `Serialize` always canonicalizes padding to a single
504        // trailing run, since RFC 8285 assigns it no semantic content — see
505        // `one_byte_reparses_interspersed_padding_to_the_same_elements` below
506        // for the RFC diagram's own inter-element padding placement).
507        let elements = OneByteElements(vec![
508            OneByteElement {
509                id: OneByteId::new(1).unwrap(),
510                data: &[0x11],
511            },
512            OneByteElement {
513                id: OneByteId::new(2).unwrap(),
514                data: &[0x22, 0x33],
515            },
516            OneByteElement {
517                id: OneByteId::new(3).unwrap(),
518                data: &[0x44, 0x55, 0x66, 0x77],
519            },
520        ]);
521        let mut out = vec![0u8; elements.serialized_len()];
522        elements.serialize_into(&mut out).unwrap();
523        let expected = [
524            1 << 4, // ID=1 L=0 (1 byte)
525            0x11,
526            (2 << 4) | 1, // ID=2 L=1 (2 bytes)
527            0x22,
528            0x33,
529            (3 << 4) | 3, // ID=3 L=3 (4 bytes)
530            0x44,
531            0x55,
532            0x66,
533            0x77,
534            0x00,
535            0x00, // 2 trailing pad bytes
536        ];
537        assert_eq!(out, expected);
538        assert_eq!(
539            out.len(),
540            12,
541            "3 words, matching length=3 in the RFC diagram"
542        );
543        let reparsed = OneByteElements::parse(&out).unwrap();
544        assert_eq!(reparsed, elements);
545    }
546
547    #[test]
548    fn one_byte_reparses_interspersed_padding_to_the_same_elements() {
549        // The RFC 8285 §4.2 diagram itself places its 2 padding bytes
550        // *between* the second and third elements, not at the tail (padding
551        // "MAY be placed between extension elements, if desired for
552        // alignment, or after the last extension element" -- §4.1.2).
553        // Parsing MUST still recover the same elements regardless of where
554        // the (semantically meaningless) padding bytes fall.
555        #[rustfmt::skip]
556        let interspersed: [u8; 12] = [
557            1 << 4, 0x11,
558            (2 << 4) | 1, 0x22, 0x33,
559            0x00, 0x00, // pad between elements, per the RFC's own diagram
560            (3 << 4) | 3, 0x44, 0x55, 0x66, 0x77,
561        ];
562        let parsed = OneByteElements::parse(&interspersed).unwrap();
563        let expected_elements = OneByteElements(vec![
564            OneByteElement {
565                id: OneByteId::new(1).unwrap(),
566                data: &[0x11],
567            },
568            OneByteElement {
569                id: OneByteId::new(2).unwrap(),
570                data: &[0x22, 0x33],
571            },
572            OneByteElement {
573                id: OneByteId::new(3).unwrap(),
574                data: &[0x44, 0x55, 0x66, 0x77],
575            },
576        ]);
577        assert_eq!(parsed, expected_elements, "decoded elements are identical");
578
579        // Re-serializing canonicalizes the padding to the tail: NOT
580        // byte-identical to `interspersed`, but re-parsing the canonical
581        // form still yields the same elements (semantic round trip).
582        let mut out = vec![0u8; parsed.serialized_len()];
583        parsed.serialize_into(&mut out).unwrap();
584        assert_ne!(
585            out, interspersed,
586            "padding position is canonicalized, not preserved verbatim"
587        );
588        assert_eq!(OneByteElements::parse(&out).unwrap(), expected_elements);
589    }
590
591    #[test]
592    fn one_byte_stop_marker_halts_parsing() {
593        // elem(id=1, 1 byte data), then ID=15 (stop) with a nonzero length
594        // nibble that MUST be ignored, then a trailing byte that must NOT be
595        // parsed as another element.
596        let bytes = [1 << 4, 0xAA, (15 << 4) | 5, 0xFF];
597        let parsed = OneByteElements::parse(&bytes).unwrap();
598        assert_eq!(parsed.elements().len(), 1);
599        assert_eq!(parsed.elements()[0].id.get(), 1);
600        assert_eq!(parsed.elements()[0].data, &[0xAA]);
601    }
602
603    #[test]
604    fn one_byte_malformed_id_zero_with_length_halts_parsing() {
605        // §4.1.2: an element with ID 0 and a length field > 0 is malformed;
606        // the length field MUST be ignored and processing MUST terminate,
607        // keeping only prior elements. Byte 0x05 = id nibble 0, len nibble 5
608        // (nonzero byte, so NOT the plain 0x00 padding case).
609        let bytes = [1 << 4, 0xAA, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
610        let parsed = OneByteElements::parse(&bytes).unwrap();
611        assert_eq!(parsed.elements().len(), 1);
612        assert_eq!(parsed.elements()[0].data, &[0xAA]);
613    }
614
615    #[test]
616    fn one_byte_pure_padding_byte_is_skipped_not_terminal() {
617        // A literal 0x00 byte is plain padding and parsing continues past
618        // it (unlike the id=0-with-nonzero-length case above).
619        let bytes = [
620            1 << 4,
621            0xAA,
622            0x00, // pad
623            2 << 4,
624            0xBB,
625        ];
626        let parsed = OneByteElements::parse(&bytes).unwrap();
627        assert_eq!(parsed.elements().len(), 2);
628        assert_eq!(parsed.elements()[1].id.get(), 2);
629        assert_eq!(parsed.elements()[1].data, &[0xBB]);
630    }
631
632    #[test]
633    fn one_byte_rejects_data_len_out_of_range() {
634        let elements = OneByteElements(vec![OneByteElement {
635            id: OneByteId::new(1).unwrap(),
636            data: &[],
637        }]);
638        let mut out = vec![0u8; 4];
639        assert!(matches!(
640            elements.serialize_into(&mut out),
641            Err(Error::InvalidValue {
642                field: "OneByteElement::data.len()",
643                ..
644            })
645        ));
646    }
647
648    #[test]
649    fn one_byte_truncated_element_data_is_buffer_too_short() {
650        // Header claims len nibble 3 (=> 4 bytes) but only 2 remain.
651        let bytes = [(1 << 4) | 3, 0xAA, 0xBB];
652        assert!(matches!(
653            OneByteElements::parse(&bytes),
654            Err(Error::BufferTooShort { .. })
655        ));
656    }
657
658    #[test]
659    fn one_byte_empty_is_valid() {
660        let elements = OneByteElements::default();
661        assert_eq!(elements.serialized_len(), 0);
662        let mut out: [u8; 0] = [];
663        elements.serialize_into(&mut out).unwrap();
664        let reparsed = OneByteElements::parse(&[]).unwrap();
665        assert_eq!(reparsed, elements);
666    }
667
668    // -- Two-byte form round trips -------------------------------------------
669
670    #[test]
671    fn two_byte_round_trip_single_element() {
672        let elements = TwoByteElements(vec![TwoByteElement {
673            id: TwoByteId::new(200).unwrap(),
674            data: &[0x01, 0x02, 0x03],
675        }]);
676        let mut out = vec![0u8; elements.serialized_len()];
677        elements.serialize_into(&mut out).unwrap();
678        assert_eq!(out[0], 200);
679        assert_eq!(out[1], 3);
680        assert_eq!(&out[2..5], &[0x01, 0x02, 0x03]);
681        assert_eq!(out.len(), 8, "5 bytes of content padded to 8");
682        let reparsed = TwoByteElements::parse(&out).unwrap();
683        assert_eq!(reparsed, elements);
684    }
685
686    #[test]
687    fn two_byte_spec_worked_example_structure() {
688        // RFC 8285 §4.3 worked example structure: elem(L=0 -> 0 bytes),
689        // elem(L=1 -> 1 byte), elem(L=4 -> 4 bytes). Padding is placed at
690        // the tail here (see the one-byte-form comment above for why this
691        // crate's `Serialize` canonicalizes padding placement).
692        let elements = TwoByteElements(vec![
693            TwoByteElement {
694                id: TwoByteId::new(10).unwrap(),
695                data: &[],
696            },
697            TwoByteElement {
698                id: TwoByteId::new(20).unwrap(),
699                data: &[0x99],
700            },
701            TwoByteElement {
702                id: TwoByteId::new(30).unwrap(),
703                data: &[0x01, 0x02, 0x03, 0x04],
704            },
705        ]);
706        let mut out = vec![0u8; elements.serialized_len()];
707        elements.serialize_into(&mut out).unwrap();
708        let expected = [
709            10, 0, // ID=10 L=0 (0 bytes)
710            20, 1, 0x99, // ID=20 L=1 (1 byte)
711            30, 4, // ID=30 L=4 (4 bytes)
712            0x01, 0x02, 0x03, 0x04, 0x00, // 1 trailing pad byte
713        ];
714        assert_eq!(out, expected);
715        assert_eq!(
716            out.len(),
717            12,
718            "3 words, matching length=3 in the RFC diagram"
719        );
720        let reparsed = TwoByteElements::parse(&out).unwrap();
721        assert_eq!(reparsed, elements);
722    }
723
724    #[test]
725    fn two_byte_reparses_interspersed_padding_to_the_same_elements() {
726        // As with the one-byte form: the RFC 8285 §4.3 diagram places its
727        // padding byte between the second and third elements. Decoding must
728        // still recover the same elements; re-serializing canonicalizes the
729        // padding to the tail (not byte-identical to the original, but
730        // semantically equal once re-parsed).
731        #[rustfmt::skip]
732        let interspersed: [u8; 12] = [
733            10, 0,
734            20, 1, 0x99,
735            0x00, // pad between elements, per the RFC's own diagram
736            30, 4, 0x01, 0x02, 0x03, 0x04,
737        ];
738        let parsed = TwoByteElements::parse(&interspersed).unwrap();
739        let expected_elements = TwoByteElements(vec![
740            TwoByteElement {
741                id: TwoByteId::new(10).unwrap(),
742                data: &[],
743            },
744            TwoByteElement {
745                id: TwoByteId::new(20).unwrap(),
746                data: &[0x99],
747            },
748            TwoByteElement {
749                id: TwoByteId::new(30).unwrap(),
750                data: &[0x01, 0x02, 0x03, 0x04],
751            },
752        ]);
753        assert_eq!(parsed, expected_elements, "decoded elements are identical");
754
755        let mut out = vec![0u8; parsed.serialized_len()];
756        parsed.serialize_into(&mut out).unwrap();
757        assert_ne!(
758            out, interspersed,
759            "padding position is canonicalized, not preserved verbatim"
760        );
761        assert_eq!(TwoByteElements::parse(&out).unwrap(), expected_elements);
762    }
763
764    #[test]
765    fn two_byte_zero_id_byte_is_padding_not_an_element() {
766        // A literal 0x00 byte is always a padding byte in the byte-by-byte
767        // scan, even in two-byte form -- it must never be interpreted as
768        // the start of an id=0 element header.
769        let bytes = [10u8, 0, 0x00, 20, 1, 0x77];
770        let parsed = TwoByteElements::parse(&bytes).unwrap();
771        assert_eq!(parsed.elements().len(), 2);
772        assert_eq!(parsed.elements()[0].id.get(), 10);
773        assert_eq!(parsed.elements()[0].data, &[] as &[u8]);
774        assert_eq!(parsed.elements()[1].id.get(), 20);
775        assert_eq!(parsed.elements()[1].data, &[0x77]);
776    }
777
778    #[test]
779    fn two_byte_truncated_length_byte_is_buffer_too_short() {
780        let bytes = [10u8]; // id byte with no following length byte
781        assert!(matches!(
782            TwoByteElements::parse(&bytes),
783            Err(Error::BufferTooShort { .. })
784        ));
785    }
786
787    #[test]
788    fn two_byte_truncated_element_data_is_buffer_too_short() {
789        let bytes = [10u8, 4, 0x01, 0x02]; // claims 4 bytes, only 2 present
790        assert!(matches!(
791            TwoByteElements::parse(&bytes),
792            Err(Error::BufferTooShort { .. })
793        ));
794    }
795
796    #[test]
797    fn two_byte_empty_is_valid() {
798        let elements = TwoByteElements::default();
799        assert_eq!(elements.serialized_len(), 0);
800        let reparsed = TwoByteElements::parse(&[]).unwrap();
801        assert_eq!(reparsed, elements);
802    }
803
804    // -- Top-level dispatch ---------------------------------------------------
805
806    #[test]
807    fn parse_extensions_dispatches_one_byte() {
808        let data = [1 << 4, 0xAA, 0x00, 0x00];
809        let ext = HeaderExtension {
810            profile_id: ONE_BYTE_PROFILE_ID,
811            data: &data,
812        };
813        let parsed = parse_extensions(&ext).unwrap();
814        assert!(matches!(parsed, ExtensionElements::OneByte(_)));
815    }
816
817    #[test]
818    fn parse_extensions_dispatches_two_byte() {
819        let data = [10u8, 1, 0xAA, 0x00];
820        let ext = HeaderExtension {
821            profile_id: 0x1005, // 0x1000 | appbits(5)
822            data: &data,
823        };
824        let parsed = parse_extensions(&ext).unwrap();
825        assert!(matches!(parsed, ExtensionElements::TwoByte(_)));
826    }
827
828    #[test]
829    fn parse_extensions_rejects_unknown_profile() {
830        let data: [u8; 0] = [];
831        let ext = HeaderExtension {
832            profile_id: 0x1234,
833            data: &data,
834        };
835        assert!(matches!(
836            parse_extensions(&ext),
837            Err(Error::NotRfc8285Extension { profile_id: 0x1234 })
838        ));
839    }
840
841    #[test]
842    fn extension_elements_serialize_round_trip_via_dispatch() {
843        let data = [(5 << 4) | 1, 0x01, 0x02, 0x00];
844        let ext = HeaderExtension {
845            profile_id: ONE_BYTE_PROFILE_ID,
846            data: &data,
847        };
848        let elements = parse_extensions(&ext).unwrap();
849        let mut out = vec![0u8; elements.serialized_len()];
850        elements.serialize_into(&mut out).unwrap();
851        assert_eq!(out, data);
852    }
853}