fix: read array datatypes and chunked layouts from HDF5 2.0 (version 5)
Follow-up to the v5 compound fix, found by an interop sweep over diverse h5py/HDF5 2.0 (libver=latest) datasets: - Array datatype (class 10) version 5 was rejected. v3/v4/v5 share the same array encoding, so the parser now accepts 3-5. - Data Layout message version 5 was rejected, which broke EVERY chunked/ compressed dataset written by modern HDF5. v5 reuses the v4 message structure, so it now routes through parse_v4. Validated end-to-end: a gzip-compressed, Fixed-Array-indexed v5 chunked dataset now decodes to the correct values. Adds h5py-free regression tests using the real v5 array-datatype and chunked-layout bytes, and updates the layout invalid-version test to use v6. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -250,7 +250,9 @@ impl DataLayout {
|
|||||||
|
|
||||||
match version {
|
match version {
|
||||||
3 => Self::parse_v3(data, layout_class, offset_size, length_size),
|
3 => Self::parse_v3(data, layout_class, offset_size, length_size),
|
||||||
4 => Self::parse_v4(data, layout_class, offset_size, length_size),
|
// v5 (emitted by HDF5 1.14+/2.0 with `libver=latest`) uses the same
|
||||||
|
// message structure as v4 — only the version number was bumped.
|
||||||
|
4 | 5 => Self::parse_v4(data, layout_class, offset_size, length_size),
|
||||||
_ => Err(FormatError::InvalidLayoutVersion(version)),
|
_ => Err(FormatError::InvalidLayoutVersion(version)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,6 +628,30 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v5_chunked_from_hdf5_2_0() {
|
||||||
|
// Real data layout message from h5py 3.16 / HDF5 2.0 (`libver=latest`)
|
||||||
|
// for a gzip-compressed 1-D chunked dataset. Version 5 uses the same
|
||||||
|
// structure as v4 (here: chunked, Fixed Array index). Regression guard
|
||||||
|
// for reading modern-format chunked datasets.
|
||||||
|
let bytes: [u8; 17] = [
|
||||||
|
0x05, 0x02, 0x00, 0x02, 0x01, 0x0a, 0x08, 0x03, 0x0a, 0xef, 0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let layout = DataLayout::parse(&bytes, 8, 8).unwrap();
|
||||||
|
match layout {
|
||||||
|
DataLayout::Chunked {
|
||||||
|
chunk_dimensions,
|
||||||
|
chunk_index_type,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(chunk_dimensions, vec![10, 8]);
|
||||||
|
assert_eq!(chunk_index_type, Some(3)); // Fixed Array
|
||||||
|
}
|
||||||
|
other => panic!("expected Chunked, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v4_chunked_single_chunk_no_filters() {
|
fn v4_chunked_single_chunk_no_filters() {
|
||||||
let mut buf = vec![4u8, 2]; // version=4, class=2
|
let mut buf = vec![4u8, 2]; // version=4, class=2
|
||||||
@@ -678,9 +704,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_version() {
|
fn invalid_version() {
|
||||||
let buf = vec![5u8, 0, 0, 0];
|
// v3-v5 are supported; v6 is not a real layout message version.
|
||||||
|
let buf = vec![6u8, 0, 0, 0];
|
||||||
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
|
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
|
||||||
assert_eq!(err, FormatError::InvalidLayoutVersion(5));
|
assert_eq!(err, FormatError::InvalidLayoutVersion(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -503,7 +503,9 @@ impl Datatype {
|
|||||||
},
|
},
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
} else if version == 3 {
|
} else if (3..=5).contains(&version) {
|
||||||
|
// v3, v4 and v5 share the array encoding (ndims, dims, base
|
||||||
|
// type); HDF5 1.14+/2.0 with `libver=latest` emits v5.
|
||||||
ensure_len(data, pos, 1)?;
|
ensure_len(data, pos, 1)?;
|
||||||
let ndims = data[pos] as usize;
|
let ndims = data[pos] as usize;
|
||||||
pos += 1;
|
pos += 1;
|
||||||
@@ -1057,6 +1059,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_v5_from_hdf5_2_0() {
|
||||||
|
// Real datatype message from h5py 3.16 / HDF5 2.0 (`libver=latest`) for
|
||||||
|
// an array dtype `('f8', (3,))`: datatype version 5, class 10, reusing
|
||||||
|
// the v3 array encoding (ndims, dims, base type).
|
||||||
|
let bytes: [u8; 33] = [
|
||||||
|
0x5a, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x11,
|
||||||
|
0x20, 0x3f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x34, 0x0b, 0x00,
|
||||||
|
0x34, 0xff, 0x03, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let (dt, _) = Datatype::parse(&bytes).unwrap();
|
||||||
|
match dt {
|
||||||
|
Datatype::Array {
|
||||||
|
base_type,
|
||||||
|
dimensions,
|
||||||
|
} => {
|
||||||
|
assert_eq!(dimensions, vec![3]);
|
||||||
|
assert!(matches!(*base_type, Datatype::FloatingPoint { size: 8, .. }));
|
||||||
|
}
|
||||||
|
other => panic!("expected Array, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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