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 item_count: u32 = self
124            .item_locations
125            .len()
126            .try_into()
127            .map_err(|_| Error::TooLarge(Self::KIND))?;
128
129        let mut base_offset_size = 0u8;
130        let mut offset_size = 4u8;
131        let mut length_size = 4u8;
132        let mut index_size = 0u8;
133        let mut needs_version_1 = false;
134        let mut needs_version_2 = self.item_locations.len() > u16::MAX as usize;
135
136        for item_location in &self.item_locations {
137            if item_location.construction_method > 2 {
138                return Err(Error::InvalidSize);
139            }
140            if item_location.extents.len() > u16::MAX as usize {
141                return Err(Error::TooLarge(Self::KIND));
142            }
143
144            needs_version_1 |= item_location.construction_method != 0;
145            needs_version_2 |= item_location.item_id > u16::MAX as u32;
146
147            if item_location.base_offset > 0 {
148                if item_location.base_offset > u32::MAX as u64 {
149                    base_offset_size = 8u8;
150                } else if base_offset_size != 8 {
151                    base_offset_size = 4u8;
152                }
153            }
154
155            for extent in &item_location.extents {
156                if extent.item_reference_index > 0 {
157                    needs_version_1 = true;
158                    if extent.item_reference_index > u32::MAX as u64 {
159                        index_size = 8;
160                    } else if index_size != 8 {
161                        index_size = 4;
162                    }
163                }
164                if extent.offset > u32::MAX as u64 {
165                    offset_size = 8;
166                }
167                if extent.length > u32::MAX as u64 {
168                    length_size = 8;
169                }
170            }
171        }
172
173        let version = if needs_version_2 {
174            IlocVersion::V2
175        } else if needs_version_1 {
176            IlocVersion::V1
177        } else {
178            IlocVersion::V0
179        };
180
181        let size0 = (offset_size << 4) | length_size;
182        let size1 = (base_offset_size << 4) | index_size;
183        size0.encode(buf)?;
184        size1.encode(buf)?;
185        if version == IlocVersion::V0 || version == IlocVersion::V1 {
186            u16::try_from(item_count)
187                .map_err(|_| Error::TooLarge(Self::KIND))?
188                .encode(buf)?;
189        } else {
190            item_count.encode(buf)?;
191        }
192        for item_location in &self.item_locations {
193            if version == IlocVersion::V0 || version == IlocVersion::V1 {
194                u16::try_from(item_location.item_id)
195                    .map_err(|_| Error::TooLarge(Self::KIND))?
196                    .encode(buf)?;
197            } else {
198                item_location.item_id.encode(buf)?;
199            }
200            if version == IlocVersion::V1 || version == IlocVersion::V2 {
201                (item_location.construction_method as u16).encode(buf)?
202            }
203            item_location.data_reference_index.encode(buf)?;
204            match base_offset_size {
205                0 => {}
206                4 => u32::try_from(item_location.base_offset)
207                    .map_err(|_| Error::TooLarge(Self::KIND))?
208                    .encode(buf)?,
209                8 => item_location.base_offset.encode(buf)?,
210                _ => unreachable!("iloc base_offset_size must be in [0,4,8]"),
211            }
212            u16::try_from(item_location.extents.len())
213                .map_err(|_| Error::TooLarge(Self::KIND))?
214                .encode(buf)?;
215            for extent in &item_location.extents {
216                match index_size {
217                    0 => {}
218                    4 => u32::try_from(extent.item_reference_index)
219                        .map_err(|_| Error::TooLarge(Self::KIND))?
220                        .encode(buf)?,
221                    8 => extent.item_reference_index.encode(buf)?,
222                    _ => unreachable!("iloc index_size must be in [0,4,8]"),
223                }
224                match offset_size {
225                    0 => {}
226                    4 => u32::try_from(extent.offset)
227                        .map_err(|_| Error::TooLarge(Self::KIND))?
228                        .encode(buf)?,
229                    8 => extent.offset.encode(buf)?,
230                    _ => unreachable!("iloc offset_size must be in [0,4,8]"),
231                }
232                match length_size {
233                    0 => {}
234                    4 => u32::try_from(extent.length)
235                        .map_err(|_| Error::TooLarge(Self::KIND))?
236                        .encode(buf)?,
237                    8 => extent.length.encode(buf)?,
238                    _ => unreachable!("iloc length_size must be in [0,4,8]"),
239                }
240            }
241        }
242        Ok(IlocExt { version })
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    const ENCODED_ILOC_LIBAVIF: &[u8] = &[
251        0x00, 0x00, 0x00, 0x1e, 0x69, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
252        0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x1a,
253    ];
254
255    #[test]
256    fn test_iloc_libavif_decode() {
257        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_ILOC_LIBAVIF);
258
259        let iloc: Iloc = Iloc {
260            item_locations: vec![ItemLocation {
261                item_id: 1,
262                construction_method: 0,
263                data_reference_index: 0,
264                base_offset: 0,
265                extents: vec![ItemLocationExtent {
266                    item_reference_index: 0,
267                    offset: 312,
268                    length: 26,
269                }],
270            }],
271        };
272        let decoded = Iloc::decode(buf).unwrap();
273        assert_eq!(decoded, iloc);
274    }
275
276    // Regression for issue #158: out-of-range *_size nibbles (anything
277    // outside {0, 4, 8} per ISO/IEC 14496-12 ยง8.11.3.3) must return
278    // Err, not panic.
279
280    #[test]
281    fn test_iloc_reserved_base_offset_size() {
282        // version=0, flags=0, sizes0=0x00 (offset_size=0, length_size=0),
283        // sizes1=0xF0 (base_offset_size=15), item_count=1,
284        // item_id=0, data_reference_index=0, then base_offset_size match.
285        let body: &[u8] = &[0x00, 0xF0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
286        let ext = IlocExt {
287            version: IlocVersion::V0,
288        };
289        assert!(matches!(
290            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
291            Err(Error::Reserved)
292        ));
293    }
294
295    #[test]
296    fn test_iloc_reserved_index_size() {
297        // V1: sizes1 low nibble is index_size. base_offset_size=0,
298        // index_size=15. Reach the index_size match by giving an extent.
299        let body: &[u8] = &[
300            0x00, 0x0F, // sizes0=0, sizes1=0x0F
301            0x00, 0x01, // item_count=1
302            0x00, 0x00, // item_id
303            0x00, 0x00, // construction_method
304            0x00, 0x00, // data_reference_index
305            // base_offset_size=0 -> no base_offset
306            0x00, 0x01, // extent_count=1
307        ];
308        let ext = IlocExt {
309            version: IlocVersion::V1,
310        };
311        assert!(matches!(
312            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
313            Err(Error::Reserved)
314        ));
315    }
316
317    #[test]
318    fn test_iloc_reserved_offset_size() {
319        // V0: sizes0 high nibble is offset_size=0xF.
320        let body: &[u8] = &[
321            0xF0, 0x00, // sizes0=0xF0 (offset_size=15), sizes1=0
322            0x00, 0x01, // item_count=1
323            0x00, 0x00, // item_id
324            0x00, 0x00, // data_reference_index
325            0x00, 0x01, // extent_count=1
326        ];
327        let ext = IlocExt {
328            version: IlocVersion::V0,
329        };
330        assert!(matches!(
331            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
332            Err(Error::Reserved)
333        ));
334    }
335
336    #[test]
337    fn test_iloc_reserved_length_size() {
338        // V0: sizes0 low nibble is length_size=0xF, offset_size=0
339        // so the offset_size arm passes through 0 and reaches length_size.
340        let body: &[u8] = &[
341            0x0F, 0x00, // sizes0=0x0F (length_size=15), sizes1=0
342            0x00, 0x01, // item_count=1
343            0x00, 0x00, // item_id
344            0x00, 0x00, // data_reference_index
345            0x00, 0x01, // extent_count=1
346        ];
347        let ext = IlocExt {
348            version: IlocVersion::V0,
349        };
350        assert!(matches!(
351            Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
352            Err(Error::Reserved)
353        ));
354    }
355
356    #[test]
357    fn test_iloc_avif_encode() {
358        let iloc: Iloc = Iloc {
359            item_locations: vec![ItemLocation {
360                item_id: 1,
361                construction_method: 0,
362                data_reference_index: 0,
363                base_offset: 0,
364                extents: vec![ItemLocationExtent {
365                    item_reference_index: 0,
366                    offset: 312,
367                    length: 26,
368                }],
369            }],
370        };
371        let mut buf = Vec::new();
372        iloc.encode(&mut buf).unwrap();
373
374        assert_eq!(buf.as_slice(), ENCODED_ILOC_LIBAVIF);
375    }
376
377    #[test]
378    fn test_iloc_version_1_fields_round_trip() {
379        let iloc = Iloc {
380            item_locations: vec![ItemLocation {
381                item_id: 1,
382                construction_method: 1,
383                data_reference_index: 0,
384                base_offset: 0,
385                extents: vec![ItemLocationExtent {
386                    item_reference_index: u32::MAX as u64 + 1,
387                    offset: u32::MAX as u64 + 1,
388                    length: u32::MAX as u64 + 2,
389                }],
390            }],
391        };
392        let mut buf = Vec::new();
393        iloc.encode(&mut buf).unwrap();
394
395        assert_eq!(buf[8], 1); // version 1
396        assert_eq!(buf[12], 0x88); // 64-bit extent offset and length
397        assert_eq!(buf[13], 0x08); // no base offset, 64-bit extent index
398        assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
399    }
400
401    #[test]
402    fn test_iloc_version_2_item_id_round_trip() {
403        let iloc = Iloc {
404            item_locations: vec![ItemLocation {
405                item_id: u16::MAX as u32 + 1,
406                construction_method: 0,
407                data_reference_index: 0,
408                base_offset: 0,
409                extents: vec![],
410            }],
411        };
412        let mut buf = Vec::new();
413        iloc.encode(&mut buf).unwrap();
414
415        assert_eq!(buf[8], 2);
416        assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
417    }
418
419    #[test]
420    fn test_iloc_version_2_item_count_round_trip() {
421        let item_location = ItemLocation {
422            item_id: 0,
423            construction_method: 0,
424            data_reference_index: 0,
425            base_offset: 0,
426            extents: vec![],
427        };
428        let iloc = Iloc {
429            item_locations: vec![item_location; u16::MAX as usize + 1],
430        };
431        let mut buf = Vec::new();
432        iloc.encode(&mut buf).unwrap();
433
434        assert_eq!(buf[8], 2);
435        assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
436    }
437
438    #[test]
439    fn test_iloc_rejects_unrepresentable_construction_method() {
440        let iloc = Iloc {
441            item_locations: vec![ItemLocation {
442                item_id: 0,
443                construction_method: 3,
444                data_reference_index: 0,
445                base_offset: 0,
446                extents: vec![],
447            }],
448        };
449
450        assert!(matches!(
451            iloc.encode(&mut Vec::new()),
452            Err(Error::InvalidSize)
453        ));
454    }
455
456    #[test]
457    fn test_iloc_rejects_too_many_extents() {
458        let extent = ItemLocationExtent {
459            item_reference_index: 0,
460            offset: 0,
461            length: 0,
462        };
463        let iloc = Iloc {
464            item_locations: vec![ItemLocation {
465                item_id: 0,
466                construction_method: 0,
467                data_reference_index: 0,
468                base_offset: 0,
469                extents: vec![extent; u16::MAX as usize + 1],
470            }],
471        };
472
473        assert!(matches!(
474            iloc.encode(&mut Vec::new()),
475            Err(Error::TooLarge(kind)) if kind == Iloc::KIND
476        ));
477    }
478}