1use crate::boxes::{FourCc, Reader};
20use crate::props::Properties;
21use otf_pixels_core::{PixelsError, Result};
22use std::borrow::Cow;
23
24pub const URN_ALPHA: &str = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
26
27pub const URN_ALPHA_LEGACY: &str = "urn:mpeg:hevc:2015:auxid:1";
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Construction {
34 File,
37 Idat,
41 Item,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Extent {
52 pub offset: u64,
54 pub length: u64,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Item {
61 pub id: u32,
63 pub kind: FourCc,
66 pub name: String,
68 pub hidden: bool,
71 pub construction: Construction,
73 pub extents: Vec<Extent>,
75}
76
77impl Item {
78 #[must_use]
80 pub fn is_coded_image(&self) -> bool {
81 self.kind == FourCc::new(b"av01")
82 }
83
84 #[must_use]
86 pub fn is_grid(&self) -> bool {
87 self.kind == FourCc::new(b"grid")
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Reference {
94 pub kind: FourCc,
98 pub from: u32,
100 pub to: Vec<u32>,
102}
103
104#[derive(Debug, Clone, Default)]
106pub struct Meta {
107 pub primary: Option<u32>,
109 pub items: Vec<Item>,
111 pub references: Vec<Reference>,
113 pub properties: Properties,
115 idat: Option<(usize, usize)>,
117}
118
119impl Meta {
120 pub fn parse(mut meta: Reader<'_>) -> Result<Self> {
127 let (_version, _flags) = meta.full_box()?;
129
130 let mut out = Self::default();
131 let mut locations = Vec::new();
135 let mut infos = Vec::new();
136
137 while let Some(header) = meta.next_box() {
138 let header = header?;
139 let payload = meta.payload(&header);
140 match &header.kind.0 {
141 b"pitm" => out.primary = Some(parse_pitm(payload)?),
142 b"iinf" => infos = parse_iinf(payload)?,
143 b"iloc" => locations = parse_iloc(payload)?,
144 b"iref" => out.references = parse_iref(payload)?,
145 b"iprp" => out.properties = Properties::parse(payload)?,
146 b"idat" => out.idat = Some((header.payload_start, header.payload_len)),
147 _ => {}
148 }
149 }
150
151 out.items = join(infos, locations);
152 Ok(out)
153 }
154
155 #[must_use]
157 pub fn item(&self, id: u32) -> Option<&Item> {
158 self.items.iter().find(|item| item.id == id)
159 }
160
161 #[must_use]
167 pub fn primary_item(&self) -> Option<&Item> {
168 self.primary.and_then(|id| self.item(id)).or_else(|| {
169 self.items
170 .iter()
171 .find(|item| item.is_coded_image() || item.is_grid())
172 })
173 }
174
175 #[must_use]
177 pub fn referenced(&self, from: u32, kind: &[u8; 4]) -> &[u32] {
178 let wanted = FourCc::new(kind);
179 self.references
180 .iter()
181 .find(|reference| reference.from == from && reference.kind == wanted)
182 .map_or(&[], |reference| reference.to.as_slice())
183 }
184
185 #[must_use]
193 pub fn alpha_item(&self, id: u32) -> Option<&Item> {
194 self.references
195 .iter()
196 .filter(|reference| {
197 reference.kind == FourCc::new(b"auxl") && reference.to.contains(&id)
198 })
199 .find_map(|reference| {
200 let urn = self.properties.auxiliary_type(reference.from)?;
201 if urn == URN_ALPHA || urn == URN_ALPHA_LEGACY {
202 self.item(reference.from)
203 } else {
204 None
205 }
206 })
207 }
208
209 pub fn item_data<'a>(&self, file: &'a [u8], item: &Item) -> Result<Cow<'a, [u8]>> {
220 let (base, region) = match item.construction {
221 Construction::File => (0_usize, file),
222 Construction::Idat => {
223 let (start, len) = self.idat.ok_or_else(|| {
224 PixelsError::malformed(
225 "avif",
226 format!(
227 "item {} is stored in idat, but the file has no idat box",
228 item.id
229 ),
230 )
231 })?;
232 let region = file.get(start..start.saturating_add(len)).ok_or_else(|| {
233 PixelsError::malformed("avif", "the idat box extends past the file")
234 })?;
235 (0_usize, region)
236 }
237 Construction::Item => {
238 return Err(PixelsError::unsupported(format!(
239 "avif: item {} uses item-relative extent offsets, which this decoder does not resolve",
240 item.id
241 )));
242 }
243 };
244 let _ = base;
245
246 let total: u64 = item.extents.iter().map(|extent| extent.length).sum();
250 if total > region.len() as u64 {
251 return Err(PixelsError::malformed(
252 "avif",
253 format!(
254 "item {} declares {total} bytes across {} extents, more than the {} available",
255 item.id,
256 item.extents.len(),
257 region.len()
258 ),
259 ));
260 }
261
262 let slice_of = |extent: &Extent| -> Result<&'a [u8]> {
263 let start = usize::try_from(extent.offset).map_err(|_| {
264 PixelsError::malformed(
265 "avif",
266 format!(
267 "item {} starts at offset {}, beyond this platform's addressing",
268 item.id, extent.offset
269 ),
270 )
271 })?;
272 let len = usize::try_from(extent.length).map_err(|_| {
273 PixelsError::malformed(
274 "avif",
275 format!(
276 "item {} declares a {}-byte extent, beyond this platform's addressing",
277 item.id, extent.length
278 ),
279 )
280 })?;
281 let end = start.checked_add(len).ok_or_else(|| {
282 PixelsError::malformed("avif", format!("item {}'s extent overflows", item.id))
283 })?;
284 region.get(start..end).ok_or_else(|| {
285 PixelsError::malformed(
286 "avif",
287 format!(
288 "item {} has an extent at {start}..{end}, outside the {} bytes available",
289 item.id,
290 region.len()
291 ),
292 )
293 })
294 };
295
296 match item.extents.as_slice() {
297 [] => Err(PixelsError::malformed(
298 "avif",
299 format!("item {} has no extents, so it has no data", item.id),
300 )),
301 [single] => slice_of(single).map(Cow::Borrowed),
302 many => {
303 let mut joined = Vec::with_capacity(usize::try_from(total).unwrap_or(0));
304 for extent in many {
305 joined.extend_from_slice(slice_of(extent)?);
306 }
307 Ok(Cow::Owned(joined))
308 }
309 }
310 }
311}
312
313fn join(infos: Vec<(u32, FourCc, String, bool)>, locations: Vec<Located>) -> Vec<Item> {
318 infos
319 .into_iter()
320 .filter_map(|(id, kind, name, hidden)| {
321 let located = locations.iter().find(|located| located.id == id)?;
322 Some(Item {
323 id,
324 kind,
325 name,
326 hidden,
327 construction: located.construction,
328 extents: located.extents.clone(),
329 })
330 })
331 .collect()
332}
333
334#[derive(Debug, Clone)]
336struct Located {
337 id: u32,
338 construction: Construction,
339 extents: Vec<Extent>,
340}
341
342fn parse_pitm(mut payload: Reader<'_>) -> Result<u32> {
344 let (version, _flags) = payload.full_box()?;
345 if version == 0 {
346 payload.u16().map(u32::from)
347 } else {
348 payload.u32()
349 }
350}
351
352fn parse_iinf(mut payload: Reader<'_>) -> Result<Vec<(u32, FourCc, String, bool)>> {
354 let (version, _flags) = payload.full_box()?;
355 let count = if version == 0 {
356 u32::from(payload.u16()?)
357 } else {
358 payload.u32()?
359 };
360
361 if u64::from(count) * 8 > payload.remaining() as u64 {
364 return Err(PixelsError::malformed(
365 "avif",
366 format!(
367 "iinf declares {count} items, more than its {} remaining bytes can hold",
368 payload.remaining()
369 ),
370 ));
371 }
372
373 let mut out = Vec::with_capacity(count as usize);
374 while let Some(header) = payload.next_box() {
375 let header = header?;
376 if header.kind != FourCc::new(b"infe") {
377 continue;
378 }
379 out.push(parse_infe(payload.payload(&header))?);
380 }
381 Ok(out)
382}
383
384fn parse_infe(mut payload: Reader<'_>) -> Result<(u32, FourCc, String, bool)> {
386 let (version, flags) = payload.full_box()?;
387 if version < 2 {
390 return Err(PixelsError::malformed(
391 "avif",
392 format!("infe version {version} cannot carry an item type"),
393 ));
394 }
395 let id = if version == 2 {
396 u32::from(payload.u16()?)
397 } else {
398 payload.u32()?
399 };
400 let _protection = payload.u16()?;
401 let kind = payload.fourcc()?;
402 let name = payload.cstring()?.to_owned();
403 let hidden = flags & 1 == 1;
405 Ok((id, kind, name, hidden))
406}
407
408fn parse_iloc(mut payload: Reader<'_>) -> Result<Vec<Located>> {
410 let (version, _flags) = payload.full_box()?;
411
412 let sizes = payload.u8()?;
413 let offset_size = sizes >> 4;
414 let length_size = sizes & 0x0f;
415 let sizes = payload.u8()?;
416 let base_offset_size = sizes >> 4;
417 let index_size = if version == 1 || version == 2 {
419 sizes & 0x0f
420 } else {
421 0
422 };
423
424 let count = match version {
425 0 | 1 => u32::from(payload.u16()?),
426 2 => payload.u32()?,
427 other => {
428 return Err(PixelsError::malformed(
429 "avif",
430 format!("iloc version {other} is not one this format defines"),
431 ));
432 }
433 };
434
435 if u64::from(count) * 6 > payload.remaining() as u64 {
438 return Err(PixelsError::malformed(
439 "avif",
440 format!(
441 "iloc declares {count} items, more than its {} remaining bytes can hold",
442 payload.remaining()
443 ),
444 ));
445 }
446
447 let mut out = Vec::with_capacity(count as usize);
448 for _ in 0..count {
449 let id = if version < 2 {
450 u32::from(payload.u16()?)
451 } else {
452 payload.u32()?
453 };
454
455 let construction = if version == 1 || version == 2 {
456 let word = payload.u16()?;
458 match word & 0x0f {
459 0 => Construction::File,
460 1 => Construction::Idat,
461 2 => Construction::Item,
462 other => {
463 return Err(PixelsError::malformed(
464 "avif",
465 format!("iloc construction method {other} is not one this format defines"),
466 ));
467 }
468 }
469 } else {
470 Construction::File
471 };
472
473 let _data_reference_index = payload.u16()?;
474 let base_offset = payload.uint(base_offset_size)?;
475 let extent_count = payload.u16()?;
476
477 let per_extent = u64::from(offset_size)
481 .saturating_add(u64::from(length_size))
482 .saturating_add(u64::from(index_size))
483 .max(1);
484 if u64::from(extent_count) * per_extent > payload.remaining() as u64 {
485 return Err(PixelsError::malformed(
486 "avif",
487 format!(
488 "item {id} declares {extent_count} extents, more than its {} remaining bytes can hold",
489 payload.remaining()
490 ),
491 ));
492 }
493
494 let mut extents = Vec::with_capacity(usize::from(extent_count));
495 for _ in 0..extent_count {
496 if index_size > 0 {
497 let _extent_index = payload.uint(index_size)?;
498 }
499 let offset = payload.uint(offset_size)?;
500 let length = payload.uint(length_size)?;
501 let offset = base_offset.checked_add(offset).ok_or_else(|| {
504 PixelsError::malformed(
505 "avif",
506 format!("item {id} has an extent offset that overflows its base"),
507 )
508 })?;
509 extents.push(Extent { offset, length });
510 }
511
512 out.push(Located {
513 id,
514 construction,
515 extents,
516 });
517 }
518 Ok(out)
519}
520
521fn parse_iref(mut payload: Reader<'_>) -> Result<Vec<Reference>> {
523 let (version, _flags) = payload.full_box()?;
524 let mut out = Vec::new();
525
526 while let Some(header) = payload.next_box() {
527 let header = header?;
528 let mut reference = payload.payload(&header);
529 let from = if version == 0 {
531 u32::from(reference.u16()?)
532 } else {
533 reference.u32()?
534 };
535 let count = reference.u16()?;
536 let width = if version == 0 { 2_u64 } else { 4 };
537 if u64::from(count) * width > reference.remaining() as u64 {
538 return Err(PixelsError::malformed(
539 "avif",
540 format!(
541 "a '{}' reference declares {count} targets, more than its {} remaining bytes can hold",
542 header.kind,
543 reference.remaining()
544 ),
545 ));
546 }
547 let mut to = Vec::with_capacity(usize::from(count));
548 for _ in 0..count {
549 to.push(if version == 0 {
550 u32::from(reference.u16()?)
551 } else {
552 reference.u32()?
553 });
554 }
555 out.push(Reference {
556 kind: header.kind,
557 from,
558 to,
559 });
560 }
561 Ok(out)
562}
563
564#[cfg(test)]
565#[allow(
566 clippy::unwrap_used,
567 clippy::indexing_slicing,
568 reason = "tests operate on known-good values and assert shapes directly"
569)]
570mod tests {
571 use super::*;
572 use otf_pixels_core::ErrorCode;
573
574 fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
575 let mut out = Vec::new();
576 let total = u32::try_from(8 + payload.len()).unwrap();
577 out.extend_from_slice(&total.to_be_bytes());
578 out.extend_from_slice(kind);
579 out.extend_from_slice(payload);
580 out
581 }
582
583 fn infe(id: u16, kind: &[u8; 4], name: &str, hidden: bool) -> Vec<u8> {
585 let mut payload = vec![2, 0, 0, if hidden { 1 } else { 0 }];
586 payload.extend_from_slice(&id.to_be_bytes());
587 payload.extend_from_slice(&[0, 0]);
588 payload.extend_from_slice(kind);
589 payload.extend_from_slice(name.as_bytes());
590 payload.push(0);
591 boxed(b"infe", &payload)
592 }
593
594 fn iinf(entries: &[Vec<u8>]) -> Vec<u8> {
596 let mut payload = vec![0, 0, 0, 0];
597 payload.extend_from_slice(&u16::try_from(entries.len()).unwrap().to_be_bytes());
598 for entry in entries {
599 payload.extend_from_slice(entry);
600 }
601 boxed(b"iinf", &payload)
602 }
603
604 fn iloc(rows: &[(u16, u32, u32)]) -> Vec<u8> {
606 let mut payload = vec![0, 0, 0, 0];
607 payload.push(0x44); payload.push(0x00); payload.extend_from_slice(&u16::try_from(rows.len()).unwrap().to_be_bytes());
610 for (id, offset, length) in rows {
611 payload.extend_from_slice(&id.to_be_bytes());
612 payload.extend_from_slice(&[0, 0]); payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&offset.to_be_bytes());
615 payload.extend_from_slice(&length.to_be_bytes());
616 }
617 boxed(b"iloc", &payload)
618 }
619
620 fn pitm(id: u16) -> Vec<u8> {
621 let mut payload = vec![0, 0, 0, 0];
622 payload.extend_from_slice(&id.to_be_bytes());
623 boxed(b"pitm", &payload)
624 }
625
626 fn meta_box(children: &[Vec<u8>]) -> Vec<u8> {
627 let mut payload = vec![0, 0, 0, 0];
628 for child in children {
629 payload.extend_from_slice(child);
630 }
631 boxed(b"meta", &payload)
632 }
633
634 fn parse(file: &[u8]) -> Result<Meta> {
635 let mut reader = Reader::new(file);
636 let header = reader.next_box().unwrap().unwrap();
637 Meta::parse(reader.payload(&header))
638 }
639
640 #[test]
641 fn joins_the_type_table_with_the_extent_table() {
642 let file = meta_box(&[
643 pitm(1),
644 iinf(&[infe(1, b"av01", "color", false)]),
645 iloc(&[(1, 100, 42)]),
646 ]);
647 let meta = parse(&file).unwrap();
648
649 assert_eq!(meta.primary, Some(1));
650 assert_eq!(meta.items.len(), 1);
651 let item = &meta.items[0];
652 assert_eq!(item.id, 1);
653 assert!(item.is_coded_image());
654 assert_eq!(item.name, "color");
655 assert!(!item.hidden);
656 assert_eq!(item.construction, Construction::File);
657 assert_eq!(
658 item.extents,
659 vec![Extent {
660 offset: 100,
661 length: 42
662 }]
663 );
664 }
665
666 #[test]
670 fn an_item_missing_from_either_table_is_dropped() {
671 let file = meta_box(&[
672 iinf(&[infe(1, b"av01", "a", false), infe(2, b"av01", "b", false)]),
673 iloc(&[(1, 0, 4), (3, 0, 4)]),
674 ]);
675 let meta = parse(&file).unwrap();
676 assert_eq!(meta.items.len(), 1);
677 assert_eq!(meta.items[0].id, 1);
678 }
679
680 fn file_with_data(build: impl Fn(u32) -> Vec<u8>, data: &[u8]) -> Vec<u8> {
687 let probe = build(0);
688 let base = u32::try_from(probe.len()).unwrap();
689 let mut file = build(base);
690 assert_eq!(file.len(), probe.len(), "the offset changed the box length");
691 file.extend_from_slice(data);
692 file
693 }
694
695 #[test]
696 fn resolves_item_data_by_borrowing_a_single_extent() {
697 let file = file_with_data(
698 |base| meta_box(&[iinf(&[infe(1, b"av01", "", false)]), iloc(&[(1, base, 3)])]),
699 &[0xDE, 0xAD, 0xBE],
700 );
701
702 let meta = parse(&file).unwrap();
703 let data = meta.item_data(&file, &meta.items[0]).unwrap();
704 assert_eq!(&*data, &[0xDE, 0xAD, 0xBE]);
705 assert!(matches!(data, Cow::Borrowed(_)));
706 }
707
708 #[test]
709 fn concatenates_multiple_extents() {
710 let file = file_with_data(
712 |base| {
713 let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
714 payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&[0, 0]);
717 payload.extend_from_slice(&2_u16.to_be_bytes()); payload.extend_from_slice(&base.to_be_bytes());
719 payload.extend_from_slice(&2_u32.to_be_bytes());
720 payload.extend_from_slice(&(base + 4).to_be_bytes());
721 payload.extend_from_slice(&2_u32.to_be_bytes());
722 meta_box(&[
723 iinf(&[infe(1, b"av01", "", false)]),
724 boxed(b"iloc", &payload),
725 ])
726 },
727 &[1, 2, 0xFF, 0xFF, 3, 4],
728 );
729
730 let meta = parse(&file).unwrap();
731 let data = meta.item_data(&file, &meta.items[0]).unwrap();
732 assert_eq!(&*data, &[1, 2, 3, 4]);
733 assert!(matches!(data, Cow::Owned(_)));
734 }
735
736 #[test]
737 fn an_extent_outside_the_file_is_rejected() {
738 let file = meta_box(&[
739 iinf(&[infe(1, b"av01", "", false)]),
740 iloc(&[(1, 0, 100_000)]),
741 ]);
742 let meta = parse(&file).unwrap();
743 let error = meta.item_data(&file, &meta.items[0]).unwrap_err();
744 assert_eq!(error.code(), ErrorCode::Malformed);
745 }
746
747 #[test]
751 fn repeated_extents_cannot_sum_past_the_file() {
752 let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
753 payload.extend_from_slice(&1_u16.to_be_bytes());
754 payload.extend_from_slice(&1_u16.to_be_bytes());
755 payload.extend_from_slice(&[0, 0]);
756 payload.extend_from_slice(&64_u16.to_be_bytes()); for _ in 0..64 {
758 payload.extend_from_slice(&0_u32.to_be_bytes());
759 payload.extend_from_slice(&40_u32.to_be_bytes());
760 }
761 let iloc = boxed(b"iloc", &payload);
762 let file = meta_box(&[iinf(&[infe(1, b"av01", "", false)]), iloc]);
763
764 let meta = parse(&file).unwrap();
765 let error = meta.item_data(&file, &meta.items[0]).unwrap_err();
766 assert_eq!(error.code(), ErrorCode::Malformed);
767 assert!(error.to_string().contains("more than the"), "{error}");
768 }
769
770 #[test]
771 fn idat_construction_resolves_against_the_idat_box() {
772 let mut payload = vec![1, 0, 0, 0, 0x44, 0x00];
774 payload.extend_from_slice(&1_u16.to_be_bytes());
775 payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&[0, 0]);
778 payload.extend_from_slice(&1_u16.to_be_bytes()); payload.extend_from_slice(&2_u32.to_be_bytes()); payload.extend_from_slice(&2_u32.to_be_bytes());
781 let iloc = boxed(b"iloc", &payload);
782
783 let idat = boxed(b"idat", &[9, 9, 0xC0, 0xDE]);
784 let file = meta_box(&[iinf(&[infe(1, b"grid", "", false)]), iloc, idat]);
785
786 let meta = parse(&file).unwrap();
787 let data = meta.item_data(&file, &meta.items[0]).unwrap();
788 assert_eq!(&*data, &[0xC0, 0xDE]);
789 }
790
791 #[test]
792 fn item_relative_construction_is_unsupported_rather_than_guessed() {
793 let mut payload = vec![1, 0, 0, 0, 0x44, 0x00];
794 payload.extend_from_slice(&1_u16.to_be_bytes());
795 payload.extend_from_slice(&1_u16.to_be_bytes());
796 payload.extend_from_slice(&2_u16.to_be_bytes()); payload.extend_from_slice(&[0, 0]);
798 payload.extend_from_slice(&1_u16.to_be_bytes());
799 payload.extend_from_slice(&0_u32.to_be_bytes());
800 payload.extend_from_slice(&1_u32.to_be_bytes());
801 let iloc = boxed(b"iloc", &payload);
802 let file = meta_box(&[iinf(&[infe(1, b"av01", "", false)]), iloc]);
803
804 let meta = parse(&file).unwrap();
805 let error = meta.item_data(&file, &meta.items[0]).unwrap_err();
806 assert_eq!(error.code(), ErrorCode::Unsupported);
807 }
808
809 #[test]
810 fn references_are_read_and_queryable() {
811 let mut dimg = vec![];
812 dimg.extend_from_slice(&1_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes());
815 dimg.extend_from_slice(&3_u16.to_be_bytes());
816
817 let mut payload = vec![0, 0, 0, 0];
818 payload.extend_from_slice(&boxed(b"dimg", &dimg));
819 let iref = boxed(b"iref", &payload);
820
821 let file = meta_box(&[
822 iinf(&[infe(1, b"grid", "", false)]),
823 iloc(&[(1, 0, 1)]),
824 iref,
825 ]);
826 let meta = parse(&file).unwrap();
827 assert_eq!(meta.referenced(1, b"dimg"), &[2, 3]);
828 assert_eq!(meta.referenced(1, b"auxl"), &[] as &[u32]);
829 assert_eq!(meta.referenced(9, b"dimg"), &[] as &[u32]);
830 }
831
832 #[test]
835 fn only_an_alpha_urn_makes_an_auxiliary_item_the_alpha_plane() {
836 fn build(urn: &str) -> Vec<u8> {
837 let mut auxl = vec![];
838 auxl.extend_from_slice(&2_u16.to_be_bytes()); auxl.extend_from_slice(&1_u16.to_be_bytes());
840 auxl.extend_from_slice(&1_u16.to_be_bytes()); let mut iref_payload = vec![0, 0, 0, 0];
842 iref_payload.extend_from_slice(&boxed(b"auxl", &auxl));
843
844 let mut auxc_payload = vec![0, 0, 0, 0];
845 auxc_payload.extend_from_slice(urn.as_bytes());
846 auxc_payload.push(0);
847 let ipco = boxed(b"auxC", &auxc_payload);
848 let ipma_payload = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0x01];
850 let mut iprp = boxed(b"ipco", &ipco);
851 iprp.extend_from_slice(&boxed(b"ipma", &ipma_payload));
852
853 meta_box(&[
854 pitm(1),
855 iinf(&[
856 infe(1, b"av01", "color", false),
857 infe(2, b"av01", "alpha", true),
858 ]),
859 iloc(&[(1, 0, 1), (2, 0, 1)]),
860 boxed(b"iref", &iref_payload),
861 boxed(b"iprp", &iprp),
862 ])
863 }
864
865 let file = build(URN_ALPHA);
866 let meta = parse(&file).unwrap();
867 assert_eq!(meta.alpha_item(1).map(|item| item.id), Some(2));
868
869 let file = build(URN_ALPHA_LEGACY);
870 let meta = parse(&file).unwrap();
871 assert_eq!(meta.alpha_item(1).map(|item| item.id), Some(2));
872
873 let file = build("urn:mpeg:mpegB:cicp:systems:auxiliary:depth");
874 let meta = parse(&file).unwrap();
875 assert!(
876 meta.alpha_item(1).is_none(),
877 "a depth map must not be mistaken for an alpha plane"
878 );
879 }
880
881 #[test]
882 fn a_missing_pitm_falls_back_to_the_first_image_item() {
883 let file = meta_box(&[iinf(&[infe(4, b"av01", "", false)]), iloc(&[(4, 0, 1)])]);
884 let meta = parse(&file).unwrap();
885 assert_eq!(meta.primary, None);
886 assert_eq!(meta.primary_item().map(|item| item.id), Some(4));
887 }
888
889 #[test]
890 fn a_huge_declared_item_count_is_rejected_against_the_box_size() {
891 let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
892 payload.extend_from_slice(&0xFFFF_u16.to_be_bytes());
893 let file = meta_box(&[boxed(b"iloc", &payload)]);
894 let error = parse(&file).unwrap_err();
895 assert_eq!(error.code(), ErrorCode::Malformed);
896 assert!(error.to_string().contains("more than its"), "{error}");
897 }
898
899 #[test]
900 fn an_infe_too_old_to_carry_a_type_is_rejected() {
901 let mut payload = vec![1, 0, 0, 0];
902 payload.extend_from_slice(&1_u16.to_be_bytes());
903 payload.extend_from_slice(&[0, 0]);
904 let file = meta_box(&[iinf(&[boxed(b"infe", &payload)])]);
905 let error = parse(&file).unwrap_err();
906 assert_eq!(error.code(), ErrorCode::Malformed);
907 assert!(error.to_string().contains("item type"), "{error}");
908 }
909
910 #[test]
911 fn an_undefined_construction_method_is_rejected() {
912 let mut payload = vec![1, 0, 0, 0, 0x44, 0x00];
913 payload.extend_from_slice(&1_u16.to_be_bytes());
914 payload.extend_from_slice(&1_u16.to_be_bytes());
915 payload.extend_from_slice(&7_u16.to_be_bytes()); payload.extend_from_slice(&[0, 0]);
917 payload.extend_from_slice(&0_u16.to_be_bytes());
918 let file = meta_box(&[boxed(b"iloc", &payload)]);
919 let error = parse(&file).unwrap_err();
920 assert_eq!(error.code(), ErrorCode::Malformed);
921 assert!(error.to_string().contains("construction method"), "{error}");
922 }
923}