fn read_iloc()

in mp4parse/src/lib.rs [3937:4058]


fn read_iloc<T: Read>(src: &mut BMFFBox<T>) -> Result<TryHashMap<ItemId, ItemLocationBoxItem>> {
    let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?;

    let iloc = src.read_into_try_vec()?;
    let mut iloc = BitReader::new(&iloc);

    let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
    let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
    let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;

    let index_size: Option<IlocFieldSize> = match version {
        IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
        IlocVersion::Zero => {
            let _reserved = iloc.read_u8(4)?;
            None
        }
    };

    let item_count = match version {
        IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
        IlocVersion::Two => iloc.read_u32(32)?,
    };

    let mut items = TryHashMap::with_capacity(item_count.to_usize())?;

    for _ in 0..item_count {
        let item_id = ItemId(match version {
            IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
            IlocVersion::Two => iloc.read_u32(32)?,
        });

        // The spec isn't entirely clear how an `iloc` should be interpreted for version 0,
        // which has no `construction_method` field. It does say:
        // "For maximum compatibility, version 0 of this box should be used in preference to
        //  version 1 with `construction_method==0`, or version 2 when possible."
        // We take this to imply version 0 can be interpreted as using file offsets.
        let construction_method = match version {
            IlocVersion::Zero => ConstructionMethod::File,
            IlocVersion::One | IlocVersion::Two => {
                let _reserved = iloc.read_u16(12)?;
                match iloc.read_u16(4)? {
                    0 => ConstructionMethod::File,
                    1 => ConstructionMethod::Idat,
                    2 => ConstructionMethod::Item,
                    _ => return Status::IlocBadConstructionMethod.into(),
                }
            }
        };

        let data_reference_index = iloc.read_u16(16)?;
        if data_reference_index != 0 {
            return Err(Error::Unsupported(
                "external file references (iloc.data_reference_index != 0) are not supported",
            ));
        }
        let base_offset = iloc.read_u64(base_offset_size.as_bits())?;
        let extent_count = iloc.read_u16(16)?;

        if extent_count < 1 {
            return Status::IlocBadExtentCount.into();
        }

        // "If only one extent is used (extent_count = 1) then either or both of the
        //  offset and length may be implied"
        if extent_count != 1
            && (offset_size == IlocFieldSize::Zero || length_size == IlocFieldSize::Zero)
        {
            return Status::IlocBadExtent.into();
        }

        let mut extents = TryVec::with_capacity(extent_count.to_usize())?;

        for _ in 0..extent_count {
            // Parsed but currently ignored, see `Extent`
            let _extent_index = match &index_size {
                None | Some(IlocFieldSize::Zero) => None,
                Some(index_size) => {
                    debug_assert!(version == IlocVersion::One || version == IlocVersion::Two);
                    Some(iloc.read_u64(index_size.as_bits())?)
                }
            };

            // Per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1:
            // "If the offset is not identified (the field has a length of zero), then the
            //  beginning of the source (offset 0) is implied"
            // This behavior will follow from BitReader::read_u64(0) -> 0.
            let extent_offset = iloc.read_u64(offset_size.as_bits())?;
            let extent_length = iloc.read_u64(length_size.as_bits())?.try_into()?;

            // "If the length is not specified, or specified as zero, then the entire length of
            //  the source is implied" (ibid)
            let offset = base_offset
                .checked_add(extent_offset)
                .ok_or_else(|| Error::from(Status::IlocOffsetOverflow))?;
            let extent = if extent_length == 0 {
                Extent::ToEnd { offset }
            } else {
                Extent::WithLength {
                    offset,
                    len: extent_length,
                }
            };

            extents.push(extent)?;
        }

        let loc = ItemLocationBoxItem {
            construction_method,
            extents,
        };

        if items.insert(item_id, loc)?.is_some() {
            return Status::IlocDuplicateItemId.into();
        }
    }

    if iloc.remaining() == 0 {
        Ok(items)
    } else {
        Status::IlocBadSize.into()
    }
}