1use crate::bytes::ReadAt;
4use crate::{Error, Result};
5
6#[derive(Clone, Copy)]
8pub struct BmffBox<'a> {
9 pub typ: [u8; 4],
10 pub offset: usize,
12 pub data: &'a [u8],
14 pub data_offset: usize,
16 pub uuid: Option<[u8; 16]>,
17}
18
19impl std::fmt::Debug for BmffBox<'_> {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "Box({}, off={}, len={})", String::from_utf8_lossy(&self.typ), self.offset, self.data.len())
22 }
23}
24
25impl<'a> BmffBox<'a> {
26 pub fn is(&self, t: &[u8; 4]) -> bool {
27 &self.typ == t
28 }
29 pub fn children(&self, skip: usize) -> BoxIter<'a> {
32 BoxIter { data: self.data, pos: skip.min(self.data.len()), base: self.data_offset, failed: false }
33 }
34 pub fn child(&self, skip: usize, t: &[u8; 4]) -> Option<BmffBox<'a>> {
36 self.children(skip).filter_map(|b| b.ok()).find(|b| b.is(t))
37 }
38 pub fn child_uuid(&self, skip: usize, uuid: &[u8; 16]) -> Option<BmffBox<'a>> {
40 self.children(skip).filter_map(|b| b.ok()).find(|b| b.uuid.as_ref() == Some(uuid))
41 }
42}
43
44pub struct BoxIter<'a> {
46 data: &'a [u8],
47 pos: usize,
48 base: usize,
49 failed: bool,
50}
51
52pub fn boxes(data: &[u8]) -> BoxIter<'_> {
54 BoxIter { data, pos: 0, base: 0, failed: false }
55}
56
57impl<'a> Iterator for BoxIter<'a> {
58 type Item = Result<BmffBox<'a>>;
59 fn next(&mut self) -> Option<Self::Item> {
60 if self.failed || self.pos + 8 > self.data.len() {
61 return None;
62 }
63 let r = parse_box(self.data, self.pos, self.base);
64 match &r {
65 Ok((_, next)) => self.pos = *next,
66 Err(_) => self.failed = true,
67 }
68 Some(r.map(|(b, _)| b))
69 }
70}
71
72fn parse_box<'a>(data: &'a [u8], pos: usize, base: usize) -> Result<(BmffBox<'a>, usize)> {
73 let size32 = data.u32be(pos)? as u64;
74 let mut typ = [0u8; 4];
75 typ.copy_from_slice(&data[pos + 4..pos + 8]);
76 let mut hdr = 8usize;
77 let size = match size32 {
78 0 => (data.len() - pos) as u64,
79 1 => {
80 hdr = 16;
81 data.u64_at(crate::ByteOrder::Big, pos + 8)?
82 }
83 s => s,
84 };
85 let mut uuid = None;
86 if &typ == b"uuid" {
87 let u = data.get(pos + hdr..pos + hdr + 16).ok_or_else(|| Error::corrupt("truncated BMFF uuid box"))?;
88 let mut a = [0u8; 16];
89 a.copy_from_slice(u);
90 uuid = Some(a);
91 hdr += 16;
92 }
93 let end = (pos as u64).checked_add(size).filter(|&e| size >= hdr as u64 && e <= data.len() as u64);
94 let Some(end) = end else {
95 return Err(Error::corrupt_at(format!("invalid BMFF box size {size}"), (base + pos) as u64));
96 };
97 let end = end as usize;
98 let b = BmffBox { typ, offset: base + pos, data: &data[pos + hdr..end], data_offset: base + pos + hdr, uuid };
99 Ok((b, end))
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn mk(t: &[u8; 4], payload: &[u8]) -> Vec<u8> {
107 let mut v = ((payload.len() + 8) as u32).to_be_bytes().to_vec();
108 v.extend_from_slice(t);
109 v.extend_from_slice(payload);
110 v
111 }
112
113 #[test]
114 fn nested_boxes() {
115 let inner = mk(b"abcd", &[1, 2, 3]);
116 let outer = mk(b"moov", &inner);
117 let mut file = mk(b"ftyp", b"crx ");
118 file.extend(&outer);
119 let top: Vec<_> = boxes(&file).map(|b| b.unwrap()).collect();
120 assert_eq!(top.len(), 2);
121 assert!(top[1].is(b"moov"));
122 let c = top[1].child(0, b"abcd").unwrap();
123 assert_eq!(c.data, &[1, 2, 3]);
124 assert_eq!(c.data_offset, file.len() - 3);
125 }
126
127 #[test]
128 fn bad_sizes() {
129 let mut b = mk(b"moov", &[0; 4]);
130 b[3] = 200; assert!(boxes(&b).next().unwrap().is_err());
132 let mut b = mk(b"moov", &[0; 4]);
133 b[3] = 3; assert!(boxes(&b).next().unwrap().is_err());
135 }
136}