fn read_iprp()

in mp4parse/src/lib.rs [2969:3219]


fn read_iprp<T: Read>(
    src: &mut BMFFBox<T>,
    brand: FourCC,
    strictness: ParseStrictness,
    unsupported_features: &mut UnsupportedFeatures,
) -> Result<ItemPropertiesBox> {
    let mut iter = src.box_iter();

    let properties = match iter.next_box()? {
        Some(mut b) if b.head.name == BoxType::ItemPropertyContainerBox => {
            read_ipco(&mut b, strictness)
        }
        Some(_) => Status::IprpBadChild.into(),
        None => Err(Error::UnexpectedEOF),
    }?;

    let mut ipma_version_and_flag_values_seen = TryVec::with_capacity(1)?;
    let mut association_entries = TryVec::<ItemPropertyAssociationEntry>::new();
    let mut forbidden_items = TryVec::new();

    while let Some(mut b) = iter.next_box()? {
        if b.head.name != BoxType::ItemPropertyAssociationBox {
            return Status::IprpBadChild.into();
        }

        let (version, flags) = read_fullbox_extra(&mut b)?;
        if ipma_version_and_flag_values_seen.contains(&(version, flags)) {
            fail_with_status_if(
                strictness != ParseStrictness::Permissive,
                Status::IpmaBadQuantity,
            )?;
        }
        if flags != 0 && properties.len() <= 127 {
            fail_with_status_if(
                strictness == ParseStrictness::Strict,
                Status::IpmaFlagsNonzero,
            )?;
        }
        ipma_version_and_flag_values_seen.push((version, flags))?;
        for association_entry in read_ipma(&mut b, strictness, version, flags)? {
            if forbidden_items.contains(&association_entry.item_id) {
                warn!(
                    "Skipping {:?} since the item referenced shall not be processed",
                    association_entry
                );
            }

            if let Some(previous_entry) = association_entries
                .iter()
                .find(|e| association_entry.item_id == e.item_id)
            {
                error!(
                    "Duplicate ipma entries for item_id\n1: {:?}\n2: {:?}",
                    previous_entry, association_entry
                );
                // It's technically possible to make sense of this situation by merging ipma
                // boxes, but this is a "shall" requirement, so we'd only do it in
                // ParseStrictness::Permissive mode, and this hasn't shown up in the wild
                return Status::IpmaDuplicateItemId.into();
            }

            const TRANSFORM_ORDER: &[BoxType] = &[
                BoxType::ImageSpatialExtentsProperty,
                BoxType::CleanApertureBox,
                BoxType::ImageRotation,
                BoxType::ImageMirror,
            ];
            let mut prev_transform_index = None;
            // Realistically, there should only ever be 1 nclx and 1 icc
            let mut colour_type_indexes: TryHashMap<FourCC, PropertyIndex> =
                TryHashMap::with_capacity(2)?;

            for a in &association_entry.associations {
                if a.property_index == PropertyIndex(0) {
                    if a.essential {
                        fail_with_status_if(
                            strictness != ParseStrictness::Permissive,
                            Status::IpmaIndexZeroNoEssential,
                        )?;
                    }
                    continue;
                }

                if let Some(property) = properties.get(&a.property_index) {
                    assert!(brand == MIF1_BRAND);

                    let feature = Feature::try_from(property);
                    let property_supported = match feature {
                        Ok(feature) => {
                            if feature.supported() {
                                true
                            } else {
                                unsupported_features.insert(feature);
                                false
                            }
                        }
                        Err(_) => false,
                    };

                    if !property_supported {
                        if a.essential && strictness != ParseStrictness::Permissive {
                            error!("Unsupported essential property {:?}", property);
                            forbidden_items.push(association_entry.item_id)?;
                        } else {
                            debug!(
                                "Ignoring unknown {} property {:?}",
                                if a.essential {
                                    "essential"
                                } else {
                                    "non-essential"
                                },
                                property
                            );
                        }
                    }

                    // Check additional requirements on specific properties
                    match property {
                        ItemProperty::AV1Config(_)
                        | ItemProperty::CleanAperture
                        | ItemProperty::Mirroring(_)
                        | ItemProperty::Rotation(_) => {
                            if !a.essential {
                                warn!("{:?} is missing required 'essential' bit", property);
                                // This is a "shall", but it is likely to change, so only
                                // fail if using strict parsing.
                                // See https://github.com/mozilla/mp4parse-rust/issues/284
                                fail_with_status_if(
                                    strictness == ParseStrictness::Strict,
                                    Status::TxformNoEssential,
                                )?;
                            }
                        }

                        // NOTE: this is contrary to the published specification; see doc comment
                        // at the beginning of this function for more details
                        ItemProperty::Colour(colr) => {
                            let colour_type = colr.colour_type();
                            if let Some(prev_colr_index) = colour_type_indexes.get(&colour_type) {
                                warn!(
                                    "Multiple '{}' type colr associations with {:?}: {:?} and {:?}",
                                    colour_type,
                                    association_entry.item_id,
                                    a.property_index,
                                    prev_colr_index
                                );
                                fail_with_status_if(
                                    strictness != ParseStrictness::Permissive,
                                    Status::ColrBadQuantity,
                                )?;
                            } else {
                                colour_type_indexes.insert(colour_type, a.property_index)?;
                            }
                        }

                        // The following properties are unsupported, but we still enforce that
                        // they've been correctly marked as essential or not.
                        ItemProperty::LayeredImageIndexing => {
                            assert!(feature.is_ok() && unsupported_features.contains(feature?));
                            if a.essential {
                                fail_with_status_if(
                                    strictness != ParseStrictness::Permissive,
                                    Status::A1lxEssential,
                                )?;
                            }
                        }

                        ItemProperty::LayerSelection => {
                            assert!(feature.is_ok() && unsupported_features.contains(feature?));
                            if a.essential {
                                assert!(
                                    forbidden_items.contains(&association_entry.item_id)
                                        || strictness == ParseStrictness::Permissive
                                );
                            } else {
                                fail_with_status_if(
                                    strictness != ParseStrictness::Permissive,
                                    Status::LselNoEssential,
                                )?;
                            }
                        }

                        ItemProperty::OperatingPointSelector => {
                            assert!(feature.is_ok() && unsupported_features.contains(feature?));
                            if a.essential {
                                assert!(
                                    forbidden_items.contains(&association_entry.item_id)
                                        || strictness == ParseStrictness::Permissive
                                );
                            } else {
                                fail_with_status_if(
                                    strictness != ParseStrictness::Permissive,
                                    Status::A1opNoEssential,
                                )?;
                            }
                        }

                        other_property => {
                            trace!("No additional checks for {:?}", other_property);
                        }
                    }

                    if let Some(transform_index) = TRANSFORM_ORDER
                        .iter()
                        .position(|t| *t == BoxType::from(property))
                    {
                        if let Some(prev) = prev_transform_index {
                            if prev >= transform_index {
                                error!(
                                    "Invalid property order: {:?} after {:?}",
                                    TRANSFORM_ORDER[transform_index], TRANSFORM_ORDER[prev]
                                );
                                fail_with_status_if(
                                    strictness != ParseStrictness::Permissive,
                                    if TRANSFORM_ORDER[transform_index]
                                        == BoxType::ImageSpatialExtentsProperty
                                    {
                                        Status::TxformBeforeIspe
                                    } else {
                                        Status::TxformOrder
                                    },
                                )?;
                            }
                        }
                        prev_transform_index = Some(transform_index);
                    }
                } else {
                    error!(
                        "Missing property at {:?} for {:?}",
                        a.property_index, association_entry.item_id
                    );
                    fail_with_status_if(
                        strictness != ParseStrictness::Permissive,
                        Status::IpmaBadIndex,
                    )?;
                }
            }
            association_entries.push(association_entry)?
        }

        check_parser_state!(b.content);
    }

    let iprp = ItemPropertiesBox {
        properties,
        association_entries,
        forbidden_items,
    };
    trace!("read_iprp -> {:#?}", iprp);
    Ok(iprp)
}