use std::{
collections::HashMap,
fmt,
io::{Read, Seek},
};
use crate::error::{Error, Result};
use crate::io::reader::HdfReader;
const GCOL_MAGIC: [u8; 4] = [b'G', b'C', b'O', b'L'];
#[derive(Debug, Clone)]
pub struct GlobalHeapRef {
pub collection_addr: u64,
pub object_index: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct GlobalHeapHeader {
pub addr: u64,
pub collection_size: u64,
}
#[derive(Debug)]
pub struct GlobalHeapCollection {
pub objects: Vec<(u32, Vec<u8>)>, }
impl GlobalHeapCollection {
pub fn create() -> Self {
Self {
objects: Vec::new(),
}
}
pub fn protect(collection: Self) -> Self {
collection
}
pub fn alloc(&mut self, data: Vec<u8>) -> Result<u32> {
let next = self
.objects
.iter()
.map(|(idx, _)| *idx)
.max()
.unwrap_or(0)
.checked_add(1)
.ok_or_else(|| Error::InvalidFormat("global heap object index overflow".into()))?;
self.objects.push((next, data));
Ok(next)
}
pub fn extend(&mut self, additional: usize) {
self.objects.reserve(additional);
}
pub fn insert(&mut self, index: u32, data: Vec<u8>) -> Result<()> {
if index == 0 {
return Err(Error::InvalidFormat(
"global heap object index zero is reserved".into(),
));
}
if self.objects.iter().any(|(idx, _)| *idx == index) {
return Err(Error::InvalidFormat(format!(
"global heap object {index} already exists"
)));
}
self.objects.push((index, data));
Ok(())
}
pub fn link(&mut self, index: u32) -> Result<()> {
if self.get_object(index).is_some() {
Ok(())
} else {
Err(Error::InvalidFormat(format!(
"global heap object {index} not found"
)))
}
}
pub fn remove(&mut self, index: u32) -> Result<Vec<u8>> {
let pos = self
.objects
.iter()
.position(|(idx, _)| *idx == index)
.ok_or_else(|| Error::InvalidFormat(format!("global heap object {index} not found")))?;
Ok(self.objects.remove(pos).1)
}
pub fn free(&mut self) {
self.objects.clear();
}
pub fn hdr_deserialize<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
) -> Result<GlobalHeapHeader> {
Self::decode_header(reader, addr)
}
pub fn cache_heap_get_final_load_size(header: &GlobalHeapHeader) -> Result<usize> {
heap_object_len(header.collection_size, "global heap collection size")
}
pub fn cache_heap_image_len(&self) -> Result<usize> {
self.cache_heap_image_len_with_size(8)
}
pub fn cache_heap_image_len_with_size(&self, sizeof_size: u8) -> Result<usize> {
if sizeof_size == 0 || sizeof_size > 8 {
return Err(Error::InvalidFormat(
"global heap size field width is invalid".into(),
));
}
let header_len = 8usize
.checked_add(usize::from(sizeof_size))
.ok_or_else(|| Error::InvalidFormat("global heap header length overflow".into()))?;
let object_header_len = header_len;
let mut len = header_len;
for (_, data) in &self.objects {
let padded = data
.len()
.checked_add(7)
.map(|size| size & !7)
.ok_or_else(|| Error::InvalidFormat("global heap object image overflow".into()))?;
len = len
.checked_add(object_header_len)
.and_then(|value| value.checked_add(padded))
.ok_or_else(|| Error::InvalidFormat("global heap image length overflow".into()))?;
}
len.checked_add(7)
.map(|value| value & !7)
.ok_or_else(|| Error::InvalidFormat("global heap image length overflow".into()))
}
pub fn cache_heap_serialize_into(&self, sizeof_size: u8, out: &mut Vec<u8>) -> Result<()> {
if sizeof_size == 0 || sizeof_size > 8 {
return Err(Error::InvalidFormat(
"global heap size field width is invalid".into(),
));
}
let image_len = self.cache_heap_image_len_with_size(sizeof_size)?;
out.clear();
out.try_reserve_exact(image_len).map_err(|err| {
Error::InvalidFormat(format!("global heap image allocation failed: {err}"))
})?;
out.extend_from_slice(&GCOL_MAGIC);
out.push(1);
out.extend_from_slice(&[0; 3]);
encode_heap_size(out, 0, sizeof_size, "global heap collection size")?;
for (index, data) in &self.objects {
if *index == 0 {
return Err(Error::InvalidFormat(
"global heap object index zero is reserved".into(),
));
}
let data_size = u64::try_from(data.len())
.map_err(|_| Error::InvalidFormat("global heap object size exceeds u64".into()))?;
let padded = data
.len()
.checked_add(7)
.map(|size| size & !7)
.ok_or_else(|| Error::InvalidFormat("global heap object image overflow".into()))?;
out.extend_from_slice(
&u16::try_from(*index)
.map_err(|_| {
Error::InvalidFormat("global heap object index exceeds u16".into())
})?
.to_le_bytes(),
);
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&[0; 4]);
encode_heap_size(out, data_size, sizeof_size, "global heap object size")?;
out.extend_from_slice(data);
let padded_end = out
.len()
.checked_add(padded - data.len())
.ok_or_else(|| Error::InvalidFormat("global heap object image overflow".into()))?;
out.resize(padded_end, 0);
}
let padded_collection_len = out
.len()
.checked_add(7)
.map(|value| value & !7)
.ok_or_else(|| Error::InvalidFormat("global heap collection size overflow".into()))?;
out.resize(padded_collection_len, 0);
let collection_size = u64::try_from(out.len())
.map_err(|_| Error::InvalidFormat("global heap collection size exceeds u64".into()))?;
let encoded_size =
encode_heap_size_bytes(collection_size, sizeof_size, "global heap collection size")?;
out[8..8 + usize::from(sizeof_size)]
.copy_from_slice(&encoded_size[..usize::from(sizeof_size)]);
Ok(())
}
pub fn cache_heap_serialize(&self, sizeof_size: u8) -> Result<Vec<u8>> {
let mut out = Vec::new();
self.cache_heap_serialize_into(sizeof_size, &mut out)?;
Ok(out)
}
pub fn cache_heap_free_icr(_image: Vec<u8>) {}
pub fn get_addr(reference: &GlobalHeapRef) -> u64 {
reference.collection_addr
}
pub fn get_size(&self, index: u32) -> Option<usize> {
self.get_object(index).map(|data| data.len())
}
pub fn get_free_size(&self) -> usize {
self.objects.capacity().saturating_sub(self.objects.len())
}
pub fn write_debug(&self, out: &mut impl fmt::Write) -> fmt::Result {
write!(out, "GlobalHeapCollection(objects={})", self.objects.len())
}
pub fn debug(&self) -> String {
let mut out = String::new();
self.write_debug(&mut out)
.expect("writing GlobalHeapCollection debug output to String cannot fail");
out
}
pub fn read_at<R: Read + Seek>(reader: &mut HdfReader<R>, addr: u64) -> Result<Self> {
Self::deserialize_collection(reader, addr)
}
pub fn deserialize_collection<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
) -> Result<Self> {
let header = Self::decode_header(reader, addr)?;
Self::walk_objects(reader, &header).map_err(|err| match err {
Error::Io(io_err) if io_err.kind() == std::io::ErrorKind::UnexpectedEof => {
Error::InvalidFormat("global heap collection body is truncated".into())
}
err => err,
})
}
pub fn decode_header<R: Read + Seek>(
reader: &mut HdfReader<R>,
addr: u64,
) -> Result<GlobalHeapHeader> {
reader.seek(addr)?;
let mut magic = [0u8; 4];
reader.read_bytes_into(&mut magic)?;
if magic != GCOL_MAGIC {
return Err(Error::InvalidFormat(
"invalid global heap collection magic".into(),
));
}
let version = reader.read_u8()?;
if version != 1 {
return Err(Error::Unsupported(format!("global heap version {version}")));
}
let mut reserved = [0u8; 3];
reader.read_bytes_into(&mut reserved)?;
let collection_size = reader.read_length()?;
let header_len = 8u64
.checked_add(u64::from(reader.sizeof_size()))
.ok_or_else(|| Error::InvalidFormat("global heap header length overflow".into()))?;
if collection_size < header_len {
return Err(Error::InvalidFormat(
"global heap collection is smaller than its header".into(),
));
}
if collection_size % 8 != 0 {
return Err(Error::InvalidFormat(
"global heap collection size is not 8-byte aligned".into(),
));
}
Ok(GlobalHeapHeader {
addr,
collection_size,
})
}
pub fn walk_objects<R: Read + Seek>(
reader: &mut HdfReader<R>,
header: &GlobalHeapHeader,
) -> Result<Self> {
let mut objects: Vec<(u32, Vec<u8>)> = Vec::new();
let data_end = header
.addr
.checked_add(header.collection_size)
.ok_or_else(|| Error::InvalidFormat("global heap collection size overflow".into()))?;
let object_header_len = 8u64
.checked_add(u64::from(reader.sizeof_size()))
.ok_or_else(|| {
Error::InvalidFormat("global heap object header size overflow".into())
})?;
let file_len = reader.len()?;
let mut pos = reader.position()?;
while pos < data_end {
let min_entry_end = pos.checked_add(object_header_len).ok_or_else(|| {
Error::InvalidFormat("global heap object header offset overflow".into())
})?;
if min_entry_end > data_end {
break;
}
let index = u32::from(reader.read_u16()?);
let _reference_count = reader.read_u16()?;
reader.read_u32()?;
let obj_size = reader.read_length()?;
if index == 0 {
if obj_size == 0 {
break;
}
let next_pos = pos.checked_add(obj_size).ok_or_else(|| {
Error::InvalidFormat("global heap free object offset overflow".into())
})?;
if next_pos > data_end {
return Err(Error::InvalidFormat(
"global heap free object exceeds collection bounds".into(),
));
}
reader.seek(next_pos)?;
pos = next_pos;
continue;
}
let obj_len = heap_object_len(obj_size, "global heap object size")?;
let padded = obj_size
.checked_add(7)
.map(|size| size & !7)
.ok_or_else(|| Error::InvalidFormat("global heap object size overflow".into()))?;
let data_pos = min_entry_end;
let data_bytes_end = data_pos
.checked_add(obj_size)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
let next_pos = data_pos
.checked_add(padded)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
if next_pos > data_end {
return Err(Error::InvalidFormat(
"global heap object exceeds collection bounds".into(),
));
}
if data_bytes_end > file_len {
return Err(Error::InvalidFormat(
"global heap object data extends past end of file".into(),
));
}
let mut data = Vec::new();
resize_heap_object_buffer(&mut data, obj_len)?;
reader.read_bytes_into(&mut data)?;
objects.push((index, data));
let padding = padded - obj_size;
if padding > 0 {
reader.skip(padding)?;
}
pos = next_pos;
}
Ok(Self { objects })
}
pub fn get_object(&self, index: u32) -> Option<&[u8]> {
self.objects
.iter()
.find(|(idx, _)| *idx == index)
.map(|(_, data)| data.as_slice())
}
pub fn iter_objects(&self) -> impl Iterator<Item = (u32, &[u8])> {
self.objects
.iter()
.map(|(index, data)| (*index, data.as_slice()))
}
}
fn heap_object_len(value: u64, context: &str) -> Result<usize> {
usize::try_from(value)
.map_err(|_| Error::InvalidFormat(format!("{context} does not fit in usize")))
}
fn resize_heap_object_buffer(out: &mut Vec<u8>, len: usize) -> Result<()> {
out.clear();
out.try_reserve_exact(len).map_err(|err| {
Error::InvalidFormat(format!("global heap object allocation failed: {err}"))
})?;
out.resize(len, 0);
Ok(())
}
fn read_u16_le_at(bytes: &[u8], offset: usize, context: &str) -> Result<u16> {
let end = offset
.checked_add(2)
.ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
let window = bytes
.get(offset..end)
.ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))?;
Ok(u16::from_le_bytes([window[0], window[1]]))
}
fn read_heap_size_at(bytes: &[u8], offset: usize, width: usize) -> Result<usize> {
if width == 0 || width > 8 {
return Err(Error::InvalidFormat(
"global heap size field width is invalid".into(),
));
}
let end = offset
.checked_add(width)
.ok_or_else(|| Error::InvalidFormat("global heap size offset overflow".into()))?;
let window = bytes
.get(offset..end)
.ok_or_else(|| Error::InvalidFormat("global heap size field is truncated".into()))?;
let mut value = 0u64;
for (idx, byte) in window.iter().copied().enumerate() {
value |= u64::from(byte) << (idx * 8);
}
heap_object_len(value, "global heap object size")
}
fn encode_heap_size_bytes(value: u64, width: u8, context: &str) -> Result<[u8; 8]> {
let width = usize::from(width);
if width == 0 || width > 8 {
return Err(Error::InvalidFormat(format!("{context} width is invalid")));
}
if width < 8 && value >= (1u64 << (width * 8)) {
return Err(Error::InvalidFormat(format!(
"{context} value {value:#x} does not fit in {width} bytes"
)));
}
Ok(value.to_le_bytes())
}
fn encode_heap_size(out: &mut Vec<u8>, value: u64, width: u8, context: &str) -> Result<()> {
let bytes = encode_heap_size_bytes(value, width, context)?;
out.extend_from_slice(&bytes[..usize::from(width)]);
Ok(())
}
pub fn read_global_heap_object_into<R: Read + Seek>(
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
out: &mut Vec<u8>,
) -> Result<()> {
let header = GlobalHeapCollection::decode_header(reader, gh_ref.collection_addr)?;
read_global_heap_object_from_decoded_header_into(reader, &header, gh_ref.object_index, out)?;
trace_global_heap_deref(gh_ref, out);
Ok(())
}
pub fn read_global_heap_object<R: Read + Seek>(
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
) -> Result<Vec<u8>> {
let mut out = Vec::new();
read_global_heap_object_into(reader, gh_ref, &mut out)?;
Ok(out)
}
pub fn read_global_heap_objects_batched<R: Read + Seek>(
reader: &mut HdfReader<R>,
refs: &[GlobalHeapRef],
) -> Result<Vec<Vec<u8>>> {
let mut out = Vec::new();
read_global_heap_objects_batched_into(reader, refs, &mut out)?;
Ok(out)
}
pub fn read_global_heap_objects_batched_into<R: Read + Seek>(
reader: &mut HdfReader<R>,
refs: &[GlobalHeapRef],
out: &mut Vec<Vec<u8>>,
) -> Result<()> {
let mut cache = GlobalHeapObjectCache::new();
for gh_ref in refs {
cache.object_data(reader, gh_ref)?;
}
out.truncate(refs.len());
while out.len() < refs.len() {
out.push(Vec::new());
}
for (slot, gh_ref) in out.iter_mut().zip(refs) {
let data = cache.cached_object_data(gh_ref);
trace_global_heap_deref(gh_ref, data);
slot.clear();
slot.extend_from_slice(data);
}
Ok(())
}
#[derive(Debug, Default)]
pub struct GlobalHeapObjectCache {
collections: HashMap<u64, CachedGlobalHeapCollection>,
}
#[derive(Debug)]
struct CachedGlobalHeapCollection {
image: Vec<u8>,
object_positions: HashMap<u32, (usize, usize)>,
}
impl CachedGlobalHeapCollection {
fn read_at<R: Read + Seek>(reader: &mut HdfReader<R>, addr: u64) -> Result<Self> {
let header = GlobalHeapCollection::decode_header(reader, addr)?;
let image_len = heap_object_len(header.collection_size, "global heap collection size")?;
let mut image = vec![0u8; image_len];
reader.seek(addr)?;
reader.read_bytes_into(&mut image)?;
Self::from_image(image, reader.sizeof_size())
}
fn from_image(image: Vec<u8>, sizeof_size: u8) -> Result<Self> {
let sizeof_size = usize::from(sizeof_size);
let header_len = 8usize
.checked_add(sizeof_size)
.ok_or_else(|| Error::InvalidFormat("global heap header length overflow".into()))?;
let object_header_len = header_len;
if image.len() < header_len {
return Err(Error::InvalidFormat(
"global heap collection image is truncated".into(),
));
}
let mut object_positions = HashMap::new();
let mut pos = header_len;
while pos < image.len() {
let min_entry_end = pos.checked_add(object_header_len).ok_or_else(|| {
Error::InvalidFormat("global heap object header offset overflow".into())
})?;
if min_entry_end > image.len() {
break;
}
let index = u32::from(read_u16_le_at(&image, pos, "global heap object index")?);
let size_pos = pos.checked_add(8).ok_or_else(|| {
Error::InvalidFormat("global heap object size offset overflow".into())
})?;
let obj_size = read_heap_size_at(&image, size_pos, sizeof_size)?;
if index == 0 {
if obj_size == 0 {
break;
}
let next_pos = pos.checked_add(obj_size).ok_or_else(|| {
Error::InvalidFormat("global heap free object offset overflow".into())
})?;
if next_pos > image.len() {
return Err(Error::InvalidFormat(
"global heap free object exceeds collection bounds".into(),
));
}
pos = next_pos;
continue;
}
let padded = obj_size
.checked_add(7)
.map(|size| size & !7)
.ok_or_else(|| Error::InvalidFormat("global heap object size overflow".into()))?;
let data_start = min_entry_end;
let data_end = data_start
.checked_add(obj_size)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
let next_pos = data_start
.checked_add(padded)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
if next_pos > image.len() {
return Err(Error::InvalidFormat(
"global heap object exceeds collection bounds".into(),
));
}
object_positions
.entry(index)
.or_insert((data_start, data_end));
pos = next_pos;
}
Ok(Self {
image,
object_positions,
})
}
fn get_object(&self, index: u32) -> Option<&[u8]> {
self.object_positions
.get(&index)
.and_then(|&(start, end)| self.image.get(start..end))
}
}
impl GlobalHeapObjectCache {
pub fn new() -> Self {
Self::default()
}
pub fn read_object<R: Read + Seek>(
&mut self,
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
) -> Result<Vec<u8>> {
self.visit_object(reader, gh_ref, |data| Ok(data.to_vec()))
}
fn object_data<R: Read + Seek>(
&mut self,
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
) -> Result<&[u8]> {
let collection = match self.collections.entry(gh_ref.collection_addr) {
std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(),
std::collections::hash_map::Entry::Vacant(entry) => entry.insert(
CachedGlobalHeapCollection::read_at(reader, gh_ref.collection_addr)?,
),
};
collection.get_object(gh_ref.object_index).ok_or_else(|| {
Error::InvalidFormat(format!(
"global heap object {} not found in collection at {:#x}",
gh_ref.object_index, gh_ref.collection_addr
))
})
}
fn cached_object_data(&self, gh_ref: &GlobalHeapRef) -> &[u8] {
self.collections
.get(&gh_ref.collection_addr)
.and_then(|collection| collection.get_object(gh_ref.object_index))
.expect("global heap object was validated before cloning output")
}
pub fn visit_object<R: Read + Seek, T>(
&mut self,
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
visitor: impl FnOnce(&[u8]) -> Result<T>,
) -> Result<T> {
let data = self.object_data(reader, gh_ref)?;
trace_global_heap_deref(gh_ref, data);
visitor(data)
}
}
pub fn visit_global_heap_object<R: Read + Seek, T>(
reader: &mut HdfReader<R>,
gh_ref: &GlobalHeapRef,
visitor: impl FnOnce(&[u8]) -> Result<T>,
) -> Result<T> {
let mut data = Vec::new();
let header = GlobalHeapCollection::decode_header(reader, gh_ref.collection_addr)?;
read_global_heap_object_from_decoded_header_into(
reader,
&header,
gh_ref.object_index,
&mut data,
)?;
trace_global_heap_deref(gh_ref, &data);
visitor(&data)
}
fn read_global_heap_object_from_decoded_header_into<R: Read + Seek>(
reader: &mut HdfReader<R>,
header: &GlobalHeapHeader,
object_index: u32,
out: &mut Vec<u8>,
) -> Result<()> {
let data_end = header
.addr
.checked_add(header.collection_size)
.ok_or_else(|| Error::InvalidFormat("global heap collection size overflow".into()))?;
while reader.position()? < data_end {
let pos = reader.position()?;
let object_header_len = 8u64
.checked_add(u64::from(reader.sizeof_size()))
.ok_or_else(|| {
Error::InvalidFormat("global heap object header size overflow".into())
})?;
let min_entry_end = pos.checked_add(object_header_len).ok_or_else(|| {
Error::InvalidFormat("global heap object header offset overflow".into())
})?;
if min_entry_end > data_end {
break;
}
let index = u32::from(reader.read_u16()?);
let _reference_count = reader.read_u16()?;
reader.read_u32()?;
let obj_size = reader.read_length()?;
if index == 0 {
if obj_size == 0 {
break;
}
let next_pos = pos.checked_add(obj_size).ok_or_else(|| {
Error::InvalidFormat("global heap free object offset overflow".into())
})?;
if next_pos > data_end {
return Err(Error::InvalidFormat(
"global heap free object exceeds collection bounds".into(),
));
}
reader.seek(next_pos)?;
continue;
}
let obj_len = heap_object_len(obj_size, "global heap object size")?;
let padded = obj_size
.checked_add(7)
.map(|size| size & !7)
.ok_or_else(|| Error::InvalidFormat("global heap object size overflow".into()))?;
let data_pos = reader.position()?;
let next_pos = data_pos
.checked_add(padded)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
if next_pos > data_end {
return Err(Error::InvalidFormat(
"global heap object exceeds collection bounds".into(),
));
}
if index == object_index {
let data_bytes_end = data_pos
.checked_add(obj_size)
.ok_or_else(|| Error::InvalidFormat("global heap object offset overflow".into()))?;
if data_bytes_end > reader.len()? {
return Err(Error::InvalidFormat(
"global heap object data extends past end of file".into(),
));
}
resize_heap_object_buffer(out, obj_len)?;
reader.read_bytes_into(out)?;
return Ok(());
}
reader.seek(next_pos)?;
}
Err(Error::InvalidFormat(format!(
"global heap object {object_index} not found in collection at {:#x}",
header.addr
)))
}
#[cfg(feature = "tracehash")]
fn trace_global_heap_deref(gh_ref: &GlobalHeapRef, data: &[u8]) {
let mut th = tracehash::th_call!("hdf5.global_heap.deref");
th.input_u64(gh_ref.collection_addr);
th.input_u64(u64::from(gh_ref.object_index));
th.output_value(&(true));
th.output_u64(u64::try_from(data.len()).unwrap_or(u64::MAX));
th.output_value(data);
th.finish();
}
#[cfg(not(feature = "tracehash"))]
fn trace_global_heap_deref(_gh_ref: &GlobalHeapRef, _data: &[u8]) {}
#[cfg(test)]
mod tests {
use super::{
read_global_heap_object_into, read_global_heap_objects_batched,
read_global_heap_objects_batched_into, visit_global_heap_object, GlobalHeapCollection,
GlobalHeapRef,
};
use crate::error::Error;
use crate::io::reader::HdfReader;
use std::io::Cursor;
fn heap_with_size(collection_size: u64) -> Vec<u8> {
let mut heap = b"GCOL".to_vec();
heap.push(1);
heap.extend_from_slice(&[0; 3]);
heap.extend_from_slice(&collection_size.to_le_bytes());
let collection_size =
usize::try_from(collection_size).expect("test collection size should fit in usize");
heap.resize(collection_size.max(16), 0);
heap
}
fn heap_with_declared_object(collection_size: u64, object_size: u64) -> Vec<u8> {
let mut heap = b"GCOL".to_vec();
heap.push(1);
heap.extend_from_slice(&[0; 3]);
heap.extend_from_slice(&collection_size.to_le_bytes());
heap.extend_from_slice(&1u16.to_le_bytes());
heap.extend_from_slice(&1u16.to_le_bytes());
heap.extend_from_slice(&[0; 4]);
heap.extend_from_slice(&object_size.to_le_bytes());
heap
}
#[test]
fn global_heap_rejects_invalid_collection_sizes() {
let mut reader = HdfReader::new(Cursor::new(heap_with_size(8)));
let err = GlobalHeapCollection::read_at(&mut reader, 0).unwrap_err();
assert!(matches!(err, Error::InvalidFormat(_)));
let mut reader = HdfReader::new(Cursor::new(heap_with_size(17)));
let err = GlobalHeapCollection::read_at(&mut reader, 0).unwrap_err();
assert!(matches!(err, Error::InvalidFormat(_)));
}
#[test]
fn global_heap_ignores_trailing_fragment_smaller_than_object_header() {
let mut heap = b"GCOL".to_vec();
heap.push(1);
heap.extend_from_slice(&[0; 3]);
heap.extend_from_slice(&24u64.to_le_bytes());
heap.extend_from_slice(&1u16.to_le_bytes());
heap.extend_from_slice(&0u16.to_le_bytes());
heap.extend_from_slice(&[0; 4]);
let mut reader = HdfReader::new(Cursor::new(heap));
let collection = GlobalHeapCollection::read_at(&mut reader, 0).unwrap();
assert!(collection.objects.is_empty());
}
#[test]
fn global_heap_cache_serialize_roundtrips_and_checks_size_width() {
let mut collection = GlobalHeapCollection::create();
collection.insert(1, b"alpha".to_vec()).unwrap();
collection.insert(2, b"beta".to_vec()).unwrap();
let mut image = vec![99];
collection.cache_heap_serialize_into(8, &mut image).unwrap();
assert_eq!(collection.cache_heap_serialize(8).unwrap(), image);
assert_eq!(image.len(), collection.cache_heap_image_len().unwrap());
let mut reader = HdfReader::new(Cursor::new(image));
let decoded = GlobalHeapCollection::read_at(&mut reader, 0).unwrap();
assert_eq!(decoded.get_object(1), Some(&b"alpha"[..]));
assert_eq!(decoded.get_object(2), Some(&b"beta"[..]));
assert_eq!(decoded.debug(), "GlobalHeapCollection(objects=2)");
let mut image_4 = Vec::new();
collection
.cache_heap_serialize_into(4, &mut image_4)
.unwrap();
assert_eq!(
image_4.len(),
collection.cache_heap_image_len_with_size(4).unwrap()
);
let mut reader = HdfReader::new(Cursor::new(image_4));
reader.set_sizeof_size(4);
let decoded = GlobalHeapCollection::read_at(&mut reader, 0).unwrap();
assert_eq!(decoded.get_object(1), Some(&b"alpha"[..]));
assert_eq!(decoded.get_object(2), Some(&b"beta"[..]));
let mut too_large_index = GlobalHeapCollection::create();
too_large_index
.insert(u32::from(u16::MAX) + 1, Vec::new())
.unwrap();
assert!(too_large_index
.cache_heap_serialize_into(8, &mut Vec::new())
.is_err());
assert!(collection
.cache_heap_serialize_into(0, &mut Vec::new())
.is_err());
}
#[test]
fn global_heap_declared_over_4g_object_uses_bounds_not_helper_cap() {
let object_size = 4u64 * 1024 * 1024 * 1024 + 8;
let collection_size = 16 + 16 + object_size;
let image = heap_with_declared_object(collection_size, object_size);
let mut reader = HdfReader::new(Cursor::new(image.clone()));
let err = GlobalHeapCollection::read_at(&mut reader, 0).unwrap_err();
assert!(
err.to_string()
.contains("global heap object data extends past end of file"),
"unexpected error: {err}"
);
let mut reader = HdfReader::new(Cursor::new(image));
let mut out = b"unchanged".to_vec();
let err = read_global_heap_object_into(
&mut reader,
&GlobalHeapRef {
collection_addr: 0,
object_index: 1,
},
&mut out,
)
.unwrap_err();
assert!(
err.to_string()
.contains("global heap object data extends past end of file"),
"unexpected error: {err}"
);
assert_eq!(out, b"unchanged");
}
#[test]
fn global_heap_object_read_fills_caller_buffer() {
let mut collection = GlobalHeapCollection::create();
collection.insert(1, b"alpha".to_vec()).unwrap();
collection.insert(2, b"beta".to_vec()).unwrap();
let mut image = Vec::new();
collection.cache_heap_serialize_into(8, &mut image).unwrap();
let gh_ref = GlobalHeapRef {
collection_addr: 0,
object_index: 2,
};
let mut out = vec![99, 99, 99];
let mut reader = HdfReader::new(Cursor::new(image.clone()));
read_global_heap_object_into(&mut reader, &gh_ref, &mut out).unwrap();
assert_eq!(out, b"beta");
let mut reader = HdfReader::new(Cursor::new(image));
let len = visit_global_heap_object(&mut reader, &gh_ref, |data| Ok(data.len())).unwrap();
assert_eq!(len, 4);
}
#[test]
fn global_heap_batched_read_preserves_order_and_duplicates() {
let mut collection = GlobalHeapCollection::create();
collection.insert(1, b"alpha".to_vec()).unwrap();
collection.insert(2, b"beta".to_vec()).unwrap();
collection.insert(3, b"gamma".to_vec()).unwrap();
let mut image = Vec::new();
collection.cache_heap_serialize_into(8, &mut image).unwrap();
let refs = [
GlobalHeapRef {
collection_addr: 0,
object_index: 3,
},
GlobalHeapRef {
collection_addr: 0,
object_index: 1,
},
GlobalHeapRef {
collection_addr: 0,
object_index: 3,
},
GlobalHeapRef {
collection_addr: 0,
object_index: 2,
},
];
let mut reader = HdfReader::new(Cursor::new(image));
let out = read_global_heap_objects_batched(&mut reader, &refs).unwrap();
assert_eq!(
out,
vec![
b"gamma".to_vec(),
b"alpha".to_vec(),
b"gamma".to_vec(),
b"beta".to_vec()
]
);
let mut image = Vec::new();
collection.cache_heap_serialize_into(8, &mut image).unwrap();
let mut reader = HdfReader::new(Cursor::new(image));
let mut reused = vec![Vec::with_capacity(16), b"stale".to_vec(), b"extra".to_vec()];
read_global_heap_objects_batched_into(&mut reader, &refs[..2], &mut reused).unwrap();
assert_eq!(reused, vec![b"gamma".to_vec(), b"alpha".to_vec()]);
}
#[test]
fn global_heap_batched_read_reports_missing_object_in_input_order() {
let mut collection = GlobalHeapCollection::create();
collection.insert(1, b"alpha".to_vec()).unwrap();
let mut image = Vec::new();
collection.cache_heap_serialize_into(8, &mut image).unwrap();
let refs = [
GlobalHeapRef {
collection_addr: 0,
object_index: 1,
},
GlobalHeapRef {
collection_addr: 0,
object_index: 9,
},
];
let mut reader = HdfReader::new(Cursor::new(image));
let err = read_global_heap_objects_batched(&mut reader, &refs).unwrap_err();
assert!(
err.to_string()
.contains("global heap object 9 not found in collection at 0x0"),
"unexpected error: {err}"
);
let mut image = Vec::new();
collection.cache_heap_serialize_into(8, &mut image).unwrap();
let mut reader = HdfReader::new(Cursor::new(image));
let mut out = b"keep me".to_vec();
let missing = GlobalHeapRef {
collection_addr: 0,
object_index: 9,
};
let err = read_global_heap_object_into(&mut reader, &missing, &mut out).unwrap_err();
assert!(
err.to_string()
.contains("global heap object 9 not found in collection at 0x0"),
"unexpected error: {err}"
);
assert_eq!(out, b"keep me");
}
}