Skip to main content

mp4_atom/
atom.rs

1use std::io::Read;
2
3use crate::*;
4
5/// A helper to encode/decode a known atom type.
6pub trait Atom: Sized {
7    const KIND: FourCC;
8
9    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self>;
10    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()>;
11
12    /// Either logs or returns an error depending on the environment/flag.
13    fn decode_unknown(atom: &crate::Any) -> Result<()> {
14        crate::decode_unknown(atom, Self::KIND)
15    }
16}
17
18impl<T: Atom> Encode for T {
19    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
20        // TODO support sizes larger than u32 (4GB)
21        buf.encode_atom(Self::KIND, |buf| self.encode_body(buf))
22    }
23}
24
25impl<T: Atom> Decode for T {
26    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
27        Self::decode_maybe(buf)?.ok_or(Error::OutOfBounds)
28    }
29}
30
31impl<T: Atom> DecodeMaybe for T {
32    fn decode_maybe<B: Buf>(buf: &mut B) -> Result<Option<Self>> {
33        // Decode the header from a view so an incomplete atom leaves the
34        // caller's buffer untouched.
35        let remaining = buf.remaining();
36        let mut peek = buf.slice(remaining);
37        let header = match Header::decode_maybe(&mut peek)? {
38            Some(header) => header,
39            None => return Ok(None),
40        };
41
42        if header.kind != T::KIND {
43            return Err(Error::UnexpectedBox(header.kind));
44        }
45
46        let size = header.size.unwrap_or(peek.remaining());
47        if size > peek.remaining() {
48            return Ok(None);
49        }
50
51        buf.advance(remaining - peek.remaining());
52        let body = &mut buf.slice(size);
53
54        let atom = match Self::decode_body(body) {
55            Ok(atom) => atom,
56            Err(Error::OutOfBounds) => return Err(Error::OverDecode(T::KIND)),
57            Err(Error::ShortRead) => return Err(Error::UnderDecode(T::KIND)),
58            Err(err) => return Err(err),
59        };
60
61        if body.has_remaining() {
62            return Err(Error::UnderDecode(T::KIND));
63        }
64
65        buf.advance(size);
66
67        Ok(Some(atom))
68    }
69}
70
71impl<T: Atom> ReadFrom for T {
72    fn read_from<R: Read + ?Sized>(r: &mut R) -> Result<Self> {
73        <Option<T> as ReadFrom>::read_from(r)?.ok_or(Error::MissingBox(T::KIND))
74    }
75}
76
77impl<T: Atom> ReadFrom for Option<T> {
78    fn read_from<R: Read + ?Sized>(r: &mut R) -> Result<Self> {
79        let header = match <Option<Header> as ReadFrom>::read_from(r)? {
80            Some(header) => header,
81            None => return Ok(None),
82        };
83
84        if header.kind != T::KIND {
85            return Err(Error::UnexpectedBox(header.kind));
86        }
87
88        let body = &mut header.read_body(r)?;
89
90        let atom = match T::decode_body(body) {
91            Ok(atom) => atom,
92            Err(Error::OutOfBounds) => return Err(Error::OverDecode(T::KIND)),
93            Err(Error::ShortRead) => return Err(Error::UnderDecode(T::KIND)),
94            Err(err) => return Err(err),
95        };
96
97        if body.has_remaining() {
98            return Err(Error::UnderDecode(T::KIND));
99        }
100
101        Ok(Some(atom))
102    }
103}
104
105impl<T: Atom> ReadUntil for T {
106    fn read_until<R: Read + ?Sized>(r: &mut R) -> Result<Self> {
107        <Option<T> as ReadUntil>::read_until(r)?.ok_or(Error::MissingBox(T::KIND))
108    }
109}
110
111impl<T: Atom> ReadUntil for Option<T> {
112    fn read_until<R: Read + ?Sized>(r: &mut R) -> Result<Self> {
113        while let Some(header) = <Option<Header> as ReadFrom>::read_from(r)? {
114            if header.kind == T::KIND {
115                let body = &mut header.read_body(r)?;
116                return Ok(Some(T::decode_atom(&header, body)?));
117            }
118        }
119
120        Ok(None)
121    }
122}
123
124impl<T: Atom> DecodeAtom for T {
125    fn decode_atom<B: Buf>(header: &Header, buf: &mut B) -> Result<T> {
126        if header.kind != T::KIND {
127            return Err(Error::UnexpectedBox(header.kind));
128        }
129
130        let size = header.size.unwrap_or(buf.remaining());
131        if size > buf.remaining() {
132            return Err(Error::OutOfBounds);
133        }
134
135        let body = &mut buf.slice(size);
136
137        let atom = match T::decode_body(body) {
138            Ok(atom) => atom,
139            Err(Error::OutOfBounds) => return Err(Error::OverDecode(T::KIND)),
140            Err(Error::ShortRead) => return Err(Error::UnderDecode(T::KIND)),
141            Err(err) => return Err(err),
142        };
143
144        if body.has_remaining() {
145            return Err(Error::UnderDecode(T::KIND));
146        }
147
148        buf.advance(size);
149
150        Ok(atom)
151    }
152}
153
154impl<T: Atom> ReadAtom for T {
155    fn read_atom<R: Read + ?Sized>(header: &Header, r: &mut R) -> Result<Self> {
156        if header.kind != T::KIND {
157            return Err(Error::UnexpectedBox(header.kind));
158        }
159
160        let body = &mut header.read_body(r)?;
161        Self::decode_atom(header, body)
162    }
163}
164
165// A helper for generating nested atoms.
166/* example:
167nested! {
168    required: [ Mvhd ],
169    optional: [ Meta, Mvex, Udta ],
170    multiple: [ Trak ],
171};
172*/
173
174macro_rules! nested {
175    (required: [$($required:ident),*$(,)?], optional: [$($optional:ident),*$(,)?], multiple: [$($multiple:ident),*$(,)?],) => {
176        pastey::paste! {
177            fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
178                $( let mut [<$required:lower>] = None;)*
179                $( let mut [<$optional:lower>] = None;)*
180                $( let mut [<$multiple:lower>] = Vec::new();)*
181
182                while let Some(atom) = Any::decode_maybe(buf)? {
183                    match atom {
184                        $(Any::$required(atom) => {
185                            if [<$required:lower>].is_some() {
186                                return Err(Error::DuplicateBox($required::KIND));
187                            }
188                            [<$required:lower>] = Some(atom);
189                        },)*
190                        $(Any::$optional(atom) => {
191                            if [<$optional:lower>].is_some() {
192                                return Err(Error::DuplicateBox($optional::KIND));
193                            }
194                            [<$optional:lower>] = Some(atom);
195                        },)*
196                        $(Any::$multiple(atom) => {
197                            [<$multiple:lower>].push(atom.into());
198                        },)*
199                        Any::Skip(atom) => tracing::debug!(size = atom.zeroed.size, "skipping skip box"),
200                        Any::Free(atom) => tracing::debug!(size = atom.zeroed.size, "skipping free box"),
201                        unknown => Self::decode_unknown(&unknown)?,
202                    }
203                }
204                // Tolerate a sub-header padding remainder (e.g. a QuickTime zero
205                // terminator) after the child boxes.
206                skip_trailing_padding(buf);
207
208                Ok(Self {
209                    $([<$required:lower>]: [<$required:lower>].ok_or(Error::MissingBox($required::KIND))? ,)*
210                    $([<$optional:lower>],)*
211                    $([<$multiple:lower>],)*
212                })
213            }
214
215            fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
216                $( self.[<$required:lower>].encode(buf)?; )*
217                $( self.[<$optional:lower>].encode(buf)?; )*
218                $( self.[<$multiple:lower>].iter().map(|x| x.encode(buf)).collect::<Result<()>>()?; )*
219
220                Ok(())
221            }
222        }
223    };
224}
225
226pub(crate) use nested;
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use std::io::Cursor;
232
233    const FTYP_BODY_TAGGED_AS_MOOV: &[u8] = b"\0\0\0\x14mooviso6\0\0\x02\0mp41";
234
235    #[test]
236    fn typed_decode_rejects_unexpected_box() {
237        let err = Ftyp::decode(&mut Cursor::new(FTYP_BODY_TAGGED_AS_MOOV)).unwrap_err();
238        assert!(matches!(err, Error::UnexpectedBox(kind) if kind == Moov::KIND));
239    }
240
241    #[test]
242    fn typed_read_from_rejects_unexpected_box() {
243        let err =
244            <Ftyp as ReadFrom>::read_from(&mut Cursor::new(FTYP_BODY_TAGGED_AS_MOOV)).unwrap_err();
245        assert!(matches!(err, Error::UnexpectedBox(kind) if kind == Moov::KIND));
246    }
247}