use crate::error::{Error, Result};
use broadcast_common::error::{BufferTooShort, InvalidValue};
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
use core::fmt;
pub const SIZE_INDICATES_LARGESIZE: u32 = 1;
pub const SIZE_TO_EOF: u32 = 0;
pub const BOX_HEADER_MIN_SIZE: usize = 8;
const BOX_TYPE_RANGE: core::ops::Range<usize> = 4..8;
pub const LARGESIZE_SIZE: usize = 8;
pub const UUID_TYPE_SIZE: usize = 16;
pub const FULLBOX_EXTRA_SIZE: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BoxType(pub [u8; 4]);
impl BoxType {
pub const fn from_bytes(b: [u8; 4]) -> Self {
Self(b)
}
pub fn from_u32(v: u32) -> Self {
Self(v.to_be_bytes())
}
pub fn to_u32(self) -> u32 {
u32::from_be_bytes(self.0)
}
pub fn is(&self, literal: &[u8; 4]) -> bool {
self.0 == *literal
}
pub fn name(&self) -> &'static str {
match &self.0 {
b"ftyp" => "ftyp",
b"moov" => "moov",
b"moof" => "moof",
b"trak" => "trak",
b"mdia" => "mdia",
b"minf" => "minf",
b"stbl" => "stbl",
b"dinf" => "dinf",
b"edts" => "edts",
b"mvex" => "mvex",
b"mvhd" => "mvhd",
b"tkhd" => "tkhd",
b"mdhd" => "mdhd",
b"hdlr" => "hdlr",
b"vmhd" => "vmhd",
b"smhd" => "smhd",
b"stsd" => "stsd",
b"stts" => "stts",
b"stsc" => "stsc",
b"stsz" => "stsz",
b"stco" => "stco",
b"co64" => "co64",
b"ctts" => "ctts",
b"stss" => "stss",
b"stsh" => "stsh",
b"elst" => "elst",
b"dref" => "dref",
b"tref" => "tref",
b"mdat" => "mdat",
b"free" => "free",
b"skip" => "skip",
b"uuid" => "uuid",
b"mfhd" => "mfhd",
b"traf" => "traf",
b"tfhd" => "tfhd",
b"trun" => "trun",
b"tfdt" => "tfdt",
b"sidx" => "sidx",
b"styp" => "styp",
b"mfra" => "mfra",
b"tfra" => "tfra",
b"mfro" => "mfro",
b"emsg" => "emsg",
b"avc1" => "avc1",
b"mp4a" => "mp4a",
b"enca" => "enca",
b"encv" => "encv",
b"hvc1" => "hvc1",
b"avcC" => "avcC",
b"hvcC" => "hvcC",
b"avc2" => "avc2",
b"avc3" => "avc3",
b"avc4" => "avc4",
b"hev1" => "hev1",
_ => "<unknown>",
}
}
}
impl fmt::Display for BoxType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &b in &self.0 {
if b.is_ascii_graphic() || b == b' ' {
f.write_str(core::str::from_utf8(&[b]).unwrap_or("?"))?;
} else {
write!(f, "\\x{b:02x}")?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BoxHeader {
pub size: u64,
pub box_type: BoxType,
pub usertype: Option<[u8; UUID_TYPE_SIZE]>,
has_largesize: bool,
}
impl BoxHeader {
pub fn has_largesize(&self) -> bool {
self.has_largesize
}
pub fn header_size(&self) -> usize {
let mut sz = BOX_HEADER_MIN_SIZE;
if self.has_largesize {
sz += LARGESIZE_SIZE;
}
if self.box_type.is(b"uuid") {
sz += UUID_TYPE_SIZE;
}
sz
}
pub fn new(size: u64, box_type: BoxType, usertype: Option<[u8; UUID_TYPE_SIZE]>) -> Self {
let has_largesize = size > u32::MAX as u64;
Self {
size,
box_type,
usertype,
has_largesize,
}
}
pub(crate) fn for_payload(
box_type: BoxType,
usertype: Option<[u8; UUID_TYPE_SIZE]>,
payload_len: u64,
) -> Self {
let uuid = if box_type.is(b"uuid") {
UUID_TYPE_SIZE as u64
} else {
0
};
let compact = (BOX_HEADER_MIN_SIZE as u64 + uuid).saturating_add(payload_len);
let size = if compact > u32::MAX as u64 {
compact.saturating_add(LARGESIZE_SIZE as u64)
} else {
compact
};
Self::new(size, box_type, usertype)
}
}
#[cfg(test)]
std::thread_local! {
static FORCE_LARGESIZE: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
#[cfg(test)]
pub(crate) struct ForceLargesize;
#[cfg(test)]
impl ForceLargesize {
pub(crate) fn on() -> Self {
FORCE_LARGESIZE.with(|f| f.set(true));
ForceLargesize
}
}
#[cfg(test)]
impl Drop for ForceLargesize {
fn drop(&mut self) {
FORCE_LARGESIZE.with(|f| f.set(false));
}
}
#[cfg(test)]
fn force_largesize() -> bool {
FORCE_LARGESIZE.with(core::cell::Cell::get)
}
#[cfg(not(test))]
fn force_largesize() -> bool {
false
}
pub(crate) fn box_len(compact_len: usize) -> usize {
if force_largesize() || compact_len as u64 > u32::MAX as u64 {
compact_len.saturating_add(LARGESIZE_SIZE)
} else {
compact_len
}
}
pub(crate) fn encode_box_header(fourcc: &[u8; 4], total: u64) -> ([u8; 16], usize) {
let mut out = [0u8; 16];
out[4..8].copy_from_slice(fourcc);
if force_largesize() || total > u32::MAX as u64 {
out[0..4].copy_from_slice(&SIZE_INDICATES_LARGESIZE.to_be_bytes());
out[8..16].copy_from_slice(&total.to_be_bytes());
(out, BOX_HEADER_MIN_SIZE + LARGESIZE_SIZE)
} else {
out[0..4].copy_from_slice(&(total as u32).to_be_bytes());
(out, BOX_HEADER_MIN_SIZE)
}
}
pub(crate) fn write_box_header(buf: &mut [u8], fourcc: &[u8; 4], need: usize) -> Result<usize> {
let (hdr, len) = encode_box_header(fourcc, need as u64);
need_out(buf, len)?;
buf[..len].copy_from_slice(&hdr[..len]);
Ok(len)
}
pub(crate) fn write_compact_box_header(
buf: &mut [u8],
fourcc: &[u8; 4],
need: usize,
) -> Result<usize> {
let size = broadcast_common::len::fit_u32(need, "box size")?;
need_out(buf, BOX_HEADER_MIN_SIZE)?;
buf[0..4].copy_from_slice(&size.to_be_bytes());
buf[4..8].copy_from_slice(fourcc);
Ok(BOX_HEADER_MIN_SIZE)
}
pub(crate) fn compact_form(bytes: &[u8]) -> Result<alloc::borrow::Cow<'_, [u8]>> {
let hdr = box_header_len(bytes)?;
if hdr == BOX_HEADER_MIN_SIZE {
return Ok(alloc::borrow::Cow::Borrowed(bytes));
}
let mut out = alloc::vec::Vec::with_capacity(bytes.len() - LARGESIZE_SIZE);
let compact_len = u32::try_from(bytes.len() - LARGESIZE_SIZE).unwrap_or(u32::MAX);
out.extend_from_slice(&compact_len.to_be_bytes());
out.extend_from_slice(&bytes[BOX_TYPE_RANGE]);
out.extend_from_slice(&bytes[hdr..]);
Ok(alloc::borrow::Cow::Owned(out))
}
pub(crate) fn box_body<'a>(
bytes: &'a [u8],
min_body: usize,
what: &'static str,
) -> Result<&'a [u8]> {
let hdr = box_header_len(bytes)?;
let body = &bytes[hdr..];
if body.len() < min_body {
return Err(Error::BufferTooShort(BufferTooShort {
need: hdr + min_body,
have: bytes.len(),
what,
}));
}
Ok(body)
}
pub(crate) fn child_extent(region: &[u8]) -> Option<usize> {
let hdr = box_header_len(region).ok()?;
let size = u32::from_be_bytes([region[0], region[1], region[2], region[3]]);
let total = if size == SIZE_INDICATES_LARGESIZE {
usize::try_from(u64::from_be_bytes(region[8..16].try_into().ok()?)).ok()?
} else if size == SIZE_TO_EOF {
region.len()
} else {
size as usize
};
(total >= hdr).then_some(total)
}
pub(crate) fn child_extent_in(region: &[u8]) -> Option<usize> {
child_extent(region).map(|total| total.min(region.len()))
}
pub(crate) fn box_header_len(bytes: &[u8]) -> Result<usize> {
need_in(bytes, BOX_HEADER_MIN_SIZE, "box header")?;
let size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
if size == SIZE_INDICATES_LARGESIZE {
let need = BOX_HEADER_MIN_SIZE + LARGESIZE_SIZE;
if bytes.len() < need {
return Err(Error::LargesizeBufferTooShort {
need,
have: bytes.len(),
});
}
Ok(need)
} else {
Ok(BOX_HEADER_MIN_SIZE)
}
}
impl<'a> Parse<'a> for BoxHeader {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
need_in(bytes, BOX_HEADER_MIN_SIZE, "BoxHeader")?;
let raw_size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let box_type = BoxType::from_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let mut cursor = BOX_HEADER_MIN_SIZE;
let size: u64 = if raw_size == SIZE_INDICATES_LARGESIZE {
if bytes.len() < cursor + LARGESIZE_SIZE {
return Err(Error::LargesizeBufferTooShort {
need: cursor + LARGESIZE_SIZE,
have: bytes.len(),
});
}
let v = u64::from_be_bytes([
bytes[cursor],
bytes[cursor + 1],
bytes[cursor + 2],
bytes[cursor + 3],
bytes[cursor + 4],
bytes[cursor + 5],
bytes[cursor + 6],
bytes[cursor + 7],
]);
cursor += LARGESIZE_SIZE;
v
} else {
raw_size as u64
};
if size != 0 && size < cursor as u64 {
return Err(Error::BoxSizeUnderflow {
size,
header_size: cursor,
});
}
let usertype = if box_type.is(b"uuid") {
if bytes.len() < cursor + UUID_TYPE_SIZE {
return Err(Error::UuidBufferTooShort {
need: cursor + UUID_TYPE_SIZE,
have: bytes.len(),
});
}
let mut ut = [0u8; UUID_TYPE_SIZE];
ut.copy_from_slice(&bytes[cursor..cursor + UUID_TYPE_SIZE]);
Some(ut)
} else {
None
};
let has_largesize = raw_size == SIZE_INDICATES_LARGESIZE;
Ok(Self {
size,
box_type,
usertype,
has_largesize,
})
}
}
impl Serialize for BoxHeader {
type Error = Error;
fn serialized_len(&self) -> usize {
self.header_size()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.header_size();
need_out(buf, need)?;
let mut cursor = 0usize;
if self.has_largesize {
buf[0..4].copy_from_slice(&SIZE_INDICATES_LARGESIZE.to_be_bytes());
cursor += 4;
buf[cursor..cursor + 4].copy_from_slice(&self.box_type.to_u32().to_be_bytes());
cursor += 4;
buf[cursor..cursor + 8].copy_from_slice(&self.size.to_be_bytes());
cursor += 8;
} else {
if self.size == SIZE_INDICATES_LARGESIZE as u64 {
return Err(Error::InvalidValue(InvalidValue {
field: "BoxHeader.size",
value: self.size,
reason: "size 1 signals a 64-bit largesize, but has_largesize is false",
}));
}
let size32 = u32::try_from(self.size).map_err(|_| {
Error::InvalidValue(InvalidValue {
field: "BoxHeader.size",
value: self.size,
reason: "size exceeds the 32-bit field and has_largesize is false",
})
})?;
buf[0..4].copy_from_slice(&size32.to_be_bytes());
cursor += 4;
buf[cursor..cursor + 4].copy_from_slice(&self.box_type.to_u32().to_be_bytes());
cursor += 4;
}
if self.box_type.is(b"uuid") {
let Some(ut) = &self.usertype else {
return Err(Error::InvalidValue(InvalidValue {
field: "BoxHeader.usertype",
value: 0,
reason: "a uuid box header requires a 16-byte usertype",
}));
};
buf[cursor..cursor + UUID_TYPE_SIZE].copy_from_slice(ut);
cursor += UUID_TYPE_SIZE;
}
Ok(cursor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FullBoxHeader {
pub box_header: BoxHeader,
pub version: u8,
pub flags: u32,
}
impl FullBoxHeader {
pub fn new(box_header: BoxHeader, version: u8, flags: u32) -> Self {
debug_assert!(flags <= 0xFFFFFF, "flags must fit in 24 bits");
Self {
box_header,
version,
flags,
}
}
}
impl<'a> Parse<'a> for FullBoxHeader {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let box_header = BoxHeader::parse(bytes)?;
let hdr_sz = box_header.header_size();
need_in(bytes, hdr_sz + FULLBOX_EXTRA_SIZE, "FullBoxHeader")?;
let version = bytes[hdr_sz];
let flags =
u32::from_be_bytes([0, bytes[hdr_sz + 1], bytes[hdr_sz + 2], bytes[hdr_sz + 3]]);
Ok(Self {
box_header,
version,
flags,
})
}
}
impl Serialize for FullBoxHeader {
type Error = Error;
fn serialized_len(&self) -> usize {
self.box_header.serialized_len() + FULLBOX_EXTRA_SIZE
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let base_len = self.box_header.serialized_len();
let need = base_len + FULLBOX_EXTRA_SIZE;
need_out(buf, need)?;
let cursor = self.box_header.serialize_into(buf)?;
buf[cursor] = self.version;
let flag_bytes = self.flags.to_be_bytes();
buf[cursor + 1] = flag_bytes[1];
buf[cursor + 2] = flag_bytes[2];
buf[cursor + 3] = flag_bytes[3];
Ok(cursor + FULLBOX_EXTRA_SIZE)
}
}
#[derive(Debug, Clone, Copy)]
pub struct BoxRef<'a> {
pub header: BoxHeader,
pub body: &'a [u8],
}
pub fn parse_box<'a>(bytes: &'a [u8]) -> Result<(BoxRef<'a>, usize)> {
let header = BoxHeader::parse(bytes)?;
let hdr_sz = header.header_size();
let body: &'a [u8] = if header.size == SIZE_TO_EOF as u64 {
&bytes[hdr_sz..]
} else {
let total = usize::try_from(header.size).map_err(|_| {
Error::BufferTooShort(BufferTooShort {
need: usize::MAX,
have: bytes.len(),
what: "BoxRef body",
})
})?;
if total < hdr_sz {
return Err(Error::BoxSizeUnderflow {
size: header.size,
header_size: hdr_sz,
});
}
need_in(bytes, total, "BoxRef body")?;
&bytes[hdr_sz..total]
};
let consumed = if header.size == 0 {
bytes.len()
} else {
hdr_sz + body.len()
};
Ok((BoxRef { header, body }, consumed))
}
#[derive(Debug, Clone)]
pub struct BoxIter<'a> {
remaining: &'a [u8],
}
impl<'a> BoxIter<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { remaining: data }
}
}
impl<'a> Iterator for BoxIter<'a> {
type Item = Result<(BoxRef<'a>, usize)>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining.is_empty() {
return None;
}
match parse_box(self.remaining) {
Ok((box_ref, consumed)) => {
self.remaining = &self.remaining[consumed.min(self.remaining.len())..];
Some(Ok((box_ref, consumed)))
}
Err(e) => {
self.remaining = &[];
Some(Err(e))
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ClampedBox {
pub header: BoxHeader,
pub start: usize,
pub end: usize,
}
impl ClampedBox {
pub fn payload_start(&self) -> usize {
self.start + self.header.header_size()
}
}
pub fn clamped_boxes(data: &[u8]) -> impl Iterator<Item = ClampedBox> + '_ {
let mut offset = 0usize;
core::iter::from_fn(move || {
let rest = data
.get(offset..)
.filter(|r| r.len() >= BOX_HEADER_MIN_SIZE)?;
let header = BoxHeader::parse(rest).ok()?;
let hdr_sz = header.header_size();
if hdr_sz > rest.len() {
return None;
}
let total = if header.size == u64::from(SIZE_TO_EOF) {
rest.len()
} else {
match usize::try_from(header.size) {
Ok(t) if t >= hdr_sz => t,
Ok(_) => return None,
Err(_) => rest.len(),
}
};
let step = total.min(rest.len());
if step == 0 {
return None;
}
let found = ClampedBox {
header,
start: offset,
end: offset + step,
};
offset += step;
Some(found)
})
}
pub fn box_iter(data: &[u8]) -> BoxIter<'_> {
BoxIter::new(data)
}
pub(crate) fn box_slices(data: &[u8]) -> impl Iterator<Item = &[u8]> {
let mut offset = 0usize;
box_iter(data).map_while(move |step| {
let (_, consumed) = step.ok()?;
let start = offset;
offset += consumed;
Some(&data[start..start + consumed])
})
}
pub(crate) fn find_top_box<'a>(data: &'a [u8], fourcc: &[u8; 4]) -> Option<&'a [u8]> {
box_slices(data).find(|b| b[BOX_TYPE_RANGE] == *fourcc)
}
#[cfg(feature = "cenc")]
fn container_body(container: &[u8]) -> &[u8] {
match box_header_len(container) {
Ok(hdr) => &container[hdr..],
Err(_) => &[],
}
}
#[cfg(feature = "cenc")]
pub(crate) fn child_boxes<'a>(
container: &'a [u8],
fourcc: &'a [u8; 4],
) -> impl Iterator<Item = &'a [u8]> {
box_slices(container_body(container)).filter(move |b| b[BOX_TYPE_RANGE] == *fourcc)
}
#[cfg(feature = "cenc")]
pub(crate) fn find_child_box<'a>(container: &'a [u8], fourcc: &[u8; 4]) -> Option<&'a [u8]> {
box_slices(container_body(container)).find(|b| b[BOX_TYPE_RANGE] == *fourcc)
}
#[cfg(feature = "cenc")]
pub(crate) fn descend_boxes<'a>(start: &'a [u8], path: &[&[u8; 4]]) -> Option<&'a [u8]> {
let mut cur = start;
for fourcc in path {
cur = find_child_box(cur, fourcc)?;
}
Some(cur)
}
#[cfg(test)]
mod tests {
use broadcast_common::error::{BufferTooShort, InvalidValue};
use super::*;
use broadcast_common::{Parse, Serialize};
#[test]
fn largesize_form_round_trips_even_when_size_fits_u32() {
let wire = [
0, 0, 0, 1, b'f', b't', b'y', b'p', 0, 0, 0, 0, 0, 0, 0, 16, ];
let header = BoxHeader::parse(&wire).unwrap();
assert_eq!(header.size, 16);
assert!(header.has_largesize);
assert_eq!(header.header_size(), 16);
assert_eq!(header.serialized_len(), 16);
let mut out = [0u8; 16];
assert_eq!(header.serialize_into(&mut out).unwrap(), 16);
assert_eq!(out, wire, "largesize form must re-serialize identically");
assert_eq!(BoxHeader::parse(&out).unwrap(), header);
}
#[test]
fn compact_size_beyond_u32_errors() {
let mut header = BoxHeader::new(16, BoxType::from_bytes(*b"ftyp"), None);
assert!(!header.has_largesize);
header.size = u32::MAX as u64 + 1;
let mut out = [0u8; BOX_HEADER_MIN_SIZE];
let err = header.serialize_into(&mut out).unwrap_err();
assert!(
matches!(
err,
Error::InvalidValue(InvalidValue {
field: "BoxHeader.size",
..
})
),
"expected InvalidValue for an oversized compact size, got {err:?}"
);
header.size = u32::MAX as u64;
assert_eq!(
header.serialize_into(&mut out).unwrap(),
BOX_HEADER_MIN_SIZE
);
assert_eq!(
u32::from_be_bytes([out[0], out[1], out[2], out[3]]),
u32::MAX
);
}
#[test]
fn compact_form_still_round_trips() {
let wire = [0, 0, 0, 16, b'm', b'o', b'o', b'f'];
let header = BoxHeader::parse(&wire).unwrap();
assert!(!header.has_largesize);
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE);
let mut out = [0u8; BOX_HEADER_MIN_SIZE];
assert_eq!(
header.serialize_into(&mut out).unwrap(),
BOX_HEADER_MIN_SIZE
);
assert_eq!(out, wire);
}
#[test]
fn uuid_header_without_usertype_errors() {
let header = BoxHeader::new(16 + 16, BoxType::from_bytes(*b"uuid"), None);
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE + UUID_TYPE_SIZE);
let mut out = [0u8; BOX_HEADER_MIN_SIZE + UUID_TYPE_SIZE];
let err = header.serialize_into(&mut out).unwrap_err();
assert!(
matches!(
err,
Error::InvalidValue(InvalidValue {
field: "BoxHeader.usertype",
..
})
),
"expected InvalidValue for the missing usertype, got {err:?}"
);
}
#[test]
fn size_one_without_largesize_errors() {
let mut header = BoxHeader::new(
SIZE_INDICATES_LARGESIZE as u64,
BoxType::from_bytes(*b"ftyp"),
None,
);
assert!(!header.has_largesize);
let mut out = [0u8; BOX_HEADER_MIN_SIZE];
let err = header.serialize_into(&mut out).unwrap_err();
assert!(
matches!(
err,
Error::InvalidValue(InvalidValue {
field: "BoxHeader.size",
..
})
),
"{err:?}"
);
header.size = 16;
assert_eq!(
header.serialize_into(&mut out).unwrap(),
BOX_HEADER_MIN_SIZE
);
}
#[test]
fn test_box_header_minimal() {
let bytes = [
0, 0, 0, 12, b'f', b't', b'y', b'p', 0, 0, 0, 0, ];
let header = BoxHeader::parse(&bytes).unwrap();
assert_eq!(header.size, 12);
assert!(header.box_type.is(b"ftyp"));
assert!(header.usertype.is_none());
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE);
let mut out = vec![0u8; header.serialized_len()];
header.serialize_into(&mut out).unwrap();
assert_eq!(&out, &bytes[..header.serialized_len()]);
}
#[test]
fn test_box_header_largesize() {
let largesize: u64 = 1_234_567_890_123;
let mut bytes = vec![0u8; 24]; bytes[0..4].copy_from_slice(&SIZE_INDICATES_LARGESIZE.to_be_bytes());
bytes[4..8].copy_from_slice(b"mdat");
bytes[8..16].copy_from_slice(&largesize.to_be_bytes());
let header = BoxHeader::parse(&bytes).unwrap();
assert_eq!(header.size, largesize);
assert!(header.box_type.is(b"mdat"));
assert!(header.usertype.is_none());
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE + LARGESIZE_SIZE);
let mut out = vec![0u8; header.serialized_len()];
header.serialize_into(&mut out).unwrap();
assert_eq!(&out, &bytes[..header.serialized_len()]);
}
#[test]
fn test_box_header_uuid() {
let usertype: [u8; 16] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let mut bytes = Vec::new();
let total_size: u32 = BOX_HEADER_MIN_SIZE as u32 + UUID_TYPE_SIZE as u32 + 12;
bytes.extend_from_slice(&total_size.to_be_bytes());
bytes.extend_from_slice(b"uuid");
bytes.extend_from_slice(&usertype);
bytes.resize(total_size as usize, 0xAB);
let header = BoxHeader::parse(&bytes).unwrap();
assert_eq!(header.size, total_size as u64);
assert!(header.box_type.is(b"uuid"));
assert_eq!(header.usertype, Some(usertype));
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE + UUID_TYPE_SIZE);
}
#[test]
fn test_box_header_too_short() {
let too_short = [0u8; 4];
let err = BoxHeader::parse(&too_short).unwrap_err();
assert!(matches!(
err,
Error::BufferTooShort(BufferTooShort { need: 8, .. })
));
}
#[test]
fn test_box_size_underflow() {
let bytes = [0, 0, 0, 4, b'f', b't', b'y', b'p'];
let err = BoxHeader::parse(&bytes).unwrap_err();
assert!(matches!(err, Error::BoxSizeUnderflow { size: 4, .. }));
}
#[test]
fn test_full_box_header() {
let mut bytes = vec![0u8; 16];
bytes[0..4].copy_from_slice(&16u32.to_be_bytes());
bytes[4..8].copy_from_slice(b"mvhd");
bytes[8] = 1; bytes[9..12].copy_from_slice(&[0, 0, 3]);
let fh = FullBoxHeader::parse(&bytes).unwrap();
assert!(fh.box_header.box_type.is(b"mvhd"));
assert_eq!(fh.version, 1);
assert_eq!(fh.flags, 3);
let mut out = vec![0u8; fh.serialized_len()];
fh.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes[..fh.serialized_len()]);
}
#[test]
fn test_parse_box() {
let mut bytes = vec![0u8; 40];
bytes[0..4].copy_from_slice(&20u32.to_be_bytes());
bytes[4..8].copy_from_slice(b"ftyp");
for (i, b) in bytes.iter_mut().enumerate().take(20).skip(8) {
*b = i as u8;
}
let (bx, consumed) = parse_box(&bytes).unwrap();
assert_eq!(bx.header.size, 20);
assert_eq!(bx.header.box_type.to_string(), "ftyp");
assert_eq!(bx.body.len(), 12);
assert_eq!(consumed, 20);
}
#[test]
fn test_box_iter_empty() {
let result: Vec<_> = box_iter(&[]).collect();
assert!(result.is_empty());
}
#[test]
fn test_box_iter_multiple() {
let mut data = Vec::new();
let b1_size = 16u32;
data.extend_from_slice(&b1_size.to_be_bytes());
data.extend_from_slice(b"ftyp");
data.resize(16, 0);
let b2_size = 24u32;
data.extend_from_slice(&b2_size.to_be_bytes());
data.extend_from_slice(b"moov");
data.resize(16 + 24, 0);
let b3_size = 32u32;
data.extend_from_slice(&b3_size.to_be_bytes());
data.extend_from_slice(b"mdat");
data.resize(16 + 24 + 32, 0);
let boxes: Vec<_> = box_iter(&data).collect::<Result<Vec<_>>>().unwrap();
assert_eq!(boxes.len(), 3);
assert!(boxes[0].0.header.box_type.is(b"ftyp"));
assert!(boxes[1].0.header.box_type.is(b"moov"));
assert!(boxes[2].0.header.box_type.is(b"mdat"));
}
#[test]
fn test_round_trip_known_boxes() {
let path = test_fixtures::path("mp4/h264-aac-frag.mp4");
let data = std::fs::read(path).expect("fixture file must exist");
let original_len = data.len();
let mut output = Vec::new();
for result in box_iter(&data) {
let (bx, _consumed) = result.unwrap();
let hdr_sz = bx.header.header_size();
let total = hdr_sz + bx.body.len();
let mut buf = vec![0u8; total];
bx.header.serialize_into(&mut buf).unwrap();
buf[hdr_sz..].copy_from_slice(bx.body);
output.extend_from_slice(&buf);
}
assert_eq!(
output.len(),
original_len,
"round-trip length mismatch: got {}, expected {}",
output.len(),
original_len
);
assert_eq!(output, data, "round-trip bytes differ from original");
}
#[test]
fn test_skip_unknown_box() {
let mut data = Vec::new();
data.extend_from_slice(&16u32.to_be_bytes());
data.extend_from_slice(b"ftyp");
data.resize(16, 0);
data.extend_from_slice(&24u32.to_be_bytes());
data.extend_from_slice(b"XXXX");
data.resize(16 + 24, 0xFF);
data.extend_from_slice(&20u32.to_be_bytes());
data.extend_from_slice(b"moov");
data.resize(16 + 24 + 20, 0xEE);
let boxes: Vec<_> = box_iter(&data).collect::<Result<Vec<_>>>().unwrap();
assert_eq!(
boxes.len(),
3,
"should parse all three boxes including unknown"
);
assert!(boxes[0].0.header.box_type.is(b"ftyp"));
assert!(
boxes[1].0.header.box_type.is(b"XXXX"),
"unknown box should parse by size"
);
assert!(boxes[2].0.header.box_type.is(b"moov"));
}
#[test]
fn test_field_mutation_changes_bytes() {
let body_content = [0xAA, 0xBB, 0xCC];
let correct_size = (BOX_HEADER_MIN_SIZE + body_content.len()) as u64;
let mut header = BoxHeader::new(correct_size, BoxType(*b"moov"), None);
let mut buf = vec![0u8; header.serialized_len() + body_content.len()];
let cursor = header.serialize_into(&mut buf).unwrap();
buf[cursor..cursor + body_content.len()].copy_from_slice(&body_content);
let orig_size_bytes = [buf[0], buf[1], buf[2], buf[3]];
header.size = correct_size + 100;
let new_len = header.serialized_len() + body_content.len() + 100;
let mut buf2 = vec![0u8; new_len];
let cursor2 = header.serialize_into(&mut buf2).unwrap();
buf2[cursor2..cursor2 + body_content.len()].copy_from_slice(&body_content);
let new_size_bytes = [buf2[0], buf2[1], buf2[2], buf2[3]];
assert_ne!(
orig_size_bytes, new_size_bytes,
"mutating size must change serialized bytes"
);
}
#[test]
fn test_box_size_zero_to_eof() {
let body = [0u8; 100];
let mut bytes = Vec::new();
bytes.extend_from_slice(&0u32.to_be_bytes()); bytes.extend_from_slice(b"mdat");
bytes.extend_from_slice(&body);
let header = BoxHeader::parse(&bytes).unwrap();
assert_eq!(header.size, 0);
assert!(header.box_type.is(b"mdat"));
assert_eq!(header.header_size(), BOX_HEADER_MIN_SIZE);
let (bx, _consumed) = parse_box(&bytes).unwrap();
assert_eq!(bx.body.len(), 100);
}
#[test]
fn test_container_with_variable_items() {
let mut data = Vec::new();
data.extend_from_slice(&16u32.to_be_bytes());
data.extend_from_slice(b"ftyp");
data.resize(16, 0);
data.extend_from_slice(&100u32.to_be_bytes());
data.extend_from_slice(b"free");
data.resize(16 + 100, 0xAB);
data.extend_from_slice(&32u32.to_be_bytes());
data.extend_from_slice(b"mdat");
data.resize(16 + 100 + 32, 0xCD);
let boxes: Vec<_> = box_iter(&data).collect::<Result<Vec<_>>>().unwrap();
assert_eq!(boxes.len(), 3);
assert!(boxes[0].0.header.box_type.is(b"ftyp"));
assert_eq!(boxes[0].0.header.size, 16);
assert!(boxes[1].0.header.box_type.is(b"free"));
assert_eq!(boxes[1].0.header.size, 100);
assert_eq!(boxes[1].0.body.len(), 92); assert!(boxes[2].0.header.box_type.is(b"mdat"));
assert_eq!(boxes[2].0.header.size, 32);
}
}
#[cfg(test)]
mod largesize_tests {
use broadcast_common::error::OutputBufferTooSmall;
use super::*;
use crate::{MovieBox, MovieFragmentBox};
use broadcast_common::{Parse, Serialize};
const FOUR_GIB: u64 = 1 << 32;
#[test]
fn encode_header_threshold_is_u32_max() {
let (hdr, len) = encode_box_header(b"stts", u64::from(u32::MAX));
assert_eq!(len, 8, "u32::MAX still fits the compact form");
assert_eq!(&hdr[..4], &u32::MAX.to_be_bytes());
assert_eq!(&hdr[4..8], b"stts");
let (hdr, len) = encode_box_header(b"stts", FOUR_GIB);
assert_eq!(len, 16, "one past u32::MAX needs largesize");
assert_eq!(&hdr[..4], &1u32.to_be_bytes(), "size = 1 marker");
assert_eq!(&hdr[4..8], b"stts");
assert_eq!(&hdr[8..16], &FOUR_GIB.to_be_bytes(), "largesize = total");
let five = 5 * (1u64 << 30);
let (hdr, len) = encode_box_header(b"moov", five);
assert_eq!(len, 16);
assert_eq!(&hdr[8..16], &five.to_be_bytes());
}
#[cfg(target_pointer_width = "64")]
#[test]
fn box_len_adds_the_largesize_field_only_past_u32_max() {
let max = u32::MAX as usize;
assert_eq!(box_len(max), max, "fits compact: unchanged");
assert_eq!(
box_len(max + 1),
max + 1 + LARGESIZE_SIZE,
"the 8 largesize bytes count toward the box"
);
for payload in [
0u64,
1000,
u64::from(u32::MAX) - 8,
u64::from(u32::MAX) - 7,
6 << 30,
] {
let h = BoxHeader::for_payload(BoxType::from_bytes(*b"mdat"), None, payload);
assert_eq!(
h.size,
box_len(BOX_HEADER_MIN_SIZE + payload as usize) as u64
);
}
}
#[cfg(target_pointer_width = "64")]
#[test]
fn write_box_header_emits_largesize_for_a_synthetic_5_gib_box() {
let need = 5usize << 30;
let mut buf = [0u8; 16];
let n = write_box_header(&mut buf, b"mdat", need).unwrap();
assert_eq!(n, 16);
assert_eq!(&buf[..4], &[0, 0, 0, 1]);
assert_eq!(&buf[4..8], b"mdat");
assert_eq!(
u64::from_be_bytes(buf[8..16].try_into().unwrap()),
need as u64
);
let parsed = BoxHeader::parse(&buf).unwrap();
assert_eq!(parsed.size, need as u64);
assert!(parsed.has_largesize());
assert_eq!(parsed.header_size(), n);
}
#[test]
fn write_box_header_rejects_a_too_small_buffer() {
let mut buf = [0u8; 12];
let err = write_box_header(&mut buf, b"stts", 1usize << 32).unwrap_err();
assert!(matches!(
err,
Error::OutputBufferTooSmall(OutputBufferTooSmall { need: 16, have: 12 })
));
}
fn fixture(name: &str) -> Vec<u8> {
test_fixtures::read(&format!("mp4/{name}"))
}
fn top_box<'a>(data: &'a [u8], fourcc: &[u8; 4]) -> &'a [u8] {
find_top_box(data, fourcc).expect("box present")
}
fn count_large(bytes: &[u8], fourcc: &[u8; 4]) -> (usize, usize) {
let (mut large, mut all) = (0, 0);
for i in 4..bytes.len().saturating_sub(4) {
if &bytes[i..i + 4] == fourcc {
all += 1;
if bytes[i - 4..i] == [0, 0, 0, 1] {
large += 1;
}
}
}
(large, all)
}
#[test]
fn moov_round_trips_through_the_largesize_form() {
let data = fixture("h264-aac-prog.mp4");
let orig = top_box(&data, b"moov");
let moov = MovieBox::parse(orig).unwrap();
let forced = {
let _g = ForceLargesize::on();
let len = moov.serialized_len();
let bytes = moov.try_to_bytes().unwrap();
assert_eq!(bytes.len(), len, "serialized_len agrees with the writer");
bytes
};
assert!(forced.len() > orig.len());
assert_eq!(&forced[..4], &[0, 0, 0, 1], "moov carries the marker");
assert_eq!(&forced[4..8], b"moov");
assert_eq!(
u64::from_be_bytes(forced[8..16].try_into().unwrap()),
forced.len() as u64,
"largesize is the whole box"
);
for cc in [
b"trak", b"mdia", b"minf", b"stbl", b"stsd", b"stts", b"stsc", b"stsz", b"stco",
b"dinf", b"dref", b"url ",
] {
let (large, all) = count_large(&forced, cc);
assert!(
all > 0 && large == all,
"{cc:?}: {large}/{all} use largesize"
);
}
let (hdlr_large, _) = count_large(&forced, b"hdlr");
assert!(hdlr_large >= 2, "typed hdlr boxes use largesize");
let reparsed = MovieBox::parse(&forced).expect("largesize moov parses");
assert_eq!(reparsed, moov, "typed tree identical after largesize trip");
assert_eq!(
reparsed.try_to_bytes().unwrap(),
orig,
"compact re-serialize is byte-identical"
);
}
#[test]
fn moof_round_trips_through_the_largesize_form() {
let data = fixture("h264-aac-frag.mp4");
let orig = top_box(&data, b"moof");
let moof = MovieFragmentBox::parse_body(&orig[box_header_len(orig).unwrap()..]).unwrap();
let forced = {
let _g = ForceLargesize::on();
moof.try_to_bytes().unwrap()
};
assert_eq!(&forced[..4], &[0, 0, 0, 1]);
for cc in [b"traf", b"trun"] {
let (large, all) = count_large(&forced, cc);
assert!(all > 0 && large == all, "{cc:?}: {large}/{all}");
}
let reparsed = MovieFragmentBox::parse_body(&forced[box_header_len(&forced).unwrap()..])
.expect("largesize moof parses");
assert_eq!(reparsed, moof);
assert_eq!(reparsed.try_to_bytes().unwrap(), orig);
}
fn read_path(rel: &str) -> Vec<u8> {
let rel = rel.trim_start_matches("../fixtures/");
let path = test_fixtures::path(rel);
std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
}
#[test]
fn every_fixture_moov_round_trips_through_the_largesize_form() {
let mut checked = 0;
for rel in [
"../fixtures/mp4/h264-aac-prog.mp4",
"../fixtures/mp4/h264-aac-frag.mp4",
"../fixtures/mp4/h264-cenc.mp4",
"../fixtures/mp4/h264-cbcs.mp4",
"../fixtures/mp4/hevc-frag.mp4",
"../fixtures/mp4/cmaf-av-subtitle/av-frag.mp4",
"../fixtures/mp4/cmaf-av-subtitle/av-subtitle-frag.mp4",
"../fixtures/mp4/cmaf-av-subtitle/av-aac-subtitle-frag.mp4",
"../fixtures/mp4/cenc-boxes/aac-cenc.mp4",
"../fixtures/mp4/cenc-boxes/pcm-clear.mp4",
"../fixtures/mp4/cenc-boxes/v1-mvhd.mp4",
] {
let data = read_path(rel);
let Some(orig) = find_top_box(&data, b"moov") else {
continue;
};
checked += 1;
let moov = MovieBox::parse(orig).unwrap_or_else(|e| panic!("{rel}: {e}"));
let forced = {
let _g = ForceLargesize::on();
moov.try_to_bytes().unwrap()
};
assert_eq!(&forced[..4], &[0, 0, 0, 1], "{rel}");
let reparsed =
MovieBox::parse(&forced).unwrap_or_else(|e| panic!("{rel}: largesize: {e}"));
assert_eq!(reparsed, moov, "{rel}: tree changed through largesize");
assert_eq!(
reparsed.try_to_bytes().unwrap(),
orig,
"{rel}: not byte-identical"
);
}
assert!(
checked >= 8,
"fixtures went missing: only {checked} checked"
);
}
#[test]
fn every_fixture_moof_round_trips_through_the_largesize_form() {
let mut checked = 0;
for rel in [
"../fixtures/mp4/h264-aac-frag.mp4",
"../fixtures/mp4/h264-cenc.mp4",
"../fixtures/mp4/h264-cbcs.mp4",
"../fixtures/mp4/hevc-frag.mp4",
"../fixtures/mp4/cmaf-av-subtitle/av-frag.mp4",
"../fixtures/mp4/cmaf-av-subtitle/av-subtitle-frag.mp4",
] {
let data = read_path(rel);
let Some(orig) = find_top_box(&data, b"moof") else {
continue;
};
checked += 1;
let hdr = box_header_len(orig).unwrap();
let moof = MovieFragmentBox::parse_body(&orig[hdr..]).unwrap();
let forced = {
let _g = ForceLargesize::on();
moof.try_to_bytes().unwrap()
};
let body = &forced[box_header_len(&forced).unwrap()..];
let reparsed = MovieFragmentBox::parse_body(body)
.unwrap_or_else(|e| panic!("{rel}: largesize: {e}"));
assert_eq!(reparsed, moof, "{rel}");
assert_eq!(reparsed.try_to_bytes().unwrap(), orig, "{rel}");
}
assert!(
checked >= 4,
"fixtures went missing: only {checked} checked"
);
}
}