hdf5_reader/
global_heap.rs1use crate::error::{Error, Result};
12use crate::io::Cursor;
13
14const GCOL_SIGNATURE: [u8; 4] = *b"GCOL";
16
17#[derive(Debug, Clone)]
19pub struct GlobalHeapObject {
20 pub index: u16,
22 pub reference_count: u16,
24 pub data: Vec<u8>,
26}
27
28#[derive(Debug, Clone)]
30pub struct GlobalHeapCollection {
31 pub objects: Vec<GlobalHeapObject>,
33}
34
35impl GlobalHeapCollection {
36 pub fn parse(cursor: &mut Cursor, _offset_size: u8, length_size: u8) -> Result<Self> {
53 let header_start = cursor.position();
54
55 let sig = cursor.read_bytes(4)?;
56 if sig != GCOL_SIGNATURE {
57 return Err(Error::InvalidGlobalHeapSignature);
58 }
59
60 let version = cursor.read_u8()?;
61 if version != 1 {
62 return Err(Error::UnsupportedGlobalHeapVersion(version));
63 }
64
65 cursor.skip(3)?;
67
68 let collection_size = cursor.read_length(length_size)?;
69
70 let collection_end = header_start + collection_size;
73
74 let mut objects = Vec::new();
75
76 loop {
77 let min_obj_header = 8 + length_size as u64;
80 if cursor.position() + min_obj_header > collection_end {
81 break;
82 }
83
84 let index = cursor.read_u16_le()?;
85
86 if index == 0 {
88 break;
89 }
90
91 let reference_count = cursor.read_u16_le()?;
92 cursor.skip(4)?;
94 let obj_size = cursor.read_length(length_size)?;
95
96 if cursor.position() + obj_size > collection_end {
98 return Err(Error::UnexpectedEof {
99 offset: cursor.position(),
100 needed: obj_size,
101 available: collection_end.saturating_sub(cursor.position()),
102 });
103 }
104
105 let data = cursor.read_bytes(obj_size as usize)?.to_vec();
106
107 let padded = (obj_size + 7) & !7;
109 let padding = padded - obj_size;
110 if padding > 0 && cursor.position() + padding <= collection_end {
111 cursor.skip(padding as usize)?;
112 }
113
114 objects.push(GlobalHeapObject {
115 index,
116 reference_count,
117 data,
118 });
119 }
120
121 Ok(GlobalHeapCollection { objects })
122 }
123
124 pub fn get_object(&self, index: u16) -> Option<&GlobalHeapObject> {
126 self.objects.iter().find(|o| o.index == index)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn build_gcol(objects: &[(u16, u16, &[u8])], length_size: u8) -> Vec<u8> {
137 let mut body = Vec::new();
138 for &(index, ref_count, data) in objects {
139 body.extend_from_slice(&index.to_le_bytes());
140 body.extend_from_slice(&ref_count.to_le_bytes());
141 body.extend_from_slice(&[0u8; 4]); match length_size {
143 4 => body.extend_from_slice(&(data.len() as u32).to_le_bytes()),
144 8 => body.extend_from_slice(&(data.len() as u64).to_le_bytes()),
145 _ => panic!("test only supports 4/8"),
146 }
147 body.extend_from_slice(data);
148 let padded = (data.len() + 7) & !7;
150 body.resize(body.len() + (padded - data.len()), 0);
151 }
152 body.extend_from_slice(&0u16.to_le_bytes());
154
155 let header_size = 4 + 1 + 3 + length_size as usize; let collection_size = header_size + body.len();
158
159 let mut buf = Vec::new();
160 buf.extend_from_slice(b"GCOL");
161 buf.push(1); buf.extend_from_slice(&[0, 0, 0]); match length_size {
164 4 => buf.extend_from_slice(&(collection_size as u32).to_le_bytes()),
165 8 => buf.extend_from_slice(&(collection_size as u64).to_le_bytes()),
166 _ => panic!("test only supports 4/8"),
167 }
168 buf.extend(body);
169 buf
170 }
171
172 #[test]
173 fn test_parse_empty_collection() {
174 let data = build_gcol(&[], 8);
175 let mut cursor = Cursor::new(&data);
176 let col = GlobalHeapCollection::parse(&mut cursor, 8, 8).unwrap();
177 assert!(col.objects.is_empty());
178 }
179
180 #[test]
181 fn test_parse_single_object() {
182 let obj_data = b"hello world";
183 let data = build_gcol(&[(1, 1, obj_data)], 8);
184 let mut cursor = Cursor::new(&data);
185 let col = GlobalHeapCollection::parse(&mut cursor, 8, 8).unwrap();
186
187 assert_eq!(col.objects.len(), 1);
188 assert_eq!(col.objects[0].index, 1);
189 assert_eq!(col.objects[0].reference_count, 1);
190 assert_eq!(col.objects[0].data, obj_data);
191 }
192
193 #[test]
194 fn test_parse_multiple_objects() {
195 let data = build_gcol(
196 &[
197 (1, 1, b"alpha"),
198 (2, 3, b"beta"),
199 (5, 0, b"gamma123"), ],
201 8,
202 );
203 let mut cursor = Cursor::new(&data);
204 let col = GlobalHeapCollection::parse(&mut cursor, 8, 8).unwrap();
205
206 assert_eq!(col.objects.len(), 3);
207
208 let obj1 = col.get_object(1).unwrap();
209 assert_eq!(obj1.data, b"alpha");
210 assert_eq!(obj1.reference_count, 1);
211
212 let obj2 = col.get_object(2).unwrap();
213 assert_eq!(obj2.data, b"beta");
214 assert_eq!(obj2.reference_count, 3);
215
216 let obj5 = col.get_object(5).unwrap();
217 assert_eq!(obj5.data, b"gamma123");
218
219 assert!(col.get_object(99).is_none());
220 }
221
222 #[test]
223 fn test_parse_4byte_lengths() {
224 let data = build_gcol(&[(1, 2, b"test")], 4);
225 let mut cursor = Cursor::new(&data);
226 let col = GlobalHeapCollection::parse(&mut cursor, 4, 4).unwrap();
227
228 assert_eq!(col.objects.len(), 1);
229 assert_eq!(col.objects[0].data, b"test");
230 }
231
232 #[test]
233 fn test_bad_signature() {
234 let mut data = build_gcol(&[], 8);
235 data[0] = b'X';
236 let mut cursor = Cursor::new(&data);
237 assert!(matches!(
238 GlobalHeapCollection::parse(&mut cursor, 8, 8),
239 Err(Error::InvalidGlobalHeapSignature)
240 ));
241 }
242
243 #[test]
244 fn test_bad_version() {
245 let mut data = build_gcol(&[], 8);
246 data[4] = 2; let mut cursor = Cursor::new(&data);
248 assert!(matches!(
249 GlobalHeapCollection::parse(&mut cursor, 8, 8),
250 Err(Error::UnsupportedGlobalHeapVersion(2))
251 ));
252 }
253}