1use crate::*;
2
3ext! {
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, 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 zero_size_extents = offset_size == 0 && length_size == 0 && index_size == 0;
54
55 let item_count = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
56 u16::decode(buf)? as usize
57 } else {
58 u32::decode(buf)? as usize
59 };
60 let mut item_locations = vec![];
61 for _i in 0..item_count {
62 let item_id = if ext.version == IlocVersion::V0 || ext.version == IlocVersion::V1 {
63 u16::decode(buf)? as u32
64 } else {
65 u32::decode(buf)?
66 };
67 let construction_method: u8 =
68 if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
69 let construction_method_packed = u16::decode(buf)?;
70 (construction_method_packed & 0x0f) as u8
71 } else {
72 0
73 };
74 let data_reference_index = u16::decode(buf)?;
75 let base_offset = match base_offset_size {
76 0 => 0u64,
77 4 => u32::decode(buf)? as u64,
78 8 => u64::decode(buf)?,
79 _ => return Err(Error::Reserved),
80 };
81 let extent_count = u16::decode(buf)?;
82 if zero_size_extents && extent_count > 1 {
86 return Err(Error::OutOfBounds);
87 }
88 let mut extents = vec![];
89 for _j in 0..extent_count {
90 let item_reference_index: u64 =
91 if ext.version == IlocVersion::V1 || ext.version == IlocVersion::V2 {
92 match index_size {
93 0 => 0,
94 4 => u32::decode(buf)? as u64,
95 8 => u64::decode(buf)?,
96 _ => return Err(Error::Reserved),
97 }
98 } else {
99 0
100 };
101 let extent_offset = match offset_size {
102 0 => 0u64,
103 4 => u32::decode(buf)? as u64,
104 8 => u64::decode(buf)?,
105 _ => return Err(Error::Reserved),
106 };
107 let extent_length = match length_size {
108 0 => 0u64,
109 4 => u32::decode(buf)? as u64,
110 8 => u64::decode(buf)?,
111 _ => return Err(Error::Reserved),
112 };
113 extents.push(ItemLocationExtent {
114 item_reference_index,
115 offset: extent_offset,
116 length: extent_length,
117 });
118 }
119 item_locations.push(ItemLocation {
120 item_id,
121 construction_method,
122 data_reference_index,
123 base_offset,
124 extents,
125 })
126 }
127 Ok(Iloc { item_locations })
128 }
129
130 fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IlocExt> {
131 let item_count: u32 = self
132 .item_locations
133 .len()
134 .try_into()
135 .map_err(|_| Error::TooLarge(Self::KIND))?;
136
137 let mut base_offset_size = 0u8;
138 let mut offset_size = 4u8;
139 let mut length_size = 4u8;
140 let mut index_size = 0u8;
141 let mut needs_version_1 = false;
142 let mut needs_version_2 = self.item_locations.len() > u16::MAX as usize;
143
144 for item_location in &self.item_locations {
145 if item_location.construction_method > 2 {
146 return Err(Error::InvalidSize);
147 }
148 if item_location.extents.len() > u16::MAX as usize {
149 return Err(Error::TooLarge(Self::KIND));
150 }
151
152 needs_version_1 |= item_location.construction_method != 0;
153 needs_version_2 |= item_location.item_id > u16::MAX as u32;
154
155 if item_location.base_offset > 0 {
156 if item_location.base_offset > u32::MAX as u64 {
157 base_offset_size = 8u8;
158 } else if base_offset_size != 8 {
159 base_offset_size = 4u8;
160 }
161 }
162
163 for extent in &item_location.extents {
164 if extent.item_reference_index > 0 {
165 needs_version_1 = true;
166 if extent.item_reference_index > u32::MAX as u64 {
167 index_size = 8;
168 } else if index_size != 8 {
169 index_size = 4;
170 }
171 }
172 if extent.offset > u32::MAX as u64 {
173 offset_size = 8;
174 }
175 if extent.length > u32::MAX as u64 {
176 length_size = 8;
177 }
178 }
179 }
180
181 let version = if needs_version_2 {
182 IlocVersion::V2
183 } else if needs_version_1 {
184 IlocVersion::V1
185 } else {
186 IlocVersion::V0
187 };
188
189 let size0 = (offset_size << 4) | length_size;
190 let size1 = (base_offset_size << 4) | index_size;
191 size0.encode(buf)?;
192 size1.encode(buf)?;
193 if version == IlocVersion::V0 || version == IlocVersion::V1 {
194 u16::try_from(item_count)
195 .map_err(|_| Error::TooLarge(Self::KIND))?
196 .encode(buf)?;
197 } else {
198 item_count.encode(buf)?;
199 }
200 for item_location in &self.item_locations {
201 if version == IlocVersion::V0 || version == IlocVersion::V1 {
202 u16::try_from(item_location.item_id)
203 .map_err(|_| Error::TooLarge(Self::KIND))?
204 .encode(buf)?;
205 } else {
206 item_location.item_id.encode(buf)?;
207 }
208 if version == IlocVersion::V1 || version == IlocVersion::V2 {
209 (item_location.construction_method as u16).encode(buf)?
210 }
211 item_location.data_reference_index.encode(buf)?;
212 match base_offset_size {
213 0 => {}
214 4 => u32::try_from(item_location.base_offset)
215 .map_err(|_| Error::TooLarge(Self::KIND))?
216 .encode(buf)?,
217 8 => item_location.base_offset.encode(buf)?,
218 _ => unreachable!("iloc base_offset_size must be in [0,4,8]"),
219 }
220 u16::try_from(item_location.extents.len())
221 .map_err(|_| Error::TooLarge(Self::KIND))?
222 .encode(buf)?;
223 for extent in &item_location.extents {
224 match index_size {
225 0 => {}
226 4 => u32::try_from(extent.item_reference_index)
227 .map_err(|_| Error::TooLarge(Self::KIND))?
228 .encode(buf)?,
229 8 => extent.item_reference_index.encode(buf)?,
230 _ => unreachable!("iloc index_size must be in [0,4,8]"),
231 }
232 match offset_size {
233 0 => {}
234 4 => u32::try_from(extent.offset)
235 .map_err(|_| Error::TooLarge(Self::KIND))?
236 .encode(buf)?,
237 8 => extent.offset.encode(buf)?,
238 _ => unreachable!("iloc offset_size must be in [0,4,8]"),
239 }
240 match length_size {
241 0 => {}
242 4 => u32::try_from(extent.length)
243 .map_err(|_| Error::TooLarge(Self::KIND))?
244 .encode(buf)?,
245 8 => extent.length.encode(buf)?,
246 _ => unreachable!("iloc length_size must be in [0,4,8]"),
247 }
248 }
249 }
250 Ok(IlocExt { version })
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 const ENCODED_ILOC_LIBAVIF: &[u8] = &[
259 0x00, 0x00, 0x00, 0x1e, 0x69, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
260 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x1a,
261 ];
262
263 #[test]
264 fn test_iloc_libavif_decode() {
265 let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_ILOC_LIBAVIF);
266
267 let iloc: Iloc = Iloc {
268 item_locations: vec![ItemLocation {
269 item_id: 1,
270 construction_method: 0,
271 data_reference_index: 0,
272 base_offset: 0,
273 extents: vec![ItemLocationExtent {
274 item_reference_index: 0,
275 offset: 312,
276 length: 26,
277 }],
278 }],
279 };
280 let decoded = Iloc::decode(buf).unwrap();
281 assert_eq!(decoded, iloc);
282 }
283
284 #[test]
289 fn test_iloc_reserved_base_offset_size() {
290 let body: &[u8] = &[0x00, 0xF0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
294 let ext = IlocExt {
295 version: IlocVersion::V0,
296 };
297 assert!(matches!(
298 Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
299 Err(Error::Reserved)
300 ));
301 }
302
303 #[test]
304 fn test_iloc_reserved_index_size() {
305 let body: &[u8] = &[
308 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
316 let ext = IlocExt {
317 version: IlocVersion::V1,
318 };
319 assert!(matches!(
320 Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
321 Err(Error::Reserved)
322 ));
323 }
324
325 #[test]
326 fn test_iloc_reserved_offset_size() {
327 let body: &[u8] = &[
329 0xF0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
335 let ext = IlocExt {
336 version: IlocVersion::V0,
337 };
338 assert!(matches!(
339 Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
340 Err(Error::Reserved)
341 ));
342 }
343
344 #[test]
345 fn test_iloc_reserved_length_size() {
346 let body: &[u8] = &[
349 0x0F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
355 let ext = IlocExt {
356 version: IlocVersion::V0,
357 };
358 assert!(matches!(
359 Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
360 Err(Error::Reserved)
361 ));
362 }
363
364 #[test]
365 fn test_iloc_rejects_repeated_zero_size_extents() {
366 const ZERO_SIZE_EXTENTS: &[u8] = &[
369 0x00, 0x00, 0x00, 0x1c, 0x69, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
370 0x00, 0x02, 0xc8, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00,
371 ];
372
373 let err = Iloc::decode(&mut std::io::Cursor::new(ZERO_SIZE_EXTENTS)).unwrap_err();
374 assert!(
375 matches!(err, Error::OverDecode(kind) if kind == Iloc::KIND),
376 "{err:?}"
377 );
378 }
379
380 #[test]
381 fn test_iloc_v1_rejects_repeated_zero_size_extents() {
382 let body: &[u8] = &[
384 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, ];
391 let ext = IlocExt {
392 version: IlocVersion::V1,
393 };
394 assert!(matches!(
395 Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext),
396 Err(Error::OutOfBounds)
397 ));
398 }
399
400 #[test]
401 fn test_iloc_single_zero_size_extent_decodes() {
402 let body: &[u8] = &[
404 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, ];
413 let ext = IlocExt {
414 version: IlocVersion::V0,
415 };
416 let iloc = Iloc::decode_body_ext(&mut std::io::Cursor::new(body), ext).unwrap();
417 let extent = ItemLocationExtent {
418 item_reference_index: 0,
419 offset: 0,
420 length: 0,
421 };
422 assert_eq!(
423 iloc.item_locations
424 .iter()
425 .map(|item| (item.item_id, item.extents.clone()))
426 .collect::<Vec<_>>(),
427 vec![(1, vec![extent.clone()]), (2, vec![extent])]
428 );
429 }
430
431 #[test]
432 fn test_iloc_avif_encode() {
433 let iloc: Iloc = Iloc {
434 item_locations: vec![ItemLocation {
435 item_id: 1,
436 construction_method: 0,
437 data_reference_index: 0,
438 base_offset: 0,
439 extents: vec![ItemLocationExtent {
440 item_reference_index: 0,
441 offset: 312,
442 length: 26,
443 }],
444 }],
445 };
446 let mut buf = Vec::new();
447 iloc.encode(&mut buf).unwrap();
448
449 assert_eq!(buf.as_slice(), ENCODED_ILOC_LIBAVIF);
450 }
451
452 #[test]
453 fn test_iloc_version_1_fields_round_trip() {
454 let iloc = Iloc {
455 item_locations: vec![ItemLocation {
456 item_id: 1,
457 construction_method: 1,
458 data_reference_index: 0,
459 base_offset: 0,
460 extents: vec![ItemLocationExtent {
461 item_reference_index: u32::MAX as u64 + 1,
462 offset: u32::MAX as u64 + 1,
463 length: u32::MAX as u64 + 2,
464 }],
465 }],
466 };
467 let mut buf = Vec::new();
468 iloc.encode(&mut buf).unwrap();
469
470 assert_eq!(buf[8], 1); assert_eq!(buf[12], 0x88); assert_eq!(buf[13], 0x08); assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
474 }
475
476 #[test]
477 fn test_iloc_version_2_item_id_round_trip() {
478 let iloc = Iloc {
479 item_locations: vec![ItemLocation {
480 item_id: u16::MAX as u32 + 1,
481 construction_method: 0,
482 data_reference_index: 0,
483 base_offset: 0,
484 extents: vec![],
485 }],
486 };
487 let mut buf = Vec::new();
488 iloc.encode(&mut buf).unwrap();
489
490 assert_eq!(buf[8], 2);
491 assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
492 }
493
494 #[test]
495 fn test_iloc_version_2_item_count_round_trip() {
496 let item_location = ItemLocation {
497 item_id: 0,
498 construction_method: 0,
499 data_reference_index: 0,
500 base_offset: 0,
501 extents: vec![],
502 };
503 let iloc = Iloc {
504 item_locations: vec![item_location; u16::MAX as usize + 1],
505 };
506 let mut buf = Vec::new();
507 iloc.encode(&mut buf).unwrap();
508
509 assert_eq!(buf[8], 2);
510 assert_eq!(Iloc::decode(&mut buf.as_slice()).unwrap(), iloc);
511 }
512
513 #[test]
514 fn test_iloc_rejects_unrepresentable_construction_method() {
515 let iloc = Iloc {
516 item_locations: vec![ItemLocation {
517 item_id: 0,
518 construction_method: 3,
519 data_reference_index: 0,
520 base_offset: 0,
521 extents: vec![],
522 }],
523 };
524
525 assert!(matches!(
526 iloc.encode(&mut Vec::new()),
527 Err(Error::InvalidSize)
528 ));
529 }
530
531 #[test]
532 fn test_iloc_rejects_too_many_extents() {
533 let extent = ItemLocationExtent {
534 item_reference_index: 0,
535 offset: 0,
536 length: 0,
537 };
538 let iloc = Iloc {
539 item_locations: vec![ItemLocation {
540 item_id: 0,
541 construction_method: 0,
542 data_reference_index: 0,
543 base_offset: 0,
544 extents: vec![extent; u16::MAX as usize + 1],
545 }],
546 };
547
548 assert!(matches!(
549 iloc.encode(&mut Vec::new()),
550 Err(Error::TooLarge(kind)) if kind == Iloc::KIND
551 ));
552 }
553}