Three ways a parser can fail quietly: reject a valid file, misreport its own metadata, and misdecode a message it thinks it understood. OxiH5 0.2.0 closes all three.
Today we released OxiH5 0.2.0 — a release that adds full support for HDF5 superblock version 1 (previously an outright parse failure), decodes the superblock extension block that versions 2 and 3 carry (B-tree ‘K’ values, shared message table, file space info, and driver info messages), fixes File::info() to report the real on-disk superblock version instead of a hardcoded 0, and fixes a creation-order-tracking bug that could silently misdecode object header messages spilled into a continuation block.
No libhdf5. No FFI. No -sys crates. OxiH5 parses the published HDF5 binary format — superblock, object headers, B-trees, heaps, filter pipelines, all eleven datatype classes — directly from bytes, using nothing but std. #![forbid(unsafe_code)] guards oxih5-core; the facade crate carries exactly one documented unsafe block, the mmap call behind open_mmap. It compiles to a single static binary, needs no system libraries and no C toolchain at build time, and stays WASM-friendly by construction.
Why OxiH5 0.2.0 is a game changer
Even a Pure-Rust HDF5 reader that handles the common case still has to get every corner of the binary format right — because the corners are exactly where real-world files diverge from the fixtures a test suite happens to cover:
- A file written with superblock version 1 — a transitional format some writers emit that inserts 4 extra bytes after the File Consistency Flags, shifting the Base Address and Root Group Symbol Table Entry by +4 bytes — always failed outright with
OxiH5Error::UnsupportedSuperblock(1). - A v2/v3 superblock extension — the block that carries a file’s B-tree fan-out parameters, shared message table, file-space-management strategy, and driver info — had its address captured but never decoded; callers had no way to read what was actually in it.
File::info().superblock_versionalways reported0, regardless of which superblock version the file actually used — silently wrong self-reporting, not a crash.- Worst of all: an object header that tracks creation order and spills its messages into an OCHK continuation block had those messages misdecoded — the parser read the 2-byte creation-order field as message body data instead of skipping it, corrupting every message that followed in the block.
OxiH5 0.2.0 ends all of that.
- Superblock v1 parses correctly. New
oxih5_format::superblock::parse_v1handles the transitional 4-byte insertion; covered by 4 new unit tests (test_superblock_v1_parse,test_superblock_v1_nonzero_base_and_root,test_superblock_v1_too_short,test_superblock_v1_bad_offset_width). - Superblock extensions decode fully. New
oxih5_format::superblock::{SuperblockExtension, BtreeKValues}andread_superblock_extension()decode the extension object header’s B-tree ‘K’ Values (0x0013), Shared Message Table (0x000F), File Space Info (0x0018), and Driver Info (0x0014) messages — exposed at the facade level viaFile::superblock_extension()and re-exported asoxih5::{SuperblockExtension, BtreeKValues}. 9 new unit tests plus a new integration test,test_superblock_extension_none_on_v0_file. FileInfotells the truth about the file it just parsed.superblock_versionnow reports the real on-disk version (0, 1, 2, or 3) instead of a hardcoded0; a newsuperblock_extension_address: Option<u64>field exposes the extension’s location directly.- OCHK continuation decoding is correct under creation-order tracking. The owning object header’s
track_creation_orderflag is now threaded throughparse_v2_block→parse_v2_ochk_block, including nested continuations, so the 2-byte creation-order field is skipped instead of misread as data. New regression testtest_parse_messages_v2_ochk_continuation_creation_order.
Technical Deep Dive: how superblock v1, extension parsing, and the OCHK fix fit together
- Superblock parsing —
oxih5_format::superblock.parse()dispatches on the version byte read from the file;parse_v1now sits alongside the existing v0/v2/v3 paths, accounting for the transitional format’s extra 4-byte insertion before decoding the Base Address and Root Group Symbol Table Entry. - Extension decoding —
read_superblock_extension+header::parse_messages. A v2/v3 superblock’ssuperblock_extension_address(when not the undefined-address sentinel) points at an ordinary object header;read_superblock_extensionparses it through the sameheader::parse_messagespath used everywhere else, then interprets the four message types it currently understands into aSuperblockExtensionstruct — unrecognized message types are ignored rather than rejected. - Version/extension surfacing —
File::info()/File::superblock_extension().FileInfonow carries the realsuperblock_versionandsuperblock_extension_addressstraight from the parsedSuperblock, and the newFile::superblock_extension()facade method callsread_superblock_extensionon demand. - Creation-order threading —
parse_v2_block→parse_v2_ochk_block. The owning object header’s message flags (bit 2 = tracks creation order) now propagate into every OCHK continuation-block parse call, including continuations that themselves point at further continuations, so the per-message creation-order field is consistently skipped rather than only being handled correctly in the primary OHDR block.
Getting Started
cargo add oxih5
use oxih5::open;
let f = open("data.h5")?;
// New in 0.2.0: the real on-disk superblock version, not a hardcoded 0.
let info = f.info()?;
println!("superblock version: {}", info.superblock_version);
// New in 0.2.0: decode the v2/v3 superblock extension, if present.
if let Some(ext) = f.superblock_extension()? {
if let Some(k) = ext.btree_k {
println!(
"B-tree K values: indexed_storage={}, group_internal={}, group_leaf={}",
k.indexed_storage_internal_k, k.group_internal_k, k.group_leaf_k
);
}
println!("file space info present: {}", ext.file_space_info_present);
}
// Superblock v1 files (the "transitional" format) now open like any other.
let ds = f.dataset("/temperature")?;
let values: Vec<f32> = ds.as_f32()?;
What’s New in 0.2.0
- Superblock version 1 support — the “transitional” format (4 extra bytes after the File Consistency Flags) now parses instead of failing with
OxiH5Error::UnsupportedSuperblock(1), via newoxih5_format::superblock::parse_v1. - Superblock extension parsing (v2/v3) — new
SuperblockExtension/BtreeKValuestypes andread_superblock_extension()decode B-tree ‘K’ Values, Shared Message Table, File Space Info, and Driver Info messages; exposed viaFile::superblock_extension(). FileInfo::superblock_versionnow reports the real on-disk superblock version (0/1/2/3) instead of a hardcoded0; newFileInfo::superblock_extension_address: Option<u64>field.- Fixed: OCHK continuation blocks now correctly honor an owning object header’s creation-order-tracking flag instead of always assuming it’s off — preventing misdecoded messages in v2 object headers that both track creation order and spill into a continuation block.
- 494 tests pass (
--all-features; 473 with default features) across all four crates (~22.1k SLOC).
Tips
- Check
info()?.superblock_versionif you need to branch on file provenance. It’s no longer hardcoded to0— files written with any HDF5 writer that emits v1/v2/v3 superblocks now report their real version. superblock_extension()returnsOk(None), not an error, on v0/v1 files or on v2/v3 files without an extension. Only v2/v3 files can carry one at all; check forSomebefore reading its fields.SuperblockExtension’s fields are all independently optional. A file’s extension may carry any subset of the four supported message types —btree_k,shared_message_table_address,file_space_info_present/file_space_strategy, anddriver_info_presentare eachNone/falsewhen absent, never a guessed default.- If you previously worked around superblock v1 files failing to open, you can drop that workaround.
UnsupportedSuperblock(1)no longer fires for well-formed v1 files. - If you decoded object headers with creation-order tracking before 0.2.0, re-verify any that were large enough to spill into an OCHK continuation block. The bug only manifested when both conditions held at once — creation-order tracking alone, or a continuation block alone, always decoded correctly.
This is the foundation
OxiH5 belongs to NoFFI — the COOLJAPAN initiative retiring every C/C++/Fortran/-sys FFI dependency in the Rust ecosystem with a clean, memory-safe, Pure-Rust implementation. OxiH5’s assignment is hdf5-sys/hdf5 (and netcdf-sys on the read path); 0.2.0’s superblock v1 and extension support close two more gaps against real-world files that earlier versions couldn’t open or fully introspect.
It already has real consumers pinned at 0.2.0 across the workspace:
oxinetcdf— ships in the same workspace, reading NetCDF-4 conventions (NcFile,NcVariable,NcDimension) on top of the parser this release hardened.- OxiGDAL — the Pure-Rust GDAL replacement depends on both
oxih5andoxinetcdfat 0.2.0 for its HDF5/NetCDF raster paths. - OxiProj — the Pure-Rust PROJ replacement depends on
oxih50.2.0 as well. - SciRS2 —
scirs2-ioandscirs2-datasetsdepend onoxih5-core/oxih5-format0.2.0, staged behind an optionalhdf5_iofeature alongside the Chdf5crate until OxiH5’s write support matures far enough to drop it.
Repository: https://github.com/cool-japan/oxih5
Star the repo ⭐ if you want an HDF5 reader that gets superblock introspection right — the real version, the real extension contents — without libhdf5 anywhere in the build.
The era of a parser that either rejects a valid file or quietly hands back wrong metadata is over. Pure Rust HDF5 is here — and as of 0.2.0, it knows exactly which superblock it just read.
— KitaSan at COOLJAPAN OÜ July 18, 2026