Skip to main content

mp4_atom/meta/
iloc.rs

1use crate::*;
2
3// ItemInformationBox. ISO/IEC 14496-12:2022 Section 8.11.3
4// This is used to work out where the items are
5
6ext! {
7    name: Iloc,
8    versions: [0, 1, 2],
9    flags: {}
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct ItemLocationExtent {
15    pub item_reference_index: u64,
16    pub offset: u64,
17    pub length: u64,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct ItemLocation {
23    pub item_id: u32,
24    pub construction_method: u8, // enum?
25    pub data_reference_index: u16,
26    pub base_offset: u64,
27    pub extents: Vec<ItemLocationExtent>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Iloc {
33    pub item_locations: Vec<ItemLocation>,
34}
35
36impl AtomExt for Iloc {
37    type Ext = IlocExt;
38
39    const KIND_EXT: FourCC = FourCC::new(b"iloc");
40
41    fn decode_body_ext<B: Buf>(buf: &mut B, ext: IlocExt) -> Result<Self> {
42        let sizes0 = u8::decode(buf)?;
43        let offset_size = sizes0 >> 4;
44        let length_size = sizes0 & 0x0F;
45        let sizes1 = u8::decode(buf)?;
46        let base_offset_size = sizes1 >> 4;
47        let index_size: u8 = if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
48            sizes1 & 0x0F
49        } else {
50            0
51        };
52
53        let item_count = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
54            u16::decode(buf)? as usize
55        } else {
56            u32::decode(buf)? as usize
57        };
58        let mut item_locations = vec![];
59        for _i in 0..item_count {
60            let item_id = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
61                u16::decode(buf)? as u32
62            } else {
63                u32::decode(buf)?
64            };
65            let construction_method: u8 =
66                if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
67                    let construction_method_packed = u16::decode(buf)?;
68                    (construction_method_packed & 0x0f) as u8
69                } else {
70                    0
71                };
72            let data_reference_index = u16::decode(buf)?;
73            let base_offset = match base_offset_size {
74                0 => 0u64,
75                4 => u32::decode(buf)? as u64,
76                8 => u64::decode(buf)?,
77                _ => return Err(Error::Reserved),
78            };
79            let extent_count = u16::decode(buf)?;
80            let mut extents = vec![];
81            for _j in 0..extent_count {
82                let item_reference_index: u64 =
83                    if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
84                        match index_size {
85                            0 => 0,
86                            4 => u32::decode(buf)? as u64,
87                            8 => u64::decode(buf)?,
88                            _ => return Err(Error::Reserved),
89                        }
90                    } else {
91                        0
92                    };
93                let extent_offset = match offset_size {
94                    0 => 0u64,
95                    4 => u32::decode(buf)? as u64,
96                    8 => u64::decode(buf)?,
97                    _ => return Err(Error::Reserved),
98                };
99                let extent_length = match length_size {
100                    0 => 0u64,
101                    4 => u32::decode(buf)? as u64,
102                    8 => u64::decode(buf)?,
103                    _ => return Err(Error::Reserved),
104                };
105                extents.push(ItemLocationExtent {
106                    item_reference_index,
107                    offset: extent_offset,
108                    length: extent_length,
109                });
110            }
111            item_locations.push(ItemLocation {
112                item_id,
113                construction_method,
114                data_reference_index,
115                base_offset,
116                extents,
117            })
118        }
119        Ok(Iloc { item_locations })
120    }
121
122    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IlocExt> {
123        let mut base_offset_size = 0u8;
124        // TODO: work out which version and sizes we really need for this instance.
125        for item_location in &self.item_locations {
126            if item_location.base_offset > 0 {
127                if item_location.base_offset > u32::MAX as u64 {
128                    base_offset_size = 8u8;
129                } else if base_offset_size != 8 {
130                    base_offset_size = 4u8;
131                }
132            }
133        }
134        let version = IlocVersion::V0;
135        let offset_size = 4u8;
136        let length_size = 4u8;
137
138        let index_size = 0u8;
139        let size0 = (offset_size << 4) | length_size;
140        let size1 = (base_offset_size << 4) | index_size;
141        size0.encode(buf)?;
142        size1.encode(buf)?;
143        if version == IlocVersion::V0 || version == IlocVersion::V1 {
144            (self.item_locations.len() as u16).encode(buf)?;
145        } else {
146            (self.item_locations.len() as u32).encode(buf)?;
147        }
148        for item_location in &self.item_locations {
149            if version == IlocVersion::V0 || version == IlocVersion::V1 {
150                (item_location.item_id as u16).encode(buf)?;
151            } else {
152                item_location.item_id.encode(buf)?;
153            }
154            if version == IlocVersion::V1 || version == IlocVersion::V2 {
155                (item_location.construction_method as u16).encode(buf)?
156            }
157            item_location.data_reference_index.encode(buf)?;
158            match base_offset_size {
159                0 => {}
160                4 => (item_location.base_offset as u32).encode(buf)?,
161                8 => item_location.base_offset.encode(buf)?,
162                _ => unreachable!("iloc base_offset_size must be in [0,4,8]"),
163            }
164            (item_location.extents.len() as u16).encode(buf)?;
165            for extent in &item_location.extents {
166                match index_size {
167                    0 => {}
168                    4 => (extent.item_reference_index as u32).encode(buf)?,
169                    8 => extent.item_reference_index.encode(buf)?,
170                    _ => unreachable!("iloc index_size must be in [0,4,8]"),
171                }
172                match offset_size {
173                    0 => {}
174                    4 => (extent.offset as u32).encode(buf)?,
175                    8 => extent.offset.encode(buf)?,
176                    _ => unreachable!("iloc offset_size must be in [0,4,8]"),
177                }
178                match length_size {
179                    0 => {}
180                    4 => (extent.length as u32).encode(buf)?,
181                    8 => extent.length.encode(buf)?,
182                    _ => unreachable!("iloc length_size must be in [0,4,8]"),
183                }
184            }
185        }
186        Ok(IlocExt {
187            version: IlocVersion::V0,
188        })
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    const ENCODED_ILOC_LIBAVIF: &[u8] = &[
197        0x00, 0x00, 0x00, 0x1e, 0x69, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
198        0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x1a,
199    ];
200
201    #[test]
202    fn test_iloc_libavif_decode() {
203        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_ILOC_LIBAVIF);
204
205        let iloc: Iloc = Iloc {
206            item_locations: vec![ItemLocation {
207                item_id: 1,
208                construction_method: 0,
209                data_reference_index: 0,
210                base_offset: 0,
211                extents: vec![ItemLocationExtent {
212                    item_reference_index: 0,
213                    offset: 312,
214                    length: 26,
215                }],
216            }],
217        };
218        let decoded = Iloc::decode(buf).unwrap();
219        assert_eq!(decoded, iloc);
220    }
221
222    // Regression for issue #158: out-of-range *_size nibbles (anything
223    // outside {0, 4, 8} per ISO/IEC 14496-12 ยง8.11.3.3) must return
224    // Err, not panic.
225
226    #[test]
227    fn test_iloc_reserved_base_offset_size() {
228        // version=0, flags=0, sizes0=0x00 (offset_size=0, length_size=0),
229        // sizes1=0xF0 (base_offset_size=15), item_count=1,
230        // item_id=0, data_reference_index=0, then base_offset_size match.
231        let body: &[u8] = &[0x00, 0xF0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
232        let ext = IlocExt {
233            version: IlocVersion::V0,
234        };
235        assert!(matches!(
236            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
237            Err(Error::Reserved)
238        ));
239    }
240
241    #[test]
242    fn test_iloc_reserved_index_size() {
243        // V1: sizes1 low nibble is index_size. base_offset_size=0,
244        // index_size=15. Reach the index_size match by giving an extent.
245        let body: &[u8] = &[
246            0x00, 0x0F, // sizes0=0, sizes1=0x0F
247            0x00, 0x01, // item_count=1
248            0x00, 0x00, // item_id
249            0x00, 0x00, // construction_method
250            0x00, 0x00, // data_reference_index
251            // base_offset_size=0 -> no base_offset
252            0x00, 0x01, // extent_count=1
253        ];
254        let ext = IlocExt {
255            version: IlocVersion::V1,
256        };
257        assert!(matches!(
258            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
259            Err(Error::Reserved)
260        ));
261    }
262
263    #[test]
264    fn test_iloc_reserved_offset_size() {
265        // V0: sizes0 high nibble is offset_size=0xF.
266        let body: &[u8] = &[
267            0xF0, 0x00, // sizes0=0xF0 (offset_size=15), sizes1=0
268            0x00, 0x01, // item_count=1
269            0x00, 0x00, // item_id
270            0x00, 0x00, // data_reference_index
271            0x00, 0x01, // extent_count=1
272        ];
273        let ext = IlocExt {
274            version: IlocVersion::V0,
275        };
276        assert!(matches!(
277            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
278            Err(Error::Reserved)
279        ));
280    }
281
282    #[test]
283    fn test_iloc_reserved_length_size() {
284        // V0: sizes0 low nibble is length_size=0xF, offset_size=0
285        // so the offset_size arm passes through 0 and reaches length_size.
286        let body: &[u8] = &[
287            0x0F, 0x00, // sizes0=0x0F (length_size=15), sizes1=0
288            0x00, 0x01, // item_count=1
289            0x00, 0x00, // item_id
290            0x00, 0x00, // data_reference_index
291            0x00, 0x01, // extent_count=1
292        ];
293        let ext = IlocExt {
294            version: IlocVersion::V0,
295        };
296        assert!(matches!(
297            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
298            Err(Error::Reserved)
299        ));
300    }
301
302    #[test]
303    fn test_iloc_avif_encode() {
304        let iloc: Iloc = Iloc {
305            item_locations: vec![ItemLocation {
306                item_id: 1,
307                construction_method: 0,
308                data_reference_index: 0,
309                base_offset: 0,
310                extents: vec![ItemLocationExtent {
311                    item_reference_index: 0,
312                    offset: 312,
313                    length: 26,
314                }],
315            }],
316        };
317        let mut buf = Vec::new();
318        iloc.encode(&mut buf).unwrap();
319
320        assert_eq!(buf.as_slice(), ENCODED_ILOC_LIBAVIF);
321    }
322}