1use crate::format::bytes::read_le_uint as read_uint;
18use crate::format::{FormatError, FormatResult};
19
20pub const LOCAL_HEAP_SIGNATURE: [u8; 4] = *b"HEAP";
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct LocalHeapHeader {
26 pub data_size: u64,
28 pub free_list_offset: u64,
30 pub data_addr: u64,
32}
33
34impl LocalHeapHeader {
35 pub fn decode(buf: &[u8], sizeof_addr: usize, sizeof_size: usize) -> FormatResult<Self> {
39 let min_size = 4 + 1 + 3 + sizeof_size * 2 + sizeof_addr;
40 if buf.len() < min_size {
41 return Err(FormatError::BufferTooShort {
42 needed: min_size,
43 available: buf.len(),
44 });
45 }
46
47 if buf[0..4] != LOCAL_HEAP_SIGNATURE {
48 return Err(FormatError::InvalidSignature);
49 }
50
51 let version = buf[4];
52 if version != 0 {
53 return Err(FormatError::InvalidVersion(version));
54 }
55
56 let mut pos = 8;
58
59 let data_size = read_uint(&buf[pos..], sizeof_size);
60 pos += sizeof_size;
61
62 let free_list_offset = read_uint(&buf[pos..], sizeof_size);
63 pos += sizeof_size;
64
65 let data_addr = read_uint(&buf[pos..], sizeof_addr);
66
67 Ok(LocalHeapHeader {
68 data_size,
69 free_list_offset,
70 data_addr,
71 })
72 }
73}
74
75pub const LOCAL_HEAP_FREE_NULL: u64 = 1;
79
80pub fn local_heap_align(n: u64) -> u64 {
83 (n + 7) & !7
84}
85
86pub fn local_heap_header_size(sizeof_addr: usize, sizeof_size: usize) -> usize {
88 local_heap_align((4 + 1 + 3 + 2 * sizeof_size + sizeof_addr) as u64) as usize
89}
90
91impl LocalHeapHeader {
92 pub fn encode(&self, sizeof_addr: usize, sizeof_size: usize) -> Vec<u8> {
97 let size = local_heap_header_size(sizeof_addr, sizeof_size);
98 let mut buf = Vec::with_capacity(size);
99 buf.extend_from_slice(&LOCAL_HEAP_SIGNATURE);
100 buf.push(0); buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&self.data_size.to_le_bytes()[..sizeof_size]);
103 buf.extend_from_slice(&self.free_list_offset.to_le_bytes()[..sizeof_size]);
104 buf.extend_from_slice(&self.data_addr.to_le_bytes()[..sizeof_addr]);
105 buf.resize(size, 0); buf
107 }
108}
109
110#[derive(Debug, Default)]
118pub struct LocalHeapImage {
119 data: Vec<u8>,
120}
121
122impl LocalHeapImage {
123 pub fn with_empty_string() -> Self {
129 let mut image = Self::default();
130 let offset = image.insert(b"\0");
131 debug_assert_eq!(offset, 0);
132 image
133 }
134
135 pub fn insert(&mut self, bytes: &[u8]) -> u64 {
137 let offset = self.data.len() as u64;
138 self.data.extend_from_slice(bytes);
139 let padded = local_heap_align(self.data.len() as u64) as usize;
140 self.data.resize(padded, 0);
141 offset
142 }
143
144 pub fn insert_str(&mut self, s: &str) -> u64 {
146 let offset = self.data.len() as u64;
147 self.data.extend_from_slice(s.as_bytes());
148 self.data.push(0);
149 let padded = local_heap_align(self.data.len() as u64) as usize;
150 self.data.resize(padded, 0);
151 offset
152 }
153
154 pub fn as_bytes(&self) -> &[u8] {
156 &self.data
157 }
158}
159
160pub fn local_heap_get_string(heap_data: &[u8], offset: u64) -> FormatResult<String> {
165 let start = offset as usize;
166 if start >= heap_data.len() {
167 return Err(FormatError::InvalidData(format!(
168 "local heap offset {} out of range (heap size {})",
169 offset,
170 heap_data.len()
171 )));
172 }
173
174 let end = heap_data[start..]
176 .iter()
177 .position(|&b| b == 0)
178 .map(|p| start + p)
179 .unwrap_or(heap_data.len());
180
181 String::from_utf8(heap_data[start..end].to_vec())
182 .map_err(|e| FormatError::InvalidData(format!("invalid UTF-8 in local heap string: {}", e)))
183}
184
185#[cfg(test)]
188mod tests {
189 use super::*;
190
191 fn build_heap_header(
192 data_size: u64,
193 free_list_offset: u64,
194 data_addr: u64,
195 sizeof_addr: usize,
196 sizeof_size: usize,
197 ) -> Vec<u8> {
198 let mut buf = Vec::new();
199 buf.extend_from_slice(&LOCAL_HEAP_SIGNATURE);
200 buf.push(0); buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&data_size.to_le_bytes()[..sizeof_size]);
203 buf.extend_from_slice(&free_list_offset.to_le_bytes()[..sizeof_size]);
204 buf.extend_from_slice(&data_addr.to_le_bytes()[..sizeof_addr]);
205 buf
206 }
207
208 #[test]
209 fn decode_basic() {
210 let buf = build_heap_header(128, u64::MAX, 0x1000, 8, 8);
211 let hdr = LocalHeapHeader::decode(&buf, 8, 8).unwrap();
212 assert_eq!(hdr.data_size, 128);
213 assert_eq!(hdr.free_list_offset, u64::MAX);
214 assert_eq!(hdr.data_addr, 0x1000);
215 }
216
217 #[test]
218 fn decode_4byte() {
219 let buf = build_heap_header(64, 0xFFFFFFFF, 0x800, 4, 4);
220 let hdr = LocalHeapHeader::decode(&buf, 4, 4).unwrap();
221 assert_eq!(hdr.data_size, 64);
222 assert_eq!(hdr.free_list_offset, 0xFFFFFFFF);
223 assert_eq!(hdr.data_addr, 0x800);
224 }
225
226 #[test]
227 fn decode_bad_sig() {
228 let mut buf = build_heap_header(64, 0, 0x800, 8, 8);
229 buf[0] = b'X';
230 assert!(matches!(
231 LocalHeapHeader::decode(&buf, 8, 8).unwrap_err(),
232 FormatError::InvalidSignature
233 ));
234 }
235
236 #[test]
237 fn decode_bad_version() {
238 let mut buf = build_heap_header(64, 0, 0x800, 8, 8);
239 buf[4] = 1;
240 assert!(matches!(
241 LocalHeapHeader::decode(&buf, 8, 8).unwrap_err(),
242 FormatError::InvalidVersion(1)
243 ));
244 }
245
246 #[test]
247 fn decode_too_short() {
248 let buf = [0u8; 4];
249 assert!(matches!(
250 LocalHeapHeader::decode(&buf, 8, 8).unwrap_err(),
251 FormatError::BufferTooShort { .. }
252 ));
253 }
254
255 #[test]
256 fn get_string_basic() {
257 let mut data = Vec::new();
258 data.extend_from_slice(b"\0"); data.extend_from_slice(b"hello\0");
260 data.extend_from_slice(b"world\0");
261
262 assert_eq!(local_heap_get_string(&data, 0).unwrap(), "");
263 assert_eq!(local_heap_get_string(&data, 1).unwrap(), "hello");
264 assert_eq!(local_heap_get_string(&data, 7).unwrap(), "world");
265 }
266
267 #[test]
268 fn get_string_out_of_range() {
269 let data = b"hello\0";
270 assert!(matches!(
271 local_heap_get_string(data, 100).unwrap_err(),
272 FormatError::InvalidData(_)
273 ));
274 }
275
276 #[test]
279 fn a_local_heap_header_matches_the_bytes_libhdf5_wrote() {
280 let hdr = LocalHeapHeader {
281 data_size: 88,
282 free_list_offset: 0x20,
283 data_addr: 0x2c8,
284 };
285 let expected = [
286 b'H', b'E', b'A', b'P', 0, 0, 0, 0, 0x58, 0, 0, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0xc8, 0x02, 0, 0, 0, 0, 0, 0, ];
291 assert_eq!(hdr.encode(8, 8), expected);
292 assert_eq!(local_heap_header_size(8, 8), 32);
293 assert_eq!(LocalHeapHeader::decode(&expected, 8, 8).unwrap(), hdr);
294 }
295
296 #[test]
299 fn a_four_byte_local_heap_header_pads_to_its_alignment() {
300 assert_eq!(local_heap_header_size(4, 4), 24);
301 let hdr = LocalHeapHeader {
302 data_size: 64,
303 free_list_offset: LOCAL_HEAP_FREE_NULL,
304 data_addr: 0x800,
305 };
306 let buf = hdr.encode(4, 4);
307 assert_eq!(buf.len(), 24);
308 assert_eq!(&buf[20..], &[0, 0, 0, 0]);
309 assert_eq!(LocalHeapHeader::decode(&buf, 4, 4).unwrap(), hdr);
310 }
311
312 #[test]
315 fn a_heap_image_lays_names_out_the_way_libhdf5_does() {
316 let mut heap = LocalHeapImage::with_empty_string();
317 assert_eq!(heap.insert_str("alpha"), 8);
318 assert_eq!(heap.insert_str("beta"), 16);
319 assert_eq!(heap.insert_str("gamma"), 24);
320 assert_eq!(
321 heap.as_bytes(),
322 b"\0\0\0\0\0\0\0\0alpha\0\0\0beta\0\0\0\0gamma\0\0\0"
323 );
324 for (offset, name) in [(0, ""), (8, "alpha"), (16, "beta"), (24, "gamma")] {
325 assert_eq!(
326 local_heap_get_string(heap.as_bytes(), offset).unwrap(),
327 name
328 );
329 }
330 }
331
332 #[test]
335 fn a_heap_object_is_padded_after_its_terminator_not_before() {
336 let mut heap = LocalHeapImage::with_empty_string();
337 assert_eq!(heap.insert_str("12345678"), 8);
338 assert_eq!(heap.insert_str("next"), 24);
339 assert_eq!(heap.as_bytes().len(), 32);
340 assert_eq!(
341 local_heap_get_string(heap.as_bytes(), 8).unwrap(),
342 "12345678"
343 );
344 }
345}