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()
}
}