Skip to main content

flashraw_core/
bmff.rs

1//! ISO Base Media File Format (ISO/IEC 14496-12) box parsing, used by CR3.
2
3use crate::bytes::ReadAt;
4use crate::{Error, Result};
5
6/// A parsed box header with its payload slice.
7#[derive(Clone, Copy)]
8pub struct BmffBox<'a> {
9    pub typ: [u8; 4],
10    /// Absolute offset of the box start in the root slice.
11    pub offset: usize,
12    /// Payload (after the size/type/uuid header).
13    pub data: &'a [u8],
14    /// Absolute offset of `data` in the root slice.
15    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    /// Iterate child boxes starting `skip` bytes into the payload (e.g. 4 for full boxes,
30    /// 8 for `stsd`).
31    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    /// First child of type `t`.
35    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    /// Child by uuid.
39    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
44/// Iterator over sibling boxes in a byte range.
45pub struct BoxIter<'a> {
46    data: &'a [u8],
47    pos: usize,
48    base: usize,
49    failed: bool,
50}
51
52/// Iterate the top-level boxes of a file.
53pub 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; // size beyond data
131        assert!(boxes(&b).next().unwrap().is_err());
132        let mut b = mk(b"moov", &[0; 4]);
133        b[3] = 3; // smaller than header
134        assert!(boxes(&b).next().unwrap().is_err());
135    }
136}