use crate::error::{IoError, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
pub const HDF5_SIGNATURE: [u8; 8] = [0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a];
#[derive(Debug, Clone)]
pub struct SuperblockInfo {
pub version: u8,
pub offset_size: u8,
pub length_size: u8,
pub root_group_address: u64,
pub base_address: u64,
pub eof_address: u64,
}
pub fn parse_superblock<R: Read + Seek>(reader: &mut R) -> Result<SuperblockInfo> {
reader
.seek(SeekFrom::Start(0))
.map_err(|e| IoError::FormatError(format!("Failed to seek to start of HDF5 file: {e}")))?;
let mut sig = [0u8; 8];
reader
.read_exact(&mut sig)
.map_err(|e| IoError::FormatError(format!("Failed to read HDF5 signature: {e}")))?;
if sig != HDF5_SIGNATURE {
return Err(IoError::FormatError(
"Not a valid HDF5 file: signature mismatch".to_string(),
));
}
let sb_version = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read superblock version: {e}")))?;
match sb_version {
0 | 1 => parse_superblock_v0v1(reader, sb_version),
2 | 3 => parse_superblock_v2v3(reader, sb_version),
_ => Err(IoError::UnsupportedFormat(format!(
"HDF5 superblock version {sb_version} is not supported"
))),
}
}
fn parse_superblock_v0v1<R: Read + Seek>(reader: &mut R, version: u8) -> Result<SuperblockInfo> {
let _free_space_version = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read free-space version: {e}")))?;
let _root_group_version = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read root group version: {e}")))?;
let _reserved = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read reserved byte: {e}")))?;
let _shared_header_version = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read shared header version: {e}")))?;
let offset_size = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read offset size: {e}")))?;
let length_size = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read length size: {e}")))?;
let _reserved2 = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read reserved byte: {e}")))?;
let _group_leaf_k = reader
.read_u16::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read group leaf K: {e}")))?;
let _group_internal_k = reader
.read_u16::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read group internal K: {e}")))?;
let _consistency_flags = reader
.read_u32::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read consistency flags: {e}")))?;
if version == 1 {
let _indexed_k = reader
.read_u16::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read indexed K: {e}")))?;
let _reserved3 = reader
.read_u16::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read reserved: {e}")))?;
}
let base_address = read_offset(reader, offset_size)?;
let _free_space_address = read_offset(reader, offset_size)?;
let eof_address = read_offset(reader, offset_size)?;
let _driver_info_address = read_offset(reader, offset_size)?;
let _link_name_offset = read_offset(reader, offset_size)?;
let root_group_address = read_offset(reader, offset_size)?;
Ok(SuperblockInfo {
version,
offset_size,
length_size,
root_group_address,
base_address,
eof_address,
})
}
fn parse_superblock_v2v3<R: Read + Seek>(reader: &mut R, version: u8) -> Result<SuperblockInfo> {
let offset_size = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read offset size: {e}")))?;
let length_size = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read length size: {e}")))?;
let _consistency_flags = reader
.read_u8()
.map_err(|e| IoError::FormatError(format!("Failed to read consistency flags: {e}")))?;
let base_address = read_offset(reader, offset_size)?;
let _sb_ext_address = read_offset(reader, offset_size)?;
let eof_address = read_offset(reader, offset_size)?;
let root_group_address = read_offset(reader, offset_size)?;
let _checksum = reader
.read_u32::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read superblock checksum: {e}")))?;
Ok(SuperblockInfo {
version,
offset_size,
length_size,
root_group_address,
base_address,
eof_address,
})
}
pub fn read_offset<R: Read>(reader: &mut R, size: u8) -> Result<u64> {
match size {
2 => reader
.read_u16::<LittleEndian>()
.map(u64::from)
.map_err(|e| IoError::FormatError(format!("Failed to read 2-byte offset: {e}"))),
4 => reader
.read_u32::<LittleEndian>()
.map(u64::from)
.map_err(|e| IoError::FormatError(format!("Failed to read 4-byte offset: {e}"))),
8 => reader
.read_u64::<LittleEndian>()
.map_err(|e| IoError::FormatError(format!("Failed to read 8-byte offset: {e}"))),
_ => Err(IoError::FormatError(format!(
"Unsupported offset size: {size}"
))),
}
}
pub fn read_length<R: Read>(reader: &mut R, size: u8) -> Result<u64> {
read_offset(reader, size)
}
pub fn undefined_address(offset_size: u8) -> u64 {
match offset_size {
2 => u16::MAX as u64,
4 => u32::MAX as u64,
8 => u64::MAX,
_ => u64::MAX,
}
}