fix: read compound datatypes from HDF5 1.14+/2.0 (datatype version 5)
clawhdf5 rejected datatype message version 5 for the compound class with "invalid datatype version 5 for class 6", so it could not read compound datasets written by modern HDF5 / h5py with libver=latest. v5 reuses the same compact member encoding as v3/v4 (name, variable-width offset, member type), so the parser now accepts versions 3-5 for compound. Found by running the previously-ignored h5py interop tests against h5py 3.16 / HDF5 2.0. Adds an h5py-free regression test using the real v5 datatype bytes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -348,7 +348,10 @@ impl Datatype {
|
|||||||
let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
|
let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
|
||||||
let mut members = Vec::with_capacity(num_members as usize);
|
let mut members = Vec::with_capacity(num_members as usize);
|
||||||
|
|
||||||
if version == 3 || version == 4 {
|
if (3..=5).contains(&version) {
|
||||||
|
// v3, v4 and v5 share the compact member encoding (name,
|
||||||
|
// variable-width offset, member datatype). HDF5 1.14+/2.0
|
||||||
|
// with `libver=latest` emits v5 compound types.
|
||||||
let ob = offset_bytes_for_size(size);
|
let ob = offset_bytes_for_size(size);
|
||||||
for _ in 0..num_members {
|
for _ in 0..num_members {
|
||||||
let (name, name_len) = read_null_terminated_string(data, pos)?;
|
let (name, name_len) = read_null_terminated_string(data, pos)?;
|
||||||
@@ -1007,6 +1010,53 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compound_v5_from_hdf5_2_0() {
|
||||||
|
// Real datatype message bytes emitted by h5py 3.16 / HDF5 2.0 with
|
||||||
|
// `libver=latest` for a compound dtype [('x','f8'),('y','f8'),('id','i4')].
|
||||||
|
// The wrapper is datatype version 5; members reuse the v3 compact
|
||||||
|
// encoding. Regression guard for reading modern-format compound types.
|
||||||
|
let bytes: [u8; 70] = [
|
||||||
|
0x56, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x11, 0x20, 0x3f,
|
||||||
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x34, 0x0b, 0x00, 0x34, 0xff,
|
||||||
|
0x03, 0x00, 0x00, 0x79, 0x00, 0x08, 0x11, 0x20, 0x3f, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x40, 0x00, 0x34, 0x0b, 0x00, 0x34, 0xff, 0x03, 0x00, 0x00, 0x69, 0x64,
|
||||||
|
0x00, 0x10, 0x10, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
|
||||||
|
];
|
||||||
|
let (dt, _) = Datatype::parse(&bytes).unwrap();
|
||||||
|
match dt {
|
||||||
|
Datatype::Compound { size, members } => {
|
||||||
|
assert_eq!(size, 20);
|
||||||
|
assert_eq!(members.len(), 3);
|
||||||
|
assert_eq!(
|
||||||
|
(members[0].name.as_str(), members[0].byte_offset),
|
||||||
|
("x", 0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(members[1].name.as_str(), members[1].byte_offset),
|
||||||
|
("y", 8)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(members[2].name.as_str(), members[2].byte_offset),
|
||||||
|
("id", 16)
|
||||||
|
);
|
||||||
|
assert!(matches!(
|
||||||
|
members[0].datatype,
|
||||||
|
Datatype::FloatingPoint { size: 8, .. }
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
members[2].datatype,
|
||||||
|
Datatype::FixedPoint {
|
||||||
|
size: 4,
|
||||||
|
signed: true,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => panic!("expected Compound"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reference_object() {
|
fn test_reference_object() {
|
||||||
let buf = build_dt_header(7, 1, [0, 0, 0], 8);
|
let buf = build_dt_header(7, 1, [0, 0, 0], 8);
|
||||||
|
|||||||
Reference in New Issue
Block a user