hdf5_reader/
local_heap.rs1use crate::error::{Error, Result};
12use crate::io::Cursor;
13
14const HEAP_SIGNATURE: [u8; 4] = *b"HEAP";
16
17#[derive(Debug, Clone)]
22pub struct LocalHeap {
23 pub data_segment_size: u64,
25 pub free_list_offset: u64,
28 pub data_segment_address: u64,
30}
31
32impl LocalHeap {
33 pub fn parse(cursor: &mut Cursor, offset_size: u8, length_size: u8) -> Result<Self> {
43 let sig = cursor.read_bytes(4)?;
44 if sig != HEAP_SIGNATURE {
45 return Err(Error::InvalidLocalHeapSignature);
46 }
47
48 let version = cursor.read_u8()?;
49 if version != 0 {
50 return Err(Error::UnsupportedLocalHeapVersion(version));
51 }
52
53 cursor.skip(3)?;
55
56 let data_segment_size = cursor.read_length(length_size)?;
57 let free_list_offset = cursor.read_length(length_size)?;
58 let data_segment_address = cursor.read_offset(offset_size)?;
59
60 Ok(LocalHeap {
61 data_segment_size,
62 free_list_offset,
63 data_segment_address,
64 })
65 }
66
67 pub fn get_string(&self, offset: u64, file_data: &[u8]) -> Result<String> {
74 let abs = self
75 .data_segment_address
76 .checked_add(offset)
77 .ok_or(Error::OffsetOutOfBounds(offset))?;
78 let start = abs as usize;
79
80 if start >= file_data.len() {
81 return Err(Error::OffsetOutOfBounds(abs));
82 }
83
84 let segment_end = (self.data_segment_address as usize)
86 .saturating_add(self.data_segment_size as usize)
87 .min(file_data.len());
88
89 let search_region = &file_data[start..segment_end];
90 let null_pos = search_region.iter().position(|&b| b == 0).ok_or_else(|| {
91 Error::InvalidData("local heap string missing null terminator".into())
92 })?;
93
94 let s = std::str::from_utf8(&search_region[..null_pos])
95 .map_err(|e| Error::InvalidData(format!("invalid UTF-8 in local heap string: {e}")))?;
96
97 Ok(s.to_string())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn build_heap_header(
107 data_segment_size: u64,
108 free_list_offset: u64,
109 data_segment_address: u64,
110 ) -> Vec<u8> {
111 let mut buf = Vec::new();
112 buf.extend_from_slice(b"HEAP");
113 buf.push(0); buf.extend_from_slice(&[0, 0, 0]); buf.extend_from_slice(&data_segment_size.to_le_bytes());
116 buf.extend_from_slice(&free_list_offset.to_le_bytes());
117 buf.extend_from_slice(&data_segment_address.to_le_bytes());
118 buf
119 }
120
121 #[test]
122 fn test_parse_local_heap() {
123 let data = build_heap_header(256, 128, 0x2000);
124
125 let mut cursor = Cursor::new(&data);
126 let heap = LocalHeap::parse(&mut cursor, 8, 8).unwrap();
127
128 assert_eq!(heap.data_segment_size, 256);
129 assert_eq!(heap.free_list_offset, 128);
130 assert_eq!(heap.data_segment_address, 0x2000);
131 }
132
133 #[test]
134 fn test_parse_local_heap_4byte() {
135 let mut buf = Vec::new();
136 buf.extend_from_slice(b"HEAP");
137 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);
144 let heap = LocalHeap::parse(&mut cursor, 4, 4).unwrap();
145
146 assert_eq!(heap.data_segment_size, 64);
147 assert_eq!(heap.free_list_offset, 32);
148 assert_eq!(heap.data_segment_address, 0x400);
149 }
150
151 #[test]
152 fn test_bad_signature() {
153 let mut data = build_heap_header(256, 128, 0x2000);
154 data[0] = b'X'; let mut cursor = Cursor::new(&data);
156 assert!(matches!(
157 LocalHeap::parse(&mut cursor, 8, 8),
158 Err(Error::InvalidLocalHeapSignature)
159 ));
160 }
161
162 #[test]
163 fn test_bad_version() {
164 let mut data = build_heap_header(256, 128, 0x2000);
165 data[4] = 1; let mut cursor = Cursor::new(&data);
167 assert!(matches!(
168 LocalHeap::parse(&mut cursor, 8, 8),
169 Err(Error::UnsupportedLocalHeapVersion(1))
170 ));
171 }
172
173 #[test]
174 fn test_get_string() {
175 let mut file_data = vec![0u8; 200];
177 let seg_start = 100usize;
179 file_data[seg_start..seg_start + 6].copy_from_slice(b"hello\0");
180 file_data[seg_start + 6..seg_start + 12].copy_from_slice(b"world\0");
181
182 let heap = LocalHeap {
183 data_segment_size: 100,
184 free_list_offset: 50,
185 data_segment_address: seg_start as u64,
186 };
187
188 assert_eq!(heap.get_string(0, &file_data).unwrap(), "hello");
189 assert_eq!(heap.get_string(6, &file_data).unwrap(), "world");
190 }
191
192 #[test]
193 fn test_get_string_out_of_bounds() {
194 let file_data = vec![0u8; 50];
195 let heap = LocalHeap {
196 data_segment_size: 100,
197 free_list_offset: 0,
198 data_segment_address: 100, };
200 assert!(heap.get_string(0, &file_data).is_err());
201 }
202
203 #[test]
204 fn test_get_string_missing_null() {
205 let mut file_data = vec![0xFFu8; 200];
207 file_data[100..105].copy_from_slice(b"abcde");
208
209 let heap = LocalHeap {
210 data_segment_size: 5,
211 free_list_offset: 0,
212 data_segment_address: 100,
213 };
214 assert!(heap.get_string(0, &file_data).is_err());
215 }
216}