fix(format): locate the address in version-1 shared messages
A version-1 shared message reference is version, type, six reserved bytes and then an old-style symbol table entry: link-name offset (length size), object header address, cache type, reserved, scratch. We read the address straight after the reserved bytes, i.e. the link-name offset, and the committed datatype lookup failed with InvalidObjectHeaderVersion (the bytes checked in tcompound.h5: name offset 0x10, then 0x590 = /type1). Datasets of 1.4/1.6-era files that use a committed datatype were unreadable. Skip the name offset. parse_shared_ref has no length size, so add parse_shared_ref_sized and use it in every internal caller; parse_shared_ref keeps its signature and assumes length size == offset size. The old parse_v1_ref unit test encoded the wrong layout and now uses the real bytes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -154,13 +154,29 @@ pub fn is_shared(msg_flags: u8) -> bool {
|
||||
///
|
||||
/// When the shared flag is set on a message, the data contains a reference
|
||||
/// instead of the actual message content.
|
||||
///
|
||||
/// Assumes the file's length size equals its offset size, which only matters
|
||||
/// for version-1 references; use [`parse_shared_ref_sized`] when the
|
||||
/// superblock's length size is known.
|
||||
pub fn parse_shared_ref(data: &[u8], offset_size: u8) -> Result<SharedMessageRef, FormatError> {
|
||||
parse_shared_ref_sized(data, offset_size, offset_size)
|
||||
}
|
||||
|
||||
/// [`parse_shared_ref`] with the superblock's length size, which locates the
|
||||
/// object header address in a version-1 reference.
|
||||
pub fn parse_shared_ref_sized(
|
||||
data: &[u8],
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<SharedMessageRef, FormatError> {
|
||||
ensure_len(data, 0, 2)?;
|
||||
let version = data[0];
|
||||
let ref_type = data[1];
|
||||
|
||||
// Layouts (HDF5 spec IV.A.2 "Shared Message", and libhdf5's decoder):
|
||||
// v1: version, type, reserved(6), address — always "committed"
|
||||
// v1: version, type, reserved(6), then an old-style symbol table
|
||||
// entry: link-name offset(length_size), object header address,
|
||||
// cache type(4), reserved(4), scratch(16) — always "committed"
|
||||
// v2: version, type, address — always "committed"
|
||||
// v3: version, type, then a fractal-heap ID if type == SOHM, otherwise
|
||||
// an address
|
||||
@@ -177,7 +193,7 @@ pub fn parse_shared_ref(data: &[u8], offset_size: u8) -> Result<SharedMessageRef
|
||||
})
|
||||
};
|
||||
match version {
|
||||
1 => address_at(2 + 6),
|
||||
1 => address_at(2 + 6 + length_size as usize),
|
||||
2 => address_at(2),
|
||||
3 if ref_type == SHARE_TYPE_SOHM => {
|
||||
ensure_len(data, 2, FHEAP_ID_LEN)?;
|
||||
@@ -434,7 +450,7 @@ pub fn message_data_with_sohm<'a>(
|
||||
if !is_shared(msg.flags) {
|
||||
return Ok(Cow::Borrowed(&msg.data));
|
||||
}
|
||||
let shared_ref = parse_shared_ref(&msg.data, offset_size)?;
|
||||
let shared_ref = parse_shared_ref_sized(&msg.data, offset_size, length_size)?;
|
||||
let table = if shared_ref.heap_id.is_some() {
|
||||
load_sohm_table(file_data, offset_size, length_size)?
|
||||
} else {
|
||||
@@ -514,7 +530,7 @@ pub fn message_data<'a>(
|
||||
if !is_shared(msg.flags) {
|
||||
return Ok(Cow::Borrowed(&msg.data));
|
||||
}
|
||||
let shared_ref = parse_shared_ref(&msg.data, offset_size)?;
|
||||
let shared_ref = parse_shared_ref_sized(&msg.data, offset_size, length_size)?;
|
||||
resolve_shared_message(
|
||||
file_data,
|
||||
&shared_ref,
|
||||
@@ -649,15 +665,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_v1_ref() {
|
||||
let mut data = Vec::new();
|
||||
data.push(1); // version
|
||||
data.push(0); // type
|
||||
data.extend_from_slice(&[0u8; 6]); // reserved
|
||||
data.extend_from_slice(&0x5678u64.to_le_bytes());
|
||||
// Datatype message of `/group1/dset2` in HDF5's `tcompound.h5`
|
||||
// (written in 2000): version 1, six reserved bytes, then an old-style
|
||||
// symbol table entry — link-name offset 0x10, object header address
|
||||
// 0x590 (the committed datatype `/type1`), cache type, reserved and
|
||||
// scratch.
|
||||
let mut data = vec![1, 0, 0, 0, 0, 0, 0, 0];
|
||||
data.extend_from_slice(&0x10u64.to_le_bytes());
|
||||
data.extend_from_slice(&0x590u64.to_le_bytes());
|
||||
data.extend_from_slice(&[0; 24]);
|
||||
|
||||
let shared = parse_shared_ref(&data, 8).unwrap();
|
||||
let shared = parse_shared_ref_sized(&data, 8, 8).unwrap();
|
||||
assert_eq!(shared.version, 1);
|
||||
assert_eq!(shared.object_header_address, Some(0x5678));
|
||||
assert_eq!(shared.object_header_address, Some(0x590));
|
||||
|
||||
// The name offset is a length: 4 bytes here, then an 8-byte address.
|
||||
let mut data = vec![1, 0, 0, 0, 0, 0, 0, 0];
|
||||
data.extend_from_slice(&0x10u32.to_le_bytes());
|
||||
data.extend_from_slice(&0x590u64.to_le_bytes());
|
||||
let shared = parse_shared_ref_sized(&data, 8, 4).unwrap();
|
||||
assert_eq!(shared.object_header_address, Some(0x590));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user