use std::io::{Read, Seek};
use crate::error::{Error, Result};
use crate::format::checksum::checksum_metadata;
use crate::io::reader::HdfReader;
use super::chunk::read_v1_continuation;
use super::chunk::read_v2_continuation;
use super::msg::{read_v1_messages, read_v2_messages};
use super::{
ObjectHeader, HDR_ATTR_CRT_ORDER_TRACKED, HDR_ATTR_STORE_PHASE_CHANGE, HDR_CHUNK0_SIZE_MASK,
HDR_STORE_TIMES, HDR_V2_KNOWN_FLAGS, MSG_OBJ_REF_COUNT, OHDR_MAGIC,
};
impl ObjectHeader {
pub(super) fn read_v1<R: Read + Seek>(reader: &mut HdfReader<R>) -> Result<Self> {
let header_start = reader.position()?;
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 = u64::from(reader.read_u32()?);
reader.skip(4)?;
let chunk_start = reader.position()?;
let chunk_end = chunk_start
.checked_add(chunk_data_size)
.ok_or_else(|| Error::InvalidFormat("object header v1 chunk size overflow".into()))?;
let mut messages = Vec::new();
messages
.try_reserve_exact(usize::from(num_messages))
.map_err(|err| {
Error::InvalidFormat(format!(
"object header v1 message vector allocation failed: {err}"
))
})?;
let mut continuations = Vec::new();
let mut chunk_ranges = vec![(header_start, chunk_end)];
read_v1_messages(
reader,
chunk_end,
num_messages,
&mut messages,
&mut continuations,
&mut chunk_ranges,
0,
)?;
let mut continuation_scratch = Vec::new();
for (cont_addr, cont_len) in continuations {
read_v1_continuation(
reader,
cont_addr,
cont_len,
&mut messages,
&mut chunk_ranges,
1,
&mut continuation_scratch,
)?;
}
let num_messages = usize::from(num_messages);
if messages.len() > num_messages {
return Err(Error::InvalidFormat(format!(
"object header v1 declared {num_messages} messages but decoded {} non-NIL/non-continuation messages",
messages.len()
)));
}
Ok(ObjectHeader {
version: 1,
flags: 0,
refcount,
atime: None,
mtime: None,
ctime: None,
btime: None,
max_compact_attrs: None,
min_dense_attrs: None,
messages,
})
}
pub(super) fn read_v2<R: Read + Seek>(
reader: &mut HdfReader<R>,
header_addr: u64,
) -> Result<Self> {
let _ = OHDR_MAGIC; let version = reader.read_u8()?;
if version != 2 {
return Err(Error::InvalidFormat(format!(
"expected object header v2, got {version}"
)));
}
let flags = reader.read_u8()?;
if flags & !HDR_V2_KNOWN_FLAGS != 0 {
return Err(Error::InvalidFormat(format!(
"object header v2 flags contain reserved bits: {flags:#04x}"
)));
}
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 {
let max_compact = reader.read_u16()?;
let min_dense = reader.read_u16()?;
if max_compact < min_dense {
return Err(Error::InvalidFormat(
"object header attribute phase change max compact is less than min dense"
.into(),
));
}
(Some(max_compact), Some(min_dense))
} 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
.checked_add(chunk0_data_size)
.ok_or_else(|| Error::InvalidFormat("object header v2 chunk size overflow".into()))?;
let checksum_pos = chunk0_data_end;
reader.seek(checksum_pos)?;
let stored_checksum = reader.read_u32()?;
let check_len =
usize::try_from(checksum_pos.checked_sub(header_addr).ok_or_else(|| {
Error::InvalidFormat("object header checksum span underflow".into())
})?)
.map_err(|_| {
Error::InvalidFormat("object header checksum span exceeds usize".into())
})?;
let file_len = reader.len()?;
if checksum_pos
.checked_add(4)
.is_none_or(|checksum_end| checksum_end > file_len)
{
return Err(Error::InvalidFormat(
"object header v2 checksum range exceeds file size".into(),
));
}
reader.seek(header_addr)?;
let mut checksum_scratch = vec![0u8; check_len];
reader.read_bytes_into(&mut checksum_scratch)?;
let computed = checksum_metadata(&checksum_scratch);
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();
let chunk0_range_end = chunk0_data_end
.checked_add(4)
.ok_or_else(|| Error::InvalidFormat("object header v2 chunk range overflow".into()))?;
let mut chunk_ranges = vec![(header_addr, chunk0_range_end)];
read_v2_messages(
reader,
chunk0_data_end,
has_crt_order,
&mut messages,
&mut continuations,
&mut chunk_ranges,
0,
)?;
let mut continuation_scratch = Vec::new();
for (cont_addr, cont_len) in continuations {
read_v2_continuation(
reader,
cont_addr,
cont_len,
has_crt_order,
&mut messages,
&mut chunk_ranges,
1,
&mut continuation_scratch,
&mut checksum_scratch,
)?;
}
let refcount = messages
.iter()
.find(|msg| msg.msg_type == MSG_OBJ_REF_COUNT)
.and_then(|msg| msg.data.get(0..4))
.and_then(|raw| raw.try_into().ok())
.map(u32::from_le_bytes)
.unwrap_or(1);
Ok(ObjectHeader {
version: 2,
flags,
refcount,
atime,
mtime,
ctime,
btime,
max_compact_attrs,
min_dense_attrs,
messages,
})
}
}