Skip to main content

transmux/
sample_groups.rs

1//! ProducerReferenceTimeBox, SampleGroupDescriptionBox, SampleToGroupBox, and
2//! SubSampleInformationBox — ISO/IEC 14496-12:2015 §8.16.5, §8.9.2, §8.9.3,
3//! §8.7.7.
4//!
5//! Typed containers for:
6//!
7//! | Box   | Name                           | Section   | Description                                       |
8//! |-------|--------------------------------|-----------|---------------------------------------------------|
9//! | `prft`| ProducerReferenceTimeBox       | §8.16.5   | NTP wall-clock anchor for the reference track     |
10//! | `sgpd`| SampleGroupDescriptionBox      | §8.9.3    | Per-grouping-type sample group description table  |
11//! | `sbgp`| SampleToGroupBox               | §8.9.2    | Maps samples to sample group description indices  |
12//! | `subs`| SubSampleInformationBox        | §8.7.7    | Per-sample sub-sample size/priority table         |
13//!
14//! All boxes are FullBoxes. Sizes are computed from fields; no `self.raw`
15//! passthrough in any serializer.
16//!
17//! # Spec citations
18//!
19//! - **prft**: ISO/IEC 14496-12:2015 §8.16.5.2 — version 0: `media_time` is u32;
20//!   version 1: `media_time` is u64.
21//! - **sgpd**: ISO/IEC 14496-12:2015 §8.9.3.2 — version 1: `default_length` field
22//!   present; version 0 is deprecated. Only `'roll'` (RollRecoveryEntry, i16
23//!   `roll_distance`) is typed; all other grouping types are stored as raw bytes
24//!   via [`SgpdEntry::Unknown`].
25//! - **sbgp**: ISO/IEC 14496-12:2015 §8.9.2.2 — version 0: no
26//!   `grouping_type_parameter`; version 1: `grouping_type_parameter` present.
27//! - **subs**: ISO/IEC 14496-12:2015 §8.7.7.2 — version 0: `subsample_size` is
28//!   u16; version 1: `subsample_size` is u32.
29
30use crate::error::{Error, Result};
31use crate::init_segment::bounded_entry_count;
32use alloc::vec::Vec;
33
34use broadcast_common::{Parse, Serialize};
35
36// ---------------------------------------------------------------------------
37// Wire-layout constants
38// ---------------------------------------------------------------------------
39
40const BOX_HEADER_SIZE: usize = 8;
41const FULLBOX_EXTRA_SIZE: usize = 4;
42
43const PRFT_TYPE: u32 = u32::from_be_bytes(*b"prft");
44const SGPD_TYPE: u32 = u32::from_be_bytes(*b"sgpd");
45const SBGP_TYPE: u32 = u32::from_be_bytes(*b"sbgp");
46const SUBS_TYPE: u32 = u32::from_be_bytes(*b"subs");
47
48/// Grouping type `'roll'` (RollRecoveryEntry) — ISO/IEC 14496-12:2015 §10.6.
49pub const GROUPING_TYPE_ROLL: u32 = u32::from_be_bytes(*b"roll");
50
51/// Grouping type `'seig'` (CencSampleEncryptionInformationGroupEntry) —
52/// ISO/IEC 23001-7 (CENC). Maps a run of samples to a KID/IV-size/pattern
53/// override distinct from the track's `tenc` default, i.e. per-sample-group
54/// key rotation within a single track. Not parsed as a typed [`SgpdEntry`]
55/// variant by this module (it carries CENC-specific fields `tenc.rs` would
56/// need to interpret, not just a bare distance/count); exposed here purely
57/// as the FourCC constant a decrypt/encrypt path can test `grouping_type`
58/// against to detect the presence of key-rotation content it does not yet
59/// implement, rather than silently decrypting every sample with only the
60/// track default key (see `transmux::cenc_decrypt`, issue #990).
61pub const GROUPING_TYPE_SEIG: u32 = u32::from_be_bytes(*b"seig");
62
63// ---------------------------------------------------------------------------
64// ProducerReferenceTimeBox — prft (ISO/IEC 14496-12:2015 §8.16.5)
65// ---------------------------------------------------------------------------
66
67/// Producer Reference Time Box (`prft`) — ISO/IEC 14496-12:2015 §8.16.5.2.
68///
69/// Provides a UTC wall-clock anchor for the reference track.
70///
71/// Wire layout (FullBox header omitted):
72///
73/// ```text
74/// reference_track_ID   u(32)
75/// ntp_timestamp        u(64)  — UTC time in NTP format
76/// media_time           u(32) if version == 0
77///                      u(64) if version == 1
78/// ```
79///
80/// `reference_track_ID` identifies the track whose decoding timeline is anchored.
81/// `ntp_timestamp` is the wall-clock time in NTP format corresponding to
82/// `media_time`. `media_time` is in the timescale of the reference track.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(serde::Serialize))]
85pub struct ProducerReferenceTimeBox {
86    /// FullBox version: 0 = `media_time` is u32; 1 = `media_time` is u64.
87    pub version: u8,
88    /// FullBox flags `[23:0]`.
89    pub flags: u32,
90    /// Track ID of the reference track (§8.16.5.3).
91    pub reference_track_id: u32,
92    /// UTC time in NTP format (§8.16.5.3).
93    pub ntp_timestamp: u64,
94    /// Media time in the reference track's timescale.
95    ///
96    /// Stored as u64; for version 0 the upper 32 bits are zero on the wire.
97    pub media_time: u64,
98}
99
100impl ProducerReferenceTimeBox {
101    /// Parse the body of a `prft` box (after the 8-byte BoxHeader).
102    pub fn parse_body(body: &[u8]) -> Result<Self> {
103        // version(1) + flags(3) + ref_track_id(4) + ntp(8) = 16 minimum
104        let min = FULLBOX_EXTRA_SIZE + 4 + 8;
105        if body.len() < min {
106            return Err(Error::BufferTooShort {
107                need: min,
108                have: body.len(),
109                what: "prft body",
110            });
111        }
112        let version = body[0];
113        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
114        let mut c = FULLBOX_EXTRA_SIZE;
115        let reference_track_id =
116            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
117        c += 4;
118        let ntp_timestamp = u64::from_be_bytes([
119            body[c],
120            body[c + 1],
121            body[c + 2],
122            body[c + 3],
123            body[c + 4],
124            body[c + 5],
125            body[c + 6],
126            body[c + 7],
127        ]);
128        c += 8;
129        let media_time = if version == 0 {
130            if body.len() < c + 4 {
131                return Err(Error::BufferTooShort {
132                    need: c + 4,
133                    have: body.len(),
134                    what: "prft media_time v0",
135                });
136            }
137            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as u64
138        } else {
139            if body.len() < c + 8 {
140                return Err(Error::BufferTooShort {
141                    need: c + 8,
142                    have: body.len(),
143                    what: "prft media_time v1",
144                });
145            }
146            u64::from_be_bytes([
147                body[c],
148                body[c + 1],
149                body[c + 2],
150                body[c + 3],
151                body[c + 4],
152                body[c + 5],
153                body[c + 6],
154                body[c + 7],
155            ])
156        };
157        Ok(Self {
158            version,
159            flags,
160            reference_track_id,
161            ntp_timestamp,
162            media_time,
163        })
164    }
165}
166
167impl<'a> Parse<'a> for ProducerReferenceTimeBox {
168    type Error = Error;
169    fn parse(bytes: &'a [u8]) -> Result<Self> {
170        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + 4 {
171            return Err(Error::BufferTooShort {
172                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + 4,
173                have: bytes.len(),
174                what: "prft box",
175            });
176        }
177        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
178        if ty != PRFT_TYPE {
179            return Err(Error::InvalidValue {
180                field: "box_type",
181                value: ty as u64,
182                reason: "expected prft",
183            });
184        }
185        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
186    }
187}
188
189impl Serialize for ProducerReferenceTimeBox {
190    type Error = Error;
191    fn serialized_len(&self) -> usize {
192        let mt_size = if self.version == 0 { 4 } else { 8 };
193        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + mt_size
194    }
195    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
196        let need = self.serialized_len();
197        if buf.len() < need {
198            return Err(Error::OutputBufferTooSmall {
199                need,
200                have: buf.len(),
201            });
202        }
203        let mut c = 0;
204        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
205        c += 4;
206        buf[c..c + 4].copy_from_slice(b"prft");
207        c += 4;
208        buf[c] = self.version;
209        let fb = self.flags.to_be_bytes();
210        buf[c + 1] = fb[1];
211        buf[c + 2] = fb[2];
212        buf[c + 3] = fb[3];
213        c += 4;
214        buf[c..c + 4].copy_from_slice(&self.reference_track_id.to_be_bytes());
215        c += 4;
216        buf[c..c + 8].copy_from_slice(&self.ntp_timestamp.to_be_bytes());
217        c += 8;
218        if self.version == 0 {
219            buf[c..c + 4].copy_from_slice(&(self.media_time as u32).to_be_bytes());
220            c += 4;
221        } else {
222            buf[c..c + 8].copy_from_slice(&self.media_time.to_be_bytes());
223            c += 8;
224        }
225        Ok(c)
226    }
227}
228
229// ---------------------------------------------------------------------------
230// SampleGroupDescriptionBox — sgpd (ISO/IEC 14496-12:2015 §8.9.3)
231// ---------------------------------------------------------------------------
232
233/// A parsed entry in the sgpd sample group description table.
234///
235/// Only `'roll'` (§10.6 RollRecoveryEntry) is fully typed; all other grouping
236/// types carry their body as raw bytes in [`SgpdEntry::Unknown`].
237#[derive(Debug, Clone, PartialEq, Eq)]
238#[cfg_attr(feature = "serde", derive(serde::Serialize))]
239#[non_exhaustive]
240pub enum SgpdEntry {
241    /// RollRecoveryEntry for grouping type `'roll'` (§10.6).
242    ///
243    /// `roll_distance` is the number of samples that must be decoded before the
244    /// stream is usable (negative = pre-roll, positive = post-roll).
245    Roll {
246        /// Roll distance in samples. Negative = pre-roll.
247        roll_distance: i16,
248    },
249    /// Raw bytes for any grouping type not specifically handled.
250    Unknown(Vec<u8>),
251}
252
253impl SgpdEntry {
254    /// Serialized size of this entry on the wire (body bytes only, no length prefix).
255    pub fn wire_len(&self) -> usize {
256        match self {
257            Self::Roll { .. } => 2,
258            Self::Unknown(v) => v.len(),
259        }
260    }
261}
262
263/// Sample Group Description Box (`sgpd`) — ISO/IEC 14496-12:2015 §8.9.3.2.
264///
265/// Version 1 is the current (non-deprecated) form and is the only version
266/// emitted by this serializer. Version 0 can be parsed.
267///
268/// Wire layout (FullBox header omitted):
269///
270/// ```text
271/// grouping_type          u(32)
272/// default_length         u(32)  — version == 1 only
273/// entry_count            u(32)
274/// for each entry:
275///   [description_length  u(32)] — only if version == 1 && default_length == 0
276///   SampleGroupEntry (grouping_type)
277/// ```
278///
279/// When `version == 1` and `default_length != 0`, every entry has the same
280/// length (`default_length` bytes). When `default_length == 0`, each entry is
281/// preceded by a 4-byte `description_length`.
282#[derive(Debug, Clone, PartialEq, Eq)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize))]
284pub struct SampleGroupDescriptionBox {
285    /// FullBox version.
286    pub version: u8,
287    /// FullBox flags `[23:0]`.
288    pub flags: u32,
289    /// Four-CC grouping type (e.g. `GROUPING_TYPE_ROLL`).
290    pub grouping_type: u32,
291    /// `default_length` from the v1 syntax; 0 means variable-length entries.
292    ///
293    /// On serialization this is recomputed from entries: if all entries have the
294    /// same `wire_len`, `default_length` is set to that value; otherwise 0
295    /// (each entry gets an explicit `description_length` prefix).
296    pub default_length: u32,
297    /// Parsed entries.
298    pub entries: Vec<SgpdEntry>,
299}
300
301impl SampleGroupDescriptionBox {
302    /// Parse the body of an `sgpd` box (after the 8-byte BoxHeader).
303    pub fn parse_body(body: &[u8]) -> Result<Self> {
304        if body.len() < FULLBOX_EXTRA_SIZE + 4 + 4 {
305            return Err(Error::BufferTooShort {
306                need: FULLBOX_EXTRA_SIZE + 4 + 4,
307                have: body.len(),
308                what: "sgpd body",
309            });
310        }
311        let version = body[0];
312        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
313        let mut c = FULLBOX_EXTRA_SIZE;
314        let grouping_type = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
315        c += 4;
316
317        let default_length = if version == 1 {
318            if body.len() < c + 4 {
319                return Err(Error::BufferTooShort {
320                    need: c + 4,
321                    have: body.len(),
322                    what: "sgpd default_length",
323                });
324            }
325            let dl = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
326            c += 4;
327            dl
328        } else if version >= 2 {
329            // version 2 has default_sample_description_index instead
330            if body.len() < c + 4 {
331                return Err(Error::BufferTooShort {
332                    need: c + 4,
333                    have: body.len(),
334                    what: "sgpd default_sample_description_index",
335                });
336            }
337            c += 4; // skip default_sample_description_index
338            0
339        } else {
340            0 // version 0: no default_length field
341        };
342
343        if body.len() < c + 4 {
344            return Err(Error::BufferTooShort {
345                need: c + 4,
346                have: body.len(),
347                what: "sgpd entry_count",
348            });
349        }
350        let entry_count =
351            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
352        c += 4;
353
354        // Minimum bytes each entry can possibly consume, for bounding the
355        // up-front allocation against a hostile `entry_count` (#988): a v1
356        // per-entry `description_length` prefix (4 bytes) when
357        // `default_length` is 0, else `default_length` itself; 2 bytes for a
358        // v0 'roll' entry; 1 byte as a floor for the "consume the rest as one
359        // blob" v0 fallback.
360        let sgpd_min_entry_len: usize = if version == 1 {
361            if default_length == 0 {
362                4
363            } else {
364                default_length as usize
365            }
366        } else if grouping_type == GROUPING_TYPE_ROLL {
367            2
368        } else {
369            1
370        };
371        let mut entries = Vec::with_capacity(bounded_entry_count(
372            body.len().saturating_sub(c),
373            sgpd_min_entry_len,
374            entry_count,
375        ));
376        for _ in 0..entry_count {
377            // Determine entry length
378            let entry_len: usize = if version == 1 && default_length == 0 {
379                if body.len() < c + 4 {
380                    return Err(Error::BufferTooShort {
381                        need: c + 4,
382                        have: body.len(),
383                        what: "sgpd description_length",
384                    });
385                }
386                let dl =
387                    u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
388                c += 4;
389                dl
390            } else if version == 1 {
391                default_length as usize
392            } else {
393                // version 0: entry size is implied by the grouping_type; we
394                // parse until we hit the known size for 'roll' (2 bytes), else
395                // we consume remaining bytes as one blob (rare / deprecated).
396                if grouping_type == GROUPING_TYPE_ROLL {
397                    2
398                } else {
399                    // unknown v0: consume all remaining as one entry
400                    body.len() - c
401                }
402            };
403            if body.len() < c + entry_len {
404                return Err(Error::BufferTooShort {
405                    need: c + entry_len,
406                    have: body.len(),
407                    what: "sgpd entry body",
408                });
409            }
410            let entry_bytes = &body[c..c + entry_len];
411            let entry = if grouping_type == GROUPING_TYPE_ROLL && entry_len >= 2 {
412                let rd = i16::from_be_bytes([entry_bytes[0], entry_bytes[1]]);
413                SgpdEntry::Roll { roll_distance: rd }
414            } else {
415                SgpdEntry::Unknown(entry_bytes.to_vec())
416            };
417            entries.push(entry);
418            c += entry_len;
419        }
420
421        Ok(Self {
422            version,
423            flags,
424            grouping_type,
425            default_length,
426            entries,
427        })
428    }
429}
430
431impl<'a> Parse<'a> for SampleGroupDescriptionBox {
432    type Error = Error;
433    fn parse(bytes: &'a [u8]) -> Result<Self> {
434        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
435            return Err(Error::BufferTooShort {
436                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
437                have: bytes.len(),
438                what: "sgpd box",
439            });
440        }
441        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
442        if ty != SGPD_TYPE {
443            return Err(Error::InvalidValue {
444                field: "box_type",
445                value: ty as u64,
446                reason: "expected sgpd",
447            });
448        }
449        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
450    }
451}
452
453impl Serialize for SampleGroupDescriptionBox {
454    type Error = Error;
455    fn serialized_len(&self) -> usize {
456        // Compute whether we will use a uniform default_length.
457        let (use_default_len, per_entry_prefix) = self.effective_default_length();
458        let entry_overhead = if per_entry_prefix { 4 } else { 0 };
459        let entries_size: usize = self
460            .entries
461            .iter()
462            .map(|e| entry_overhead + e.wire_len())
463            .sum();
464        // header + fullbox + grouping_type + [default_length] + entry_count + entries
465        let dl_field = if self.version == 1 { 4 } else { 0 };
466        let _ = use_default_len;
467        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + dl_field + 4 + entries_size
468    }
469
470    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
471        let need = self.serialized_len();
472        if buf.len() < need {
473            return Err(Error::OutputBufferTooSmall {
474                need,
475                have: buf.len(),
476            });
477        }
478        let (effective_dl, per_entry_prefix) = self.effective_default_length();
479        let mut c = 0;
480        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
481        c += 4;
482        buf[c..c + 4].copy_from_slice(b"sgpd");
483        c += 4;
484        buf[c] = self.version;
485        let fb = self.flags.to_be_bytes();
486        buf[c + 1] = fb[1];
487        buf[c + 2] = fb[2];
488        buf[c + 3] = fb[3];
489        c += 4;
490        buf[c..c + 4].copy_from_slice(&self.grouping_type.to_be_bytes());
491        c += 4;
492        if self.version == 1 {
493            buf[c..c + 4].copy_from_slice(&effective_dl.to_be_bytes());
494            c += 4;
495        }
496        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
497        c += 4;
498        for entry in &self.entries {
499            if per_entry_prefix {
500                buf[c..c + 4].copy_from_slice(&(entry.wire_len() as u32).to_be_bytes());
501                c += 4;
502            }
503            match entry {
504                SgpdEntry::Roll { roll_distance } => {
505                    buf[c..c + 2].copy_from_slice(&roll_distance.to_be_bytes());
506                    c += 2;
507                }
508                SgpdEntry::Unknown(v) => {
509                    buf[c..c + v.len()].copy_from_slice(v);
510                    c += v.len();
511                }
512            }
513        }
514        Ok(c)
515    }
516}
517
518impl SampleGroupDescriptionBox {
519    /// Compute the `default_length` to write and whether per-entry length
520    /// prefixes are needed.
521    ///
522    /// Returns `(effective_default_length, per_entry_prefix_needed)`.
523    fn effective_default_length(&self) -> (u32, bool) {
524        if self.version != 1 || self.entries.is_empty() {
525            return (0, false);
526        }
527        let first = self.entries[0].wire_len();
528        let uniform = self.entries.iter().all(|e| e.wire_len() == first);
529        if uniform {
530            (first as u32, false)
531        } else {
532            (0, true)
533        }
534    }
535}
536
537// ---------------------------------------------------------------------------
538// SampleToGroupBox — sbgp (ISO/IEC 14496-12:2015 §8.9.2)
539// ---------------------------------------------------------------------------
540
541/// Entry in the sbgp sample-to-group mapping table (§8.9.2.2).
542#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543#[cfg_attr(feature = "serde", derive(serde::Serialize))]
544pub struct SbgpEntry {
545    /// Number of consecutive samples belonging to this group.
546    pub sample_count: u32,
547    /// Index into the sgpd table (1-based, or 0 = no group).
548    pub group_description_index: u32,
549}
550
551/// Sample To Group Box (`sbgp`) — ISO/IEC 14496-12:2015 §8.9.2.2.
552///
553/// Wire layout (FullBox header omitted):
554///
555/// ```text
556/// grouping_type            u(32)
557/// [grouping_type_parameter u(32)] — version == 1 only
558/// entry_count              u(32)
559/// for each entry:
560///   sample_count             u(32)
561///   group_description_index  u(32)
562/// ```
563#[derive(Debug, Clone, PartialEq, Eq)]
564#[cfg_attr(feature = "serde", derive(serde::Serialize))]
565pub struct SampleToGroupBox {
566    /// FullBox version: 0 = no `grouping_type_parameter`; 1 = has it.
567    pub version: u8,
568    /// FullBox flags `[23:0]`.
569    pub flags: u32,
570    /// Four-CC grouping type linking this box to its sgpd.
571    pub grouping_type: u32,
572    /// Optional sub-type parameter (version 1 only).
573    pub grouping_type_parameter: Option<u32>,
574    /// Mapping entries.
575    pub entries: Vec<SbgpEntry>,
576}
577
578impl SampleToGroupBox {
579    /// Parse the body of an `sbgp` box (after the 8-byte BoxHeader).
580    pub fn parse_body(body: &[u8]) -> Result<Self> {
581        if body.len() < FULLBOX_EXTRA_SIZE + 4 + 4 {
582            return Err(Error::BufferTooShort {
583                need: FULLBOX_EXTRA_SIZE + 4 + 4,
584                have: body.len(),
585                what: "sbgp body",
586            });
587        }
588        let version = body[0];
589        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
590        let mut c = FULLBOX_EXTRA_SIZE;
591        let grouping_type = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
592        c += 4;
593        let grouping_type_parameter = if version == 1 {
594            if body.len() < c + 4 {
595                return Err(Error::BufferTooShort {
596                    need: c + 4,
597                    have: body.len(),
598                    what: "sbgp grouping_type_parameter",
599                });
600            }
601            let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
602            c += 4;
603            Some(v)
604        } else {
605            None
606        };
607        if body.len() < c + 4 {
608            return Err(Error::BufferTooShort {
609                need: c + 4,
610                have: body.len(),
611                what: "sbgp entry_count",
612            });
613        }
614        let entry_count =
615            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
616        c += 4;
617        // Each entry is a fixed 8 bytes (sample_count + group_description_index).
618        let mut entries = Vec::with_capacity(bounded_entry_count(
619            body.len().saturating_sub(c),
620            8,
621            entry_count,
622        ));
623        for _ in 0..entry_count {
624            if body.len() < c + 8 {
625                return Err(Error::BufferTooShort {
626                    need: c + 8,
627                    have: body.len(),
628                    what: "sbgp entry",
629                });
630            }
631            let sample_count = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
632            let group_description_index =
633                u32::from_be_bytes([body[c + 4], body[c + 5], body[c + 6], body[c + 7]]);
634            entries.push(SbgpEntry {
635                sample_count,
636                group_description_index,
637            });
638            c += 8;
639        }
640        Ok(Self {
641            version,
642            flags,
643            grouping_type,
644            grouping_type_parameter,
645            entries,
646        })
647    }
648}
649
650impl<'a> Parse<'a> for SampleToGroupBox {
651    type Error = Error;
652    fn parse(bytes: &'a [u8]) -> Result<Self> {
653        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
654            return Err(Error::BufferTooShort {
655                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
656                have: bytes.len(),
657                what: "sbgp box",
658            });
659        }
660        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
661        if ty != SBGP_TYPE {
662            return Err(Error::InvalidValue {
663                field: "box_type",
664                value: ty as u64,
665                reason: "expected sbgp",
666            });
667        }
668        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
669    }
670}
671
672impl Serialize for SampleToGroupBox {
673    type Error = Error;
674    fn serialized_len(&self) -> usize {
675        let gtp_size = if self.version == 1 { 4 } else { 0 };
676        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + gtp_size + 4 + self.entries.len() * 8
677    }
678    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
679        let need = self.serialized_len();
680        if buf.len() < need {
681            return Err(Error::OutputBufferTooSmall {
682                need,
683                have: buf.len(),
684            });
685        }
686        let mut c = 0;
687        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
688        c += 4;
689        buf[c..c + 4].copy_from_slice(b"sbgp");
690        c += 4;
691        buf[c] = self.version;
692        let fb = self.flags.to_be_bytes();
693        buf[c + 1] = fb[1];
694        buf[c + 2] = fb[2];
695        buf[c + 3] = fb[3];
696        c += 4;
697        buf[c..c + 4].copy_from_slice(&self.grouping_type.to_be_bytes());
698        c += 4;
699        if self.version == 1 {
700            let gtp = self.grouping_type_parameter.unwrap_or(0);
701            buf[c..c + 4].copy_from_slice(&gtp.to_be_bytes());
702            c += 4;
703        }
704        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
705        c += 4;
706        for entry in &self.entries {
707            buf[c..c + 4].copy_from_slice(&entry.sample_count.to_be_bytes());
708            buf[c + 4..c + 8].copy_from_slice(&entry.group_description_index.to_be_bytes());
709            c += 8;
710        }
711        Ok(c)
712    }
713}
714
715// ---------------------------------------------------------------------------
716// SubSampleInformationBox — subs (ISO/IEC 14496-12:2015 §8.7.7)
717// ---------------------------------------------------------------------------
718
719/// A single sub-sample descriptor within a [`SubsEntry`].
720///
721/// `subsample_size` width depends on the `subs` box version:
722/// version 0 → u16; version 1 → u32. Stored as u32 in both cases.
723#[derive(Debug, Clone, Copy, PartialEq, Eq)]
724#[cfg_attr(feature = "serde", derive(serde::Serialize))]
725pub struct SubSampleDescriptor {
726    /// Size in bytes (u16 on wire for v0, u32 for v1).
727    pub subsample_size: u32,
728    /// Degradation priority (higher = more important, §8.7.7.3).
729    pub subsample_priority: u8,
730    /// 0 = required; 1 = discardable (§8.7.7.3).
731    pub discardable: u8,
732    /// Codec-specific parameters (§8.7.7.3); 0 if not defined.
733    pub codec_specific_parameters: u32,
734}
735
736impl SubSampleDescriptor {
737    fn wire_len(version: u8) -> usize {
738        let size_field = if version == 1 { 4 } else { 2 };
739        size_field + 1 + 1 + 4
740    }
741}
742
743/// Per-sample entry in the subs table (§8.7.7.2).
744#[derive(Debug, Clone, PartialEq, Eq)]
745#[cfg_attr(feature = "serde", derive(serde::Serialize))]
746pub struct SubsEntry {
747    /// Delta from the previous entry's sample number (or from 0 for the first
748    /// entry), giving the sample number of this entry's first described sample.
749    pub sample_delta: u32,
750    /// Sub-sample descriptors for this sample (may be empty).
751    pub subsamples: Vec<SubSampleDescriptor>,
752}
753
754impl SubsEntry {
755    fn wire_len(&self, version: u8) -> usize {
756        4 + 2 + self.subsamples.len() * SubSampleDescriptor::wire_len(version)
757    }
758}
759
760/// Sub-Sample Information Box (`subs`) — ISO/IEC 14496-12:2015 §8.7.7.2.
761///
762/// Wire layout (FullBox header omitted):
763///
764/// ```text
765/// entry_count     u(32)
766/// for each entry:
767///   sample_delta    u(32)
768///   subsample_count u(16)
769///   for each subsample:
770///     subsample_size      u(16) if version == 0, u(32) if version == 1
771///     subsample_priority  u(8)
772///     discardable         u(8)
773///     codec_specific_parameters u(32)
774/// ```
775#[derive(Debug, Clone, PartialEq, Eq)]
776#[cfg_attr(feature = "serde", derive(serde::Serialize))]
777pub struct SubSampleInformationBox {
778    /// FullBox version: 0 = `subsample_size` is u16; 1 = u32.
779    pub version: u8,
780    /// FullBox flags `[23:0]`.
781    pub flags: u32,
782    /// Sample entries (each covering one sample with sub-sample structure).
783    pub entries: Vec<SubsEntry>,
784}
785
786impl SubSampleInformationBox {
787    /// Parse the body of a `subs` box (after the 8-byte BoxHeader).
788    pub fn parse_body(body: &[u8]) -> Result<Self> {
789        if body.len() < FULLBOX_EXTRA_SIZE + 4 {
790            return Err(Error::BufferTooShort {
791                need: FULLBOX_EXTRA_SIZE + 4,
792                have: body.len(),
793                what: "subs body",
794            });
795        }
796        let version = body[0];
797        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
798        let mut c = FULLBOX_EXTRA_SIZE;
799        let entry_count =
800            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
801        c += 4;
802        let ss_size_field = if version == 1 { 4usize } else { 2usize };
803        // Each entry's fixed header (sample_delta + subsample_count) is 6
804        // bytes; the variable-length subsample list is bounded separately
805        // below, per entry.
806        let mut entries = Vec::with_capacity(bounded_entry_count(
807            body.len().saturating_sub(c),
808            4 + 2,
809            entry_count,
810        ));
811        for _ in 0..entry_count {
812            if body.len() < c + 4 + 2 {
813                return Err(Error::BufferTooShort {
814                    need: c + 6,
815                    have: body.len(),
816                    what: "subs entry header",
817                });
818            }
819            let sample_delta = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
820            c += 4;
821            let subsample_count = u16::from_be_bytes([body[c], body[c + 1]]) as usize;
822            c += 2;
823            let ss_wire = ss_size_field + 1 + 1 + 4;
824            let mut subsamples = Vec::with_capacity(bounded_entry_count(
825                body.len().saturating_sub(c),
826                ss_wire,
827                subsample_count,
828            ));
829            for _ in 0..subsample_count {
830                if body.len() < c + ss_wire {
831                    return Err(Error::BufferTooShort {
832                        need: c + ss_wire,
833                        have: body.len(),
834                        what: "subs subsample",
835                    });
836                }
837                let subsample_size = if version == 1 {
838                    let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
839                    c += 4;
840                    v
841                } else {
842                    let v = u16::from_be_bytes([body[c], body[c + 1]]) as u32;
843                    c += 2;
844                    v
845                };
846                let subsample_priority = body[c];
847                let discardable = body[c + 1];
848                c += 2;
849                let codec_specific_parameters =
850                    u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
851                c += 4;
852                subsamples.push(SubSampleDescriptor {
853                    subsample_size,
854                    subsample_priority,
855                    discardable,
856                    codec_specific_parameters,
857                });
858            }
859            entries.push(SubsEntry {
860                sample_delta,
861                subsamples,
862            });
863        }
864        Ok(Self {
865            version,
866            flags,
867            entries,
868        })
869    }
870}
871
872impl<'a> Parse<'a> for SubSampleInformationBox {
873    type Error = Error;
874    fn parse(bytes: &'a [u8]) -> Result<Self> {
875        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
876            return Err(Error::BufferTooShort {
877                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
878                have: bytes.len(),
879                what: "subs box",
880            });
881        }
882        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
883        if ty != SUBS_TYPE {
884            return Err(Error::InvalidValue {
885                field: "box_type",
886                value: ty as u64,
887                reason: "expected subs",
888            });
889        }
890        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
891    }
892}
893
894impl Serialize for SubSampleInformationBox {
895    type Error = Error;
896    fn serialized_len(&self) -> usize {
897        let entries_size: usize = self.entries.iter().map(|e| e.wire_len(self.version)).sum();
898        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + entries_size
899    }
900    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
901        let need = self.serialized_len();
902        if buf.len() < need {
903            return Err(Error::OutputBufferTooSmall {
904                need,
905                have: buf.len(),
906            });
907        }
908        let mut c = 0;
909        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
910        c += 4;
911        buf[c..c + 4].copy_from_slice(b"subs");
912        c += 4;
913        buf[c] = self.version;
914        let fb = self.flags.to_be_bytes();
915        buf[c + 1] = fb[1];
916        buf[c + 2] = fb[2];
917        buf[c + 3] = fb[3];
918        c += 4;
919        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
920        c += 4;
921        for entry in &self.entries {
922            buf[c..c + 4].copy_from_slice(&entry.sample_delta.to_be_bytes());
923            c += 4;
924            buf[c..c + 2].copy_from_slice(&(entry.subsamples.len() as u16).to_be_bytes());
925            c += 2;
926            for ss in &entry.subsamples {
927                if self.version == 1 {
928                    buf[c..c + 4].copy_from_slice(&ss.subsample_size.to_be_bytes());
929                    c += 4;
930                } else {
931                    buf[c..c + 2].copy_from_slice(&(ss.subsample_size as u16).to_be_bytes());
932                    c += 2;
933                }
934                buf[c] = ss.subsample_priority;
935                buf[c + 1] = ss.discardable;
936                c += 2;
937                buf[c..c + 4].copy_from_slice(&ss.codec_specific_parameters.to_be_bytes());
938                c += 4;
939            }
940        }
941        Ok(c)
942    }
943}
944
945// ---------------------------------------------------------------------------
946// Unit tests
947// ---------------------------------------------------------------------------
948
949#[cfg(test)]
950mod tests {
951    use super::*;
952    use broadcast_common::Serialize;
953
954    // -----------------------------------------------------------------------
955    // prft
956    // -----------------------------------------------------------------------
957
958    #[test]
959    fn prft_round_trip_v0() {
960        let b = ProducerReferenceTimeBox {
961            version: 0,
962            flags: 0,
963            reference_track_id: 1,
964            ntp_timestamp: 0x1234_5678_9abc_def0,
965            media_time: 0x0000_0000_0000_1234,
966        };
967        let bytes = b.to_bytes();
968        assert_eq!(bytes.len(), 8 + 4 + 4 + 8 + 4);
969        let parsed = ProducerReferenceTimeBox::parse(&bytes).unwrap();
970        assert_eq!(parsed, b);
971    }
972
973    #[test]
974    fn prft_round_trip_v1() {
975        let b = ProducerReferenceTimeBox {
976            version: 1,
977            flags: 0x000018,
978            reference_track_id: 1,
979            ntp_timestamp: 0xedefe3e3_a7ae147a,
980            media_time: 0x0000_0000_0000_1c20,
981        };
982        let bytes = b.to_bytes();
983        assert_eq!(bytes.len(), 8 + 4 + 4 + 8 + 8);
984        let parsed = ProducerReferenceTimeBox::parse(&bytes).unwrap();
985        assert_eq!(parsed, b);
986    }
987
988    // -----------------------------------------------------------------------
989    // sgpd
990    // -----------------------------------------------------------------------
991
992    #[test]
993    fn sgpd_round_trip_roll_v1() {
994        let b = SampleGroupDescriptionBox {
995            version: 1,
996            flags: 0,
997            grouping_type: GROUPING_TYPE_ROLL,
998            default_length: 2,
999            entries: vec![SgpdEntry::Roll { roll_distance: -1 }],
1000        };
1001        let bytes = b.to_bytes();
1002        let parsed = SampleGroupDescriptionBox::parse(&bytes).unwrap();
1003        assert_eq!(parsed.entries.len(), 1);
1004        assert_eq!(parsed.entries[0], SgpdEntry::Roll { roll_distance: -1 });
1005        assert_eq!(parsed.to_bytes(), bytes);
1006    }
1007
1008    #[test]
1009    fn sgpd_round_trip_two_roll_entries() {
1010        let b = SampleGroupDescriptionBox {
1011            version: 1,
1012            flags: 0,
1013            grouping_type: GROUPING_TYPE_ROLL,
1014            default_length: 2,
1015            entries: vec![
1016                SgpdEntry::Roll { roll_distance: -4 },
1017                SgpdEntry::Roll { roll_distance: -1 },
1018            ],
1019        };
1020        let bytes = b.to_bytes();
1021        let parsed = SampleGroupDescriptionBox::parse(&bytes).unwrap();
1022        assert_eq!(parsed.entries.len(), 2);
1023        assert_eq!(parsed.to_bytes(), bytes);
1024    }
1025
1026    // -----------------------------------------------------------------------
1027    // sbgp
1028    // -----------------------------------------------------------------------
1029
1030    #[test]
1031    fn sbgp_round_trip_v0() {
1032        let b = SampleToGroupBox {
1033            version: 0,
1034            flags: 0,
1035            grouping_type: GROUPING_TYPE_ROLL,
1036            grouping_type_parameter: None,
1037            entries: vec![
1038                SbgpEntry {
1039                    sample_count: 1,
1040                    group_description_index: 1,
1041                },
1042                SbgpEntry {
1043                    sample_count: 10,
1044                    group_description_index: 0,
1045                },
1046            ],
1047        };
1048        let bytes = b.to_bytes();
1049        let parsed = SampleToGroupBox::parse(&bytes).unwrap();
1050        assert_eq!(parsed, b);
1051    }
1052
1053    #[test]
1054    fn sbgp_round_trip_v1() {
1055        let b = SampleToGroupBox {
1056            version: 1,
1057            flags: 0,
1058            grouping_type: GROUPING_TYPE_ROLL,
1059            grouping_type_parameter: Some(0xDEAD_BEEF),
1060            entries: vec![SbgpEntry {
1061                sample_count: 5,
1062                group_description_index: 1,
1063            }],
1064        };
1065        let bytes = b.to_bytes();
1066        let parsed = SampleToGroupBox::parse(&bytes).unwrap();
1067        assert_eq!(parsed, b);
1068    }
1069
1070    // -----------------------------------------------------------------------
1071    // subs
1072    // -----------------------------------------------------------------------
1073
1074    #[test]
1075    fn subs_round_trip_v0() {
1076        let b = SubSampleInformationBox {
1077            version: 0,
1078            flags: 0,
1079            entries: vec![SubsEntry {
1080                sample_delta: 1,
1081                subsamples: vec![
1082                    SubSampleDescriptor {
1083                        subsample_size: 100,
1084                        subsample_priority: 255,
1085                        discardable: 0,
1086                        codec_specific_parameters: 0,
1087                    },
1088                    SubSampleDescriptor {
1089                        subsample_size: 200,
1090                        subsample_priority: 128,
1091                        discardable: 1,
1092                        codec_specific_parameters: 0,
1093                    },
1094                ],
1095            }],
1096        };
1097        let bytes = b.to_bytes();
1098        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1099        assert_eq!(parsed, b);
1100        assert_eq!(parsed.to_bytes(), bytes);
1101    }
1102
1103    #[test]
1104    fn subs_round_trip_v1() {
1105        let b = SubSampleInformationBox {
1106            version: 1,
1107            flags: 0,
1108            entries: vec![SubsEntry {
1109                sample_delta: 5,
1110                subsamples: vec![SubSampleDescriptor {
1111                    subsample_size: 0x0001_2345,
1112                    subsample_priority: 200,
1113                    discardable: 0,
1114                    codec_specific_parameters: 0xABCD_EF01,
1115                }],
1116            }],
1117        };
1118        let bytes = b.to_bytes();
1119        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1120        assert_eq!(parsed, b);
1121        assert_eq!(parsed.to_bytes(), bytes);
1122    }
1123
1124    #[test]
1125    fn subs_empty_round_trip() {
1126        let b = SubSampleInformationBox {
1127            version: 0,
1128            flags: 0,
1129            entries: vec![],
1130        };
1131        let bytes = b.to_bytes();
1132        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1133        assert_eq!(parsed, b);
1134    }
1135}