diff --git a/crates/clawhdf5/tests/header_validation_interop.rs b/crates/clawhdf5/tests/header_validation_interop.rs index 23509e1..14dc9d7 100644 --- a/crates/clawhdf5/tests/header_validation_interop.rs +++ b/crates/clawhdf5/tests/header_validation_interop.rs @@ -85,7 +85,9 @@ fn clawhdf5_reads(path: &Path) -> Result<(), String> { let ds = file.dataset("d").map_err(|e| format!("dataset: {e}"))?; ds.dtype().map_err(|e| format!("dtype: {e}"))?; ds.shape().map_err(|e| format!("shape: {e}"))?; - ds.read_i32().map(|_| ()).map_err(|e| format!("read: {e}")) + file.read_multi(&["d"]) + .map(|_| ()) + .map_err(|e| format!("read: {e}")) } /// h5py's verdict for each file must be `expected`, and clawhdf5 must read @@ -198,3 +200,72 @@ for libver in ("earliest", "latest"): assert!(err.to_string().contains("truncated file"), "{name}: {err}"); } } + +#[test] +fn header_and_datatype_damage_libhdf5_refuses_is_refused() { + skip_if_no_python!(); + let dir = tempfile::tempdir().unwrap(); + // Each case damages one field of a valid file h5py wrote (libver + // earliest, so version-1 object headers); all of these read before. + let verdicts = h5py_verdicts( + dir.path(), + r#" +def write(name, make): + path = os.path.join(d, name + ".h5") + with h5py.File(path, "w", libver="earliest") as f: + make(f) + return bytearray(open(path, "rb").read()) + +def save(name, data): + open(os.path.join(d, name + ".h5"), "wb").write(data) + +# A layout message (type 8, 24 bytes, v1 header) flagged shareable. +data = write("layout_good", lambda f: f.create_dataset("d", data=np.arange(4, dtype=" 0 +bad = bytearray(data); bad[at + 4] |= 0x40 +save("layout_shareable", bad) +# A message size that is not a multiple of 8 in a v1 header. +bad = bytearray(data); bad[at + 2] = 23 +save("layout_unaligned", bad) + +# A compound whose second field repeats the first's name, or overlaps it. +dt = np.dtype([("aa", "