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 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 #[test]
281 fn test_iloc_reserved_base_offset_size() {
282 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 let body: &[u8] = &[
300 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
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 let body: &[u8] = &[
321 0xF0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
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 let body: &[u8] = &[
341 0x0F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
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); assert_eq!(buf[12], 0x88); assert_eq!(buf[13], 0x08); 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}