fix(format): resolve shared fill value messages instead of zero-filling
dataset_fill_value treated a shared Fill Value message as "no fill value", so unwritten storage of a dataset whose fill value lives in the file's shared-message (SOHM) heap read as zeros rather than its fill value. libhdf5 shares fill values whenever the file has a SOHM index for them. - fill_value::dataset_fill_value_in follows the reference (another object header, or the SOHM heap); read_full_with_fill and the facade's selection read use it. - dataset_fill_value, which has no file to follow a reference into, now returns UnresolvedSharedMessage for a shared message instead of None. - shared_message::load_sohm_table / message_data_with_sohm load the SOHM table from the superblock extension on demand. - parse_sohm_table skipped each index's leading version byte, reading every field one byte off; SOHM references could never resolve. Fixture shared_fill_value.h5 (HDF5 2.0, gen_shared_fill.py): sohm_b read [0,1,2,3,0,0,0,0] and now reads [0,1,2,3,-7,-7,-7,-7], as h5py does. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -597,3 +597,50 @@ fn unknown_message_flags_follow_libhdf5_on_tbogus() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 8. shared fill value messages ----
|
||||
|
||||
#[test]
|
||||
fn shared_fill_value_is_resolved_not_zero() {
|
||||
// gen_shared_fill.py: HDF5 2.0 with a SOHM index for fill values, so each
|
||||
// dataset's fill value message is a reference into the SOHM heap. It
|
||||
// used to be read as "no fill value" (zeros) instead of -7.
|
||||
let bytes = include_bytes!("fixtures/shared_fill_value.h5");
|
||||
for (name, shared) in [
|
||||
("sohm_a", false),
|
||||
("sohm_b", true),
|
||||
("unwritten_a", false),
|
||||
("unwritten_b", true),
|
||||
] {
|
||||
let (sb, oh) = header_at(bytes, name);
|
||||
let msg = oh
|
||||
.messages
|
||||
.iter()
|
||||
.find(|m| m.msg_type == MessageType::FillValue)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
clawhdf5_format::shared_message::is_shared(msg.flags),
|
||||
shared,
|
||||
"{name}: fixture layout"
|
||||
);
|
||||
if shared {
|
||||
// Without the file the reference cannot be followed: an error,
|
||||
// never a silent default.
|
||||
assert_eq!(
|
||||
clawhdf5_format::fill_value::dataset_fill_value(&oh.messages),
|
||||
Err(clawhdf5_format::error::FormatError::UnresolvedSharedMessage)
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
clawhdf5_format::fill_value::dataset_fill_value_in(
|
||||
bytes,
|
||||
&oh.messages,
|
||||
sb.offset_size,
|
||||
sb.length_size
|
||||
)
|
||||
.unwrap(),
|
||||
Some((-7i32).to_le_bytes().to_vec()),
|
||||
"{name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user