use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::Serialize;
pub const FTYP: [u8; 4] = *b"ftyp";
pub const STYP: [u8; 4] = *b"styp";
pub const MDAT: [u8; 4] = *b"mdat";
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct BrandBox {
major_brand: [u8; 4],
minor_version: u32,
compatible_brands: Vec<[u8; 4]>,
}
impl BrandBox {
fn body_len(&self) -> usize {
4 + 4 + self.compatible_brands.len() * 4
}
fn parse_body(body: &[u8]) -> Result<Self> {
if body.len() < 8 {
return Err(Error::BufferTooShort {
need: 8,
have: body.len(),
what: "ftyp/styp body",
});
}
let major_brand = [body[0], body[1], body[2], body[3]];
let minor_version = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
let mut compatible_brands = Vec::new();
let mut off = 8;
while off + 4 <= body.len() {
compatible_brands.push([body[off], body[off + 1], body[off + 2], body[off + 3]]);
off += 4;
}
Ok(Self {
major_brand,
minor_version,
compatible_brands,
})
}
fn serialize_into(&self, four_cc: &[u8; 4], buf: &mut [u8]) -> Result<usize> {
let need = 8 + self.body_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
buf[0..4].copy_from_slice(&(need as u32).to_be_bytes());
buf[4..8].copy_from_slice(four_cc);
buf[8..12].copy_from_slice(&self.major_brand);
buf[12..16].copy_from_slice(&self.minor_version.to_be_bytes());
let mut off = 16;
for brand in &self.compatible_brands {
buf[off..off + 4].copy_from_slice(brand);
off += 4;
}
Ok(need)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileTypeBox {
pub major_brand: [u8; 4],
pub minor_version: u32,
pub compatible_brands: Vec<[u8; 4]>,
}
impl FileTypeBox {
pub fn parse_box(bytes: &[u8]) -> Result<Self> {
let b = BrandBox::parse_body(box_body(bytes, &FTYP)?)?;
Ok(Self {
major_brand: b.major_brand,
minor_version: b.minor_version,
compatible_brands: b.compatible_brands,
})
}
fn brand(&self) -> BrandBox {
BrandBox {
major_brand: self.major_brand,
minor_version: self.minor_version,
compatible_brands: self.compatible_brands.clone(),
}
}
}
impl Serialize for FileTypeBox {
type Error = Error;
fn serialized_len(&self) -> usize {
8 + self.brand().body_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
self.brand().serialize_into(&FTYP, buf)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SegmentTypeBox {
pub major_brand: [u8; 4],
pub minor_version: u32,
pub compatible_brands: Vec<[u8; 4]>,
}
impl SegmentTypeBox {
pub fn parse_box(bytes: &[u8]) -> Result<Self> {
let b = BrandBox::parse_body(box_body(bytes, &STYP)?)?;
Ok(Self {
major_brand: b.major_brand,
minor_version: b.minor_version,
compatible_brands: b.compatible_brands,
})
}
fn brand(&self) -> BrandBox {
BrandBox {
major_brand: self.major_brand,
minor_version: self.minor_version,
compatible_brands: self.compatible_brands.clone(),
}
}
}
impl Serialize for SegmentTypeBox {
type Error = Error;
fn serialized_len(&self) -> usize {
8 + self.brand().body_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
self.brand().serialize_into(&STYP, buf)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MediaDataBox {
pub data: Vec<u8>,
}
impl MediaDataBox {
fn needs_large(&self) -> bool {
8 + self.data.len() > u32::MAX as usize
}
pub fn parse_box(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 8 {
return Err(Error::BufferTooShort {
need: 8,
have: bytes.len(),
what: "mdat",
});
}
if bytes[4..8] != MDAT {
return Err(Error::UnexpectedBox { expected: "mdat" });
}
let size32 = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let (size, hdr) = if size32 == 1 {
if bytes.len() < 16 {
return Err(Error::BufferTooShort {
need: 16,
have: bytes.len(),
what: "mdat largesize",
});
}
(
u64::from_be_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
bytes[15],
]) as usize,
16usize,
)
} else if size32 == 0 {
(bytes.len(), 8usize)
} else {
(size32 as usize, 8usize)
};
if size < hdr || size > bytes.len() {
return Err(Error::BufferTooShort {
need: size,
have: bytes.len(),
what: "mdat payload",
});
}
Ok(Self {
data: bytes[hdr..size].to_vec(),
})
}
}
impl Serialize for MediaDataBox {
type Error = Error;
fn serialized_len(&self) -> usize {
if self.needs_large() {
16 + self.data.len()
} else {
8 + self.data.len()
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let hdr = if self.needs_large() {
buf[0..4].copy_from_slice(&1u32.to_be_bytes());
buf[4..8].copy_from_slice(&MDAT);
buf[8..16].copy_from_slice(&(need as u64).to_be_bytes());
16
} else {
buf[0..4].copy_from_slice(&(need as u32).to_be_bytes());
buf[4..8].copy_from_slice(&MDAT);
8
};
buf[hdr..hdr + self.data.len()].copy_from_slice(&self.data);
Ok(need)
}
}
fn box_body<'a>(bytes: &'a [u8], four_cc: &[u8; 4]) -> Result<&'a [u8]> {
if bytes.len() < 8 {
return Err(Error::BufferTooShort {
need: 8,
have: bytes.len(),
what: "box header",
});
}
if bytes[4..8] != *four_cc {
return Err(Error::UnexpectedBox {
expected: "ftyp/styp",
});
}
let size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if size < 8 || size > bytes.len() {
return Err(Error::BufferTooShort {
need: size,
have: bytes.len(),
what: "box size",
});
}
Ok(&bytes[8..size])
}
#[cfg(test)]
mod tests {
use super::*;
use broadcast_common::Serialize;
#[test]
fn ftyp_round_trip() {
let bytes = [
0x00, 0x00, 0x00, 0x1c, b'f', b't', b'y', b'p', b'i', b's', b'o', b'5', 0x00, 0x00,
0x02, 0x00, b'i', b's', b'o', b'5', b'i', b's', b'o', b'6', b'm', b'p', b'4', b'1',
];
let f = FileTypeBox::parse_box(&bytes).unwrap();
assert_eq!(&f.major_brand, b"iso5");
assert_eq!(f.minor_version, 512);
assert_eq!(f.compatible_brands.len(), 3);
assert_eq!(&f.compatible_brands[2], b"mp41");
let mut out = alloc::vec![0u8; f.serialized_len()];
let n = f.serialize_into(&mut out).unwrap();
assert_eq!(&out[..n], &bytes, "ftyp round-trip byte-identical");
}
#[test]
fn styp_round_trip() {
let f = SegmentTypeBox {
major_brand: *b"msdh",
minor_version: 0,
compatible_brands: alloc::vec![*b"msdh", *b"msix"],
};
let mut out = alloc::vec![0u8; f.serialized_len()];
let n = f.serialize_into(&mut out).unwrap();
assert_eq!(&out[4..8], b"styp");
let g = SegmentTypeBox::parse_box(&out[..n]).unwrap();
assert_eq!(f, g);
}
#[test]
fn mdat_round_trip() {
let m = MediaDataBox {
data: alloc::vec![1, 2, 3, 4, 5],
};
let mut out = alloc::vec![0u8; m.serialized_len()];
let n = m.serialize_into(&mut out).unwrap();
assert_eq!(n, 13);
assert_eq!(&out[4..8], b"mdat");
let g = MediaDataBox::parse_box(&out[..n]).unwrap();
assert_eq!(m, g);
}
#[test]
fn mdat_mutation_changes_bytes() {
let m = MediaDataBox {
data: alloc::vec![1, 2, 3],
};
let mut a = alloc::vec![0u8; m.serialized_len()];
m.serialize_into(&mut a).unwrap();
let m2 = MediaDataBox {
data: alloc::vec![1, 2, 4],
};
let mut b = alloc::vec![0u8; m2.serialized_len()];
m2.serialize_into(&mut b).unwrap();
assert_ne!(a, b);
}
}