hdf5_reader/
local_heap.rs1use crate::error::{Error, Result};
12use crate::io::Cursor;
13use crate::storage::Storage;
14
15const HEAP_SIGNATURE: [u8; 4] = *b"HEAP";
17
18#[derive(Debug, Clone)]
23pub struct LocalHeap {
24 pub data_segment_size: u64,
26 pub free_list_offset: u64,
29 pub data_segment_address: u64,
31}
32
33impl LocalHeap {
34 pub fn parse(cursor: &mut Cursor, offset_size: u8, length_size: u8) -> Result<Self> {
44 let sig = cursor.read_bytes(4)?;
45 if sig != HEAP_SIGNATURE {
46 return Err(Error::InvalidLocalHeapSignature);
47 }
48
49 let version = cursor.read_u8()?;
50 if version != 0 {
51 return Err(Error::UnsupportedLocalHeapVersion(version));
52 }
53
54 cursor.skip(3)?;
56
57 let data_segment_size = cursor.read_length(length_size)?;
58 let free_list_offset = cursor.read_length(length_size)?;
59 let data_segment_address = cursor.read_offset(offset_size)?;
60
61 Ok(LocalHeap {
62 data_segment_size,
63 free_list_offset,
64 data_segment_address,
65 })
66 }
67
68 pub fn parse_at_storage(
70 storage: &dyn Storage,
71 address: u64,
72 offset_size: u8,
73 length_size: u8,
74 ) -> Result<Self> {
75 let header_len = 4
76 + 1
77 + 3
78 + usize::from(length_size)
79 + usize::from(length_size)
80 + usize::from(offset_size);
81 let bytes = storage.read_range(address, header_len)?;
82 let mut cursor = Cursor::new(bytes.as_ref());
83 Self::parse(&mut cursor, offset_size, length_size)
84 }
85
86 pub fn get_string(&self, offset: u64, file_data: &[u8]) -> Result<String> {
93 let abs = self
94 .data_segment_address
95 .checked_add(offset)
96 .ok_or(Error::OffsetOutOfBounds(offset))?;
97 let start = abs as usize;
98
99 if start >= file_data.len() {
100 return Err(Error::OffsetOutOfBounds(abs));
101 }
102
103 let segment_end = (self.data_segment_address as usize)
105 .saturating_add(self.data_segment_size as usize)
106 .min(file_data.len());
107
108 let search_region = &file_data[start..segment_end];
109 let null_pos = search_region.iter().position(|&b| b == 0).ok_or_else(|| {
110 Error::InvalidData("local heap string missing null terminator".into())
111 })?;
112
113 let s = std::str::from_utf8(&search_region[..null_pos])
114 .map_err(|e| Error::InvalidData(format!("invalid UTF-8 in local heap string: {e}")))?;
115
116 Ok(s.to_string())
117 }
118
119 pub fn get_string_storage(&self, offset: u64, storage: &dyn Storage) -> Result<String> {
121 if offset >= self.data_segment_size {
122 return Err(Error::OffsetOutOfBounds(offset));
123 }
124
125 let available = self
126 .data_segment_size
127 .checked_sub(offset)
128 .ok_or(Error::OffsetOutOfBounds(offset))?;
129 let len = usize::try_from(available).map_err(|_| {
130 Error::InvalidData("local heap string region exceeds platform usize capacity".into())
131 })?;
132 let abs = self
133 .data_segment_address
134 .checked_add(offset)
135 .ok_or(Error::OffsetOutOfBounds(offset))?;
136 let bytes = storage.read_range(abs, len)?;
137 let null_pos = bytes.iter().position(|&b| b == 0).ok_or_else(|| {
138 Error::InvalidData("local heap string missing null terminator".into())
139 })?;
140 let s = std::str::from_utf8(&bytes[..null_pos])
141 .map_err(|e| Error::InvalidData(format!("invalid UTF-8 in local heap string: {e}")))?;
142 Ok(s.to_string())
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn build_heap_header(
152 data_segment_size: u64,
153 free_list_offset: u64,
154 data_segment_address: u64,
155 ) -> Vec<u8> {
156 let mut buf = Vec::new();
157 buf.extend_from_slice(b"HEAP");
158 buf.push(0); buf.extend_from_slice(&[0, 0, 0]); buf.extend_from_slice(&data_segment_size.to_le_bytes());
161 buf.extend_from_slice(&free_list_offset.to_le_bytes());
162 buf.extend_from_slice(&data_segment_address.to_le_bytes());
163 buf
164 }
165
166 #[test]
167 fn test_parse_local_heap() {
168 let data = build_heap_header(256, 128, 0x2000);
169
170 let mut cursor = Cursor::new(&data);
171 let heap = LocalHeap::parse(&mut cursor, 8, 8).unwrap();
172
173 assert_eq!(heap.data_segment_size, 256);
174 assert_eq!(heap.free_list_offset, 128);
175 assert_eq!(heap.data_segment_address, 0x2000);
176 }
177
178 #[test]
179 fn test_parse_local_heap_4byte() {
180 let mut buf = Vec::new();
181 buf.extend_from_slice(b"HEAP");
182 buf.push(0); buf.extend_from_slice(&[0, 0, 0]); buf.extend_from_slice(&64u32.to_le_bytes()); buf.extend_from_slice(&32u32.to_le_bytes()); buf.extend_from_slice(&0x400u32.to_le_bytes()); let mut cursor = Cursor::new(&buf);
189 let heap = LocalHeap::parse(&mut cursor, 4, 4).unwrap();
190
191 assert_eq!(heap.data_segment_size, 64);
192 assert_eq!(heap.free_list_offset, 32);
193 assert_eq!(heap.data_segment_address, 0x400);
194 }
195
196 #[test]
197 fn test_bad_signature() {
198 let mut data = build_heap_header(256, 128, 0x2000);
199 data[0] = b'X'; let mut cursor = Cursor::new(&data);
201 assert!(matches!(
202 LocalHeap::parse(&mut cursor, 8, 8),
203 Err(Error::InvalidLocalHeapSignature)
204 ));
205 }
206
207 #[test]
208 fn test_bad_version() {
209 let mut data = build_heap_header(256, 128, 0x2000);
210 data[4] = 1; let mut cursor = Cursor::new(&data);
212 assert!(matches!(
213 LocalHeap::parse(&mut cursor, 8, 8),
214 Err(Error::UnsupportedLocalHeapVersion(1))
215 ));
216 }
217
218 #[test]
219 fn test_get_string() {
220 let mut file_data = vec![0u8; 200];
222 let seg_start = 100usize;
224 file_data[seg_start..seg_start + 6].copy_from_slice(b"hello\0");
225 file_data[seg_start + 6..seg_start + 12].copy_from_slice(b"world\0");
226
227 let heap = LocalHeap {
228 data_segment_size: 100,
229 free_list_offset: 50,
230 data_segment_address: seg_start as u64,
231 };
232
233 assert_eq!(heap.get_string(0, &file_data).unwrap(), "hello");
234 assert_eq!(heap.get_string(6, &file_data).unwrap(), "world");
235 }
236
237 #[test]
238 fn test_get_string_out_of_bounds() {
239 let file_data = vec![0u8; 50];
240 let heap = LocalHeap {
241 data_segment_size: 100,
242 free_list_offset: 0,
243 data_segment_address: 100, };
245 assert!(heap.get_string(0, &file_data).is_err());
246 }
247
248 #[test]
249 fn test_get_string_missing_null() {
250 let mut file_data = vec![0xFFu8; 200];
252 file_data[100..105].copy_from_slice(b"abcde");
253
254 let heap = LocalHeap {
255 data_segment_size: 5,
256 free_list_offset: 0,
257 data_segment_address: 100,
258 };
259 assert!(heap.get_string(0, &file_data).is_err());
260 }
261}