use std::io::{Read, Seek};
use crate::error::{Error, Result};
use crate::format::checksum::checksum_metadata;
use crate::io::reader::HdfReader;
const OHDR_MAGIC: [u8; 4] = [b'O', b'H', b'D', b'R'];
const OCHK_MAGIC: [u8; 4] = [b'O', b'C', b'H', b'K'];
pub const HDR_CHUNK0_SIZE_MASK: u8 = 0x03;
pub const HDR_ATTR_CRT_ORDER_TRACKED: u8 = 0x04;
pub const HDR_ATTR_CRT_ORDER_INDEXED: u8 = 0x08;
pub const HDR_ATTR_STORE_PHASE_CHANGE: u8 = 0x10;
pub const HDR_STORE_TIMES: u8 = 0x20;
pub const MSG_NIL: u16 = 0x0000;
pub const MSG_DATASPACE: u16 = 0x0001;
pub const MSG_LINK_INFO: u16 = 0x0002;
pub const MSG_DATATYPE: u16 = 0x0003;
pub const MSG_FILL_VALUE_OLD: u16 = 0x0004;
pub const MSG_FILL_VALUE: u16 = 0x0005;
pub const MSG_LINK: u16 = 0x0006;
pub const MSG_EXTERNAL_FILE_LIST: u16 = 0x0007;
pub const MSG_LAYOUT: u16 = 0x0008;
pub const MSG_BOGUS: u16 = 0x0009;
pub const MSG_GROUP_INFO: u16 = 0x000A;
pub const MSG_FILTER_PIPELINE: u16 = 0x000B;
pub const MSG_ATTRIBUTE: u16 = 0x000C;
pub const MSG_OBJ_COMMENT: u16 = 0x000D;
pub const MSG_OBJ_MOD_TIME_OLD: u16 = 0x000E;
pub const MSG_SHARED_MSG_TABLE: u16 = 0x000F;
pub const MSG_HEADER_CONTINUATION: u16 = 0x0010;
pub const MSG_SYMBOL_TABLE: u16 = 0x0011;
pub const MSG_OBJ_MOD_TIME: u16 = 0x0012;
pub const MSG_BTREE_K: u16 = 0x0013;
pub const MSG_DRIVER_INFO: u16 = 0x0014;
pub const MSG_ATTR_INFO: u16 = 0x0015;
pub const MSG_OBJ_REF_COUNT: u16 = 0x0016;
pub const MSG_FILE_SPACE_INFO: u16 = 0x0017;
#[derive(Debug, Clone)]
pub struct RawMessage {
pub msg_type: u16,
pub flags: u8,
pub creation_index: Option<u16>,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct ObjectHeader {
pub version: u8,
pub flags: u8,
pub refcount: u32,
pub atime: Option<u32>,
pub mtime: Option<u32>,
pub ctime: Option<u32>,
pub btime: Option<u32>,
pub max_compact_attrs: Option<u16>,
pub min_dense_attrs: Option<u16>,
pub messages: Vec<RawMessage>,
}
impl ObjectHeader {
pub fn read_at<R: Read + Seek>(reader: &mut HdfReader<R>, addr: u64) -> Result<Self> {
reader.seek(addr)?;
let first_bytes = reader.read_bytes(4)?;
if first_bytes == OHDR_MAGIC {
Self::read_v2(reader, addr)
} else {
reader.seek(addr)?;
Self::read_v1(reader)
}
}
fn read_v1<R: Read + Seek>(reader: &mut HdfReader<R>) -> Result<Self> {
let version = reader.read_u8()?;
if version != 1 {
return Err(Error::InvalidFormat(format!(
"expected object header v1, got {version}"
)));
}
reader.skip(1)?;
let num_messages = reader.read_u16()?;
let refcount = reader.read_u32()?;
let chunk_data_size = reader.read_u32()? as u64;
reader.skip(4)?;
let chunk_start = reader.position()?;
let chunk_end = chunk_start + chunk_data_size;
let mut messages = Vec::new();
let mut continuations = Vec::new();
Self::read_v1_messages(reader, chunk_end, num_messages, &mut messages, &mut continuations)?;
for (cont_addr, cont_len) in continuations {
Self::read_v1_continuation(reader, cont_addr, cont_len, &mut messages)?;
}
Ok(ObjectHeader {
version: 1,
flags: 0,
refcount,
atime: None,
mtime: None,
ctime: None,
btime: None,
max_compact_attrs: None,
min_dense_attrs: None,
messages,
})
}
fn read_v1_messages<R: Read + Seek>(
reader: &mut HdfReader<R>,
chunk_end: u64,
_num_messages: u16,
messages: &mut Vec<RawMessage>,
continuations: &mut Vec<(u64, u64)>,
) -> Result<()> {
while reader.position()? < chunk_end {
let pos = reader.position()?;
if pos + 8 > chunk_end {
break;
}
let msg_type = reader.read_u16()?;
let msg_size = reader.read_u16()? as u64;
let msg_flags = reader.read_u8()?;
reader.skip(3)?;
let aligned_size = (msg_size + 7) & !7;
if msg_type == MSG_NIL {
reader.skip(aligned_size)?;
continue;
}
if msg_type == MSG_HEADER_CONTINUATION {
let cont_offset = reader.read_addr()?;
let cont_length = reader.read_length()?;
let remaining = aligned_size - reader.sizeof_addr() as u64 - reader.sizeof_size() as u64;
if remaining > 0 {
reader.skip(remaining)?;
}
continuations.push((cont_offset, cont_length));
continue;
}
let data = reader.read_bytes(msg_size as usize)?;
let padding = aligned_size - msg_size;
if padding > 0 {
reader.skip(padding)?;
}
messages.push(RawMessage {
msg_type,
flags: msg_flags,
creation_index: None,
data,
});
}
Ok(())
}
fn read_v1_continuation<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
length: u64,
messages: &mut Vec<RawMessage>,
) -> Result<()> {
reader.seek(addr)?;
let chunk_end = addr + length;
let mut continuations = Vec::new();
Self::read_v1_messages(reader, chunk_end, 0, messages, &mut continuations)?;
for (cont_addr, cont_len) in continuations {
Self::read_v1_continuation(reader, cont_addr, cont_len, messages)?;
}
Ok(())
}
fn read_v2<R: Read + Seek>(reader: &mut HdfReader<R>, header_addr: u64) -> Result<Self> {
let version = reader.read_u8()?;
if version != 2 {
return Err(Error::InvalidFormat(format!(
"expected object header v2, got {version}"
)));
}
let flags = reader.read_u8()?;
let (atime, mtime, ctime, btime) = if flags & HDR_STORE_TIMES != 0 {
(
Some(reader.read_u32()?),
Some(reader.read_u32()?),
Some(reader.read_u32()?),
Some(reader.read_u32()?),
)
} else {
(None, None, None, None)
};
let (max_compact_attrs, min_dense_attrs) = if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
(Some(reader.read_u16()?), Some(reader.read_u16()?))
} else {
(None, None)
};
let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
let chunk0_data_start = reader.position()?;
let chunk0_data_end = chunk0_data_start + chunk0_data_size;
let checksum_pos = chunk0_data_end;
reader.seek(checksum_pos)?;
let stored_checksum = reader.read_u32()?;
let check_len = (checksum_pos - header_addr) as usize;
reader.seek(header_addr)?;
let check_data = reader.read_bytes(check_len)?;
let computed = checksum_metadata(&check_data);
if stored_checksum != computed {
return Err(Error::InvalidFormat(format!(
"object header checksum mismatch: stored={stored_checksum:#010x}, computed={computed:#010x}"
)));
}
reader.seek(chunk0_data_start)?;
let has_crt_order = flags & HDR_ATTR_CRT_ORDER_TRACKED != 0;
let mut messages = Vec::new();
let mut continuations = Vec::new();
Self::read_v2_messages(reader, chunk0_data_end, has_crt_order, &mut messages, &mut continuations)?;
for (cont_addr, cont_len) in continuations {
Self::read_v2_continuation(reader, cont_addr, cont_len, has_crt_order, &mut messages)?;
}
Ok(ObjectHeader {
version: 2,
flags,
refcount: 1, atime,
mtime,
ctime,
btime,
max_compact_attrs,
min_dense_attrs,
messages,
})
}
fn read_v2_messages<R: Read + Seek>(
reader: &mut HdfReader<R>,
chunk_data_end: u64,
has_crt_order: bool,
messages: &mut Vec<RawMessage>,
continuations: &mut Vec<(u64, u64)>,
) -> Result<()> {
while reader.position()? < chunk_data_end {
let pos = reader.position()?;
if pos + 4 > chunk_data_end {
break;
}
let msg_type = reader.read_u8()? as u16;
let msg_size = reader.read_u16()? as u64;
let msg_flags = reader.read_u8()?;
let creation_index = if has_crt_order {
Some(reader.read_u16()?)
} else {
None
};
if msg_type == MSG_NIL as u16 {
reader.skip(msg_size)?;
continue;
}
if msg_type == MSG_HEADER_CONTINUATION as u16 {
let cont_offset = reader.read_addr()?;
let cont_length = reader.read_length()?;
let used = reader.sizeof_addr() as u64 + reader.sizeof_size() as u64;
if msg_size > used {
reader.skip(msg_size - used)?;
}
continuations.push((cont_offset, cont_length));
continue;
}
let data = reader.read_bytes(msg_size as usize)?;
messages.push(RawMessage {
msg_type,
flags: msg_flags,
creation_index,
data,
});
}
Ok(())
}
fn read_v2_continuation<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
length: u64,
has_crt_order: bool,
messages: &mut Vec<RawMessage>,
) -> Result<()> {
reader.seek(addr)?;
let magic = reader.read_bytes(4)?;
if magic != OCHK_MAGIC {
return Err(Error::InvalidFormat(
"invalid continuation chunk magic".into(),
));
}
let _data_start = reader.position()?;
let data_end = addr + length - 4;
let mut continuations = Vec::new();
Self::read_v2_messages(reader, data_end, has_crt_order, messages, &mut continuations)?;
reader.seek(data_end)?;
let stored_checksum = reader.read_u32()?;
let check_len = (data_end - addr) as usize;
reader.seek(addr)?;
let check_data = reader.read_bytes(check_len)?;
let computed = checksum_metadata(&check_data);
if stored_checksum != computed {
return Err(Error::InvalidFormat(
"continuation chunk checksum mismatch".into(),
));
}
for (cont_addr, cont_len) in continuations {
Self::read_v2_continuation(reader, cont_addr, cont_len, has_crt_order, messages)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::BufReader;
#[test]
fn test_parse_v0_root_object_header() {
let f = File::open("tests/data/simple_v0.h5").unwrap();
let mut reader = HdfReader::new(BufReader::new(f));
let sb = crate::format::superblock::Superblock::read(&mut reader).unwrap();
let oh = ObjectHeader::read_at(&mut reader, sb.root_addr).unwrap();
println!("v0 root OH: version={}, refcount={}, messages:", oh.version, oh.refcount);
for msg in &oh.messages {
println!(" type={:#06x}, flags={:#04x}, len={}", msg.msg_type, msg.flags, msg.data.len());
}
assert_eq!(oh.version, 1);
assert!(oh.messages.iter().any(|m| m.msg_type == MSG_SYMBOL_TABLE));
}
#[test]
fn test_parse_v3_root_object_header() {
let f = File::open("tests/data/simple_v2.h5").unwrap();
let mut reader = HdfReader::new(BufReader::new(f));
let sb = crate::format::superblock::Superblock::read(&mut reader).unwrap();
let oh = ObjectHeader::read_at(&mut reader, sb.root_addr).unwrap();
println!("v3 root OH: version={}, flags={:#04x}, messages:", oh.version, oh.flags);
for msg in &oh.messages {
println!(" type={:#06x}, flags={:#04x}, len={}", msg.msg_type, msg.flags, msg.data.len());
}
assert_eq!(oh.version, 2);
let has_links = oh.messages.iter().any(|m| m.msg_type == MSG_LINK || m.msg_type == MSG_LINK_INFO);
assert!(has_links, "v2 root group should have link or link info messages");
}
}