use std::io::{Read, Seek};
use crate::error::{Error, Result};
use crate::io::reader::HdfReader;
const HEAP_MAGIC: [u8; 4] = [b'H', b'E', b'A', b'P'];
const MAX_LOCAL_HEAP_BYTES: usize = 4 * 1024 * 1024 * 1024;
#[derive(Debug, Clone, Copy)]
pub struct LocalHeapPrefix {
pub data_size: u64,
pub free_list_offset: u64,
pub data_addr: u64,
}
#[derive(Debug, Clone)]
pub struct LocalHeap {
pub data: Vec<u8>,
}
impl LocalHeap {
pub fn read_at<R: Read + Seek>(reader: &mut HdfReader<R>, addr: u64) -> Result<Self> {
let prefix = Self::decode_prefix(reader, addr)?;
Self::load_data_segment(reader, &prefix)
}
pub fn decode_prefix<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
) -> Result<LocalHeapPrefix> {
reader.seek(addr)?;
let magic = reader.read_bytes(4)?;
if magic != HEAP_MAGIC {
return Err(Error::InvalidFormat("invalid local heap magic".into()));
}
let version = reader.read_u8()?;
if version != 0 {
return Err(Error::Unsupported(format!("local heap version {version}")));
}
reader.skip(3)?;
let data_size = reader.read_length()?;
let free_list_offset = reader.read_length()?;
let data_addr = reader.read_addr()?;
Ok(LocalHeapPrefix {
data_size,
free_list_offset,
data_addr,
})
}
pub fn load_data_segment<R: Read + Seek>(
reader: &mut HdfReader<R>,
prefix: &LocalHeapPrefix,
) -> Result<Self> {
reader.seek(prefix.data_addr)?;
let data = reader.read_bytes(heap_len(prefix.data_size, "local heap data size")?)?;
Ok(Self { data })
}
pub fn get_string(&self, offset: usize) -> Result<String> {
if offset >= self.data.len() {
return Err(Error::InvalidFormat(
"local heap string offset is out of bounds".into(),
));
}
let end = self.data[offset..]
.iter()
.position(|&b| b == 0)
.map(|p| offset + p)
.ok_or_else(|| {
Error::InvalidFormat("local heap string is not null-terminated".into())
})?;
Ok(String::from_utf8_lossy(&self.data[offset..end]).to_string())
}
}
fn heap_len(value: u64, context: &str) -> Result<usize> {
let len = usize::try_from(value)
.map_err(|_| Error::InvalidFormat(format!("{context} does not fit in usize")))?;
if len > MAX_LOCAL_HEAP_BYTES {
return Err(Error::InvalidFormat(format!(
"{context} {len} exceeds supported maximum {MAX_LOCAL_HEAP_BYTES}"
)));
}
Ok(len)
}