test: compare header and datatype damage with what h5py refuses

h5py writes a dataset (libver earliest, so version-1 object headers) and
the script damages one field of a copy: a layout message flagged
shareable, a message size that is not a multiple of 8, a compound field
that repeats an earlier name or overlaps it, an empty enum member name, a
float exponent overlapping the mantissa. h5py refuses every damaged copy,
and clawhdf5 must refuse exactly those and read the valid files. All of
them but the unaligned one (then an UnexpectedEof) read before this
branch. The helper now reads any datatype
(File::read_multi) rather than only integers.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 00:35:41 -05:00
co-authored by Claude Opus 5.5
parent bb39be7f24
commit a59d83d47d
@@ -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="<i4")))
at = data.find(bytes([8, 0, 24, 0]))
assert at > 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", "<i4"), ("bb", "<i4")])
data = write("compound_good", lambda f: f.create_dataset("d", data=np.zeros(2, dt)))
at = data.find(b"bb\0")
bad = bytearray(data); bad[at:at + 2] = b"aa"
save("compound_duplicate", bad)
bad = bytearray(data); bad[at + 8:at + 12] = struct.pack("<I", 2)
save("compound_overlap", bad)
# An enum member with an empty name.
et = h5py.enum_dtype({"RED": 0, "GREEN": 1}, basetype="i4")
data = write("enum_good", lambda f: f.create_dataset("d", data=np.zeros(2, "<i4"), dtype=et))
at = data.find(b"RED\0")
bad = bytearray(data); bad[at:at + 3] = b"\0\0\0"
save("enum_empty_name", bad)
# A float whose exponent overlaps its mantissa (exponent at bit 20).
data = write("float_good", lambda f: f.create_dataset("d", data=np.zeros(3, "<f4")))
at = data.find(bytes([0, 0, 32, 0, 23, 8, 0, 23]))
bad = bytearray(data); bad[at + 4] = 20
save("float_overlap", bad)
"#,
);
assert_agrees_with_h5py(
dir.path(),
&verdicts,
&[
"compound_duplicate ERROR",
"compound_good ok",
"compound_overlap ERROR",
"enum_empty_name ERROR",
"enum_good ok",
"float_good ok",
"float_overlap ERROR",
"layout_good ok",
"layout_shareable ERROR",
"layout_unaligned ERROR",
],
);
}