format: VlResolver::element_in and string_element_in over any Storage

VlResolver::element and string_element return slices of the whole file,
so they exist only for a resolver over &[u8]. Their *_in forms work for
any Storage (a remote file): the element's bytes borrowed from the
resolver's cache of heap collections, with the same null-element, NUL
and size checks. h5rs decodes variable-length values with them.

Test: over a read_at-only storage they give what element/string_element
give over the slice, for a string with an embedded NUL, a null element
and an element whose heap object has the wrong size.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 17:20:50 -05:00
co-authored by Claude Opus 5.5
parent c54c64cc9b
commit a4f586e657
+44
View File
@@ -305,6 +305,24 @@ impl<'a, S: crate::storage::Storage + ?Sized> VlResolver<'a, S> {
Ok(Some(data))
}
/// [`VlResolver::element`] over any storage: the element's bytes
/// (borrowed from the resolver's cache of heap collections, so they
/// live until the next call), or `None` for a null element.
pub fn element_in(
&mut self,
elem: &[u8],
base_size: usize,
) -> Result<Option<&[u8]>, FormatError> {
let vl = parse_vl_references(elem, 1, self.offset_size)?;
self.resolve(&vl[0], base_size)
}
/// [`VlResolver::string_element`] over any storage (see
/// [`element_in`](Self::element_in)).
pub fn string_element_in(&mut self, elem: &[u8]) -> Result<Option<&[u8]>, FormatError> {
Ok(self.element_in(elem, 1)?.map(cut_at_nul))
}
/// The strings of the variable-length string elements in `raw`, as
/// bytes. A string ends at its first NUL, as libhdf5 returns it (it
/// converts each to a C string); a null element is empty.
@@ -645,6 +663,32 @@ mod tests {
}
}
#[test]
fn element_in_over_a_storage_matches_element_over_a_slice() {
let mut file_data = vec![0u8; 512];
build_gcol_at(&mut file_data, 256, &[(1, b"Alice\0x"), (2, b"Bob")]);
let mut raw = build_vl_refs(&["Alice\0x", "Bob"], 256, 1, 8);
raw.extend(element(0, 0, 0, 8)); // null
raw.extend(element(9, 256, 1, 8)); // wrong length: an error
let storage = crate::storage::CountingStorage::new(file_data.clone());
let dynamic: &dyn crate::storage::Storage = &storage;
let mut slice = VlResolver::new(&file_data, 8, 8);
let mut any = VlResolver::new_in(dynamic, 8, 8);
for e in raw.chunks(16) {
let want = slice.element(e, 1).map(|o| o.map(<[u8]>::to_vec));
let got = any.element_in(e, 1).map(|o| o.map(<[u8]>::to_vec));
assert_eq!(format!("{want:?}"), format!("{got:?}"));
let want = slice.string_element(e).map(|o| o.map(<[u8]>::to_vec));
let got = any.string_element_in(e).map(|o| o.map(<[u8]>::to_vec));
assert_eq!(format!("{want:?}"), format!("{got:?}"));
}
assert_eq!(
any.string_element_in(&raw[..16]).unwrap(),
Some(&b"Alice"[..])
);
assert!(storage.reads() > 0);
}
#[test]
fn null_vl_element_zero_address() {
let mut raw = Vec::new();