1#[cfg(not(feature = "std"))]
4use alloc::{string::String, vec::Vec};
5
6use crate::btree_v1::collect_symbol_table_nodes;
7use crate::error::FormatError;
8use crate::local_heap::LocalHeap;
9use crate::message_type::MessageType;
10use crate::object_header::ObjectHeader;
11use crate::symbol_table::{SymbolTableMessage, SymbolTableNode};
12
13#[derive(Debug, Clone)]
15pub struct GroupEntry {
16 pub name: String,
18 pub object_header_address: u64,
20 pub cache_type: u32,
22}
23
24pub fn resolve_v1_group_entries(
26 file_data: &[u8],
27 sym_table_msg: &SymbolTableMessage,
28 offset_size: u8,
29 length_size: u8,
30) -> Result<Vec<GroupEntry>, FormatError> {
31 let heap = LocalHeap::parse(
33 file_data,
34 sym_table_msg.local_heap_address as usize,
35 offset_size,
36 length_size,
37 )?;
38
39 let snod_addrs = collect_symbol_table_nodes(
41 file_data,
42 sym_table_msg.btree_address,
43 offset_size,
44 length_size,
45 )?;
46
47 let mut entries = Vec::new();
48 for snod_addr in snod_addrs {
49 let snod = SymbolTableNode::parse(file_data, snod_addr as usize, offset_size)?;
50 for entry in &snod.entries {
51 let name = heap.read_string(file_data, entry.link_name_offset)?;
52 entries.push(GroupEntry {
53 name,
54 object_header_address: entry.object_header_address,
55 cache_type: entry.cache_type,
56 });
57 }
58 }
59
60 Ok(entries)
61}
62
63fn find_symbol_table_message(
65 obj_header: &ObjectHeader,
66 offset_size: u8,
67) -> Result<SymbolTableMessage, FormatError> {
68 for msg in &obj_header.messages {
69 if msg.msg_type == MessageType::SymbolTable {
70 return SymbolTableMessage::parse(&msg.data, offset_size);
71 }
72 }
73 Err(FormatError::PathNotFound(String::from(
74 "no symbol table message found in object header",
75 )))
76}
77
78pub fn resolve_path(
81 file_data: &[u8],
82 root_sym_table: &SymbolTableMessage,
83 path: &str,
84 offset_size: u8,
85 length_size: u8,
86) -> Result<u64, FormatError> {
87 let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
88 if components.is_empty() {
89 return Err(FormatError::PathNotFound(String::from(path)));
90 }
91
92 let mut current_sym_table = root_sym_table.clone();
93
94 for (i, component) in components.iter().enumerate() {
95 let entries =
96 resolve_v1_group_entries(file_data, ¤t_sym_table, offset_size, length_size)?;
97
98 let found = entries.iter().find(|e| e.name == *component);
99 match found {
100 Some(entry) => {
101 if i == components.len() - 1 {
102 return Ok(entry.object_header_address);
104 }
105 let obj_header = ObjectHeader::parse(
107 file_data,
108 entry.object_header_address as usize,
109 offset_size,
110 length_size,
111 )?;
112 current_sym_table = find_symbol_table_message(&obj_header, offset_size)?;
113 }
114 None => {
115 return Err(FormatError::PathNotFound(String::from(*component)));
116 }
117 }
118 }
119
120 Err(FormatError::PathNotFound(String::from(path)))
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::btree_v1::BTreeV1Node;
127
128 fn write_off(buf: &mut Vec<u8>, val: u64, size: u8) {
130 match size {
131 4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
132 8 => buf.extend_from_slice(&val.to_le_bytes()),
133 _ => panic!("test offset size"),
134 }
135 }
136
137 fn build_synthetic_group(
140 children: &[(&str, u64, u32)], offset_size: u8,
142 length_size: u8,
143 ) -> (Vec<u8>, SymbolTableMessage) {
144 let os = offset_size as usize;
145 let ls = length_size as usize;
146
147 let mut heap_data = Vec::new();
149 let mut name_offsets = Vec::new();
150 for (name, _, _) in children {
151 name_offsets.push(heap_data.len() as u64);
152 heap_data.extend_from_slice(name.as_bytes());
153 heap_data.push(0);
154 }
155 let heap_data_size = heap_data.len();
156
157 let heap_offset = 0usize;
164 let heap_header_size = 8 + ls * 2 + os;
165 let heap_data_offset = heap_header_size;
166 let snod_offset = heap_data_offset + heap_data_size;
167 let snod_offset = (snod_offset + 7) & !7;
169
170 let entry_size = os + os + 4 + 4 + 16;
171 let snod_size = 8 + children.len() * entry_size;
172 let btree_offset = snod_offset + snod_size;
173 let btree_offset = (btree_offset + 7) & !7;
174
175 let last_key = if children.is_empty() { 0u64 } else { heap_data_size as u64 };
177 let btree_header_size = 8 + os * 2; let btree_keys_children = os + os + os; let total_size = btree_offset + btree_header_size + btree_keys_children + 64;
180
181 let mut file = vec![0u8; total_size];
182
183 {
185 let mut pos = heap_offset;
186 file[pos..pos + 4].copy_from_slice(b"HEAP");
187 pos += 4;
188 file[pos] = 0; pos += 4; match length_size {
192 4 => file[pos..pos + 4].copy_from_slice(&(heap_data_size as u32).to_le_bytes()),
193 8 => file[pos..pos + 8].copy_from_slice(&(heap_data_size as u64).to_le_bytes()),
194 _ => {}
195 }
196 pos += ls;
197 match length_size {
199 4 => file[pos..pos + 4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes()),
200 8 => file[pos..pos + 8].copy_from_slice(&0xFFFFFFFFFFFFFFFFu64.to_le_bytes()),
201 _ => {}
202 }
203 pos += ls;
204 match offset_size {
206 4 => file[pos..pos + 4]
207 .copy_from_slice(&(heap_data_offset as u32).to_le_bytes()),
208 8 => file[pos..pos + 8]
209 .copy_from_slice(&(heap_data_offset as u64).to_le_bytes()),
210 _ => {}
211 }
212 }
213
214 file[heap_data_offset..heap_data_offset + heap_data_size].copy_from_slice(&heap_data);
216
217 {
219 let mut pos = snod_offset;
220 file[pos..pos + 4].copy_from_slice(b"SNOD");
221 pos += 4;
222 file[pos] = 1; pos += 1;
224 pos += 1; file[pos..pos + 2].copy_from_slice(&(children.len() as u16).to_le_bytes());
226 pos += 2;
227 for (idx, &(_, obj_addr, cache_type)) in children.iter().enumerate() {
228 match offset_size {
230 4 => file[pos..pos + 4]
231 .copy_from_slice(&(name_offsets[idx] as u32).to_le_bytes()),
232 8 => file[pos..pos + 8].copy_from_slice(&name_offsets[idx].to_le_bytes()),
233 _ => {}
234 }
235 pos += os;
236 match offset_size {
238 4 => file[pos..pos + 4].copy_from_slice(&(obj_addr as u32).to_le_bytes()),
239 8 => file[pos..pos + 8].copy_from_slice(&obj_addr.to_le_bytes()),
240 _ => {}
241 }
242 pos += os;
243 file[pos..pos + 4].copy_from_slice(&cache_type.to_le_bytes());
244 pos += 4;
245 pos += 4; pos += 16; }
248 }
249
250 {
252 let mut pos = btree_offset;
253 file[pos..pos + 4].copy_from_slice(b"TREE");
254 pos += 4;
255 file[pos] = 0; pos += 1;
257 file[pos] = 0; pos += 1;
259 file[pos..pos + 2].copy_from_slice(&1u16.to_le_bytes()); pos += 2;
261 for _ in 0..2 {
263 match offset_size {
264 4 => file[pos..pos + 4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes()),
265 8 => file[pos..pos + 8]
266 .copy_from_slice(&0xFFFFFFFFFFFFFFFFu64.to_le_bytes()),
267 _ => {}
268 }
269 pos += os;
270 }
271 match offset_size {
273 4 => file[pos..pos + 4].copy_from_slice(&0u32.to_le_bytes()),
274 8 => file[pos..pos + 8].copy_from_slice(&0u64.to_le_bytes()),
275 _ => {}
276 }
277 pos += os;
278 match offset_size {
280 4 => file[pos..pos + 4].copy_from_slice(&(snod_offset as u32).to_le_bytes()),
281 8 => file[pos..pos + 8].copy_from_slice(&(snod_offset as u64).to_le_bytes()),
282 _ => {}
283 }
284 pos += os;
285 match offset_size {
287 4 => file[pos..pos + 4].copy_from_slice(&(last_key as u32).to_le_bytes()),
288 8 => file[pos..pos + 8].copy_from_slice(&last_key.to_le_bytes()),
289 _ => {}
290 }
291 }
292
293 let msg = SymbolTableMessage {
294 btree_address: btree_offset as u64,
295 local_heap_address: heap_offset as u64,
296 };
297
298 (file, msg)
299 }
300
301 #[test]
302 fn resolve_entries_two_children() {
303 let (file, msg) = build_synthetic_group(
304 &[("alpha", 0x1000, 0), ("beta", 0x2000, 0)],
305 8,
306 8,
307 );
308 let entries = resolve_v1_group_entries(&file, &msg, 8, 8).unwrap();
309 assert_eq!(entries.len(), 2);
310 assert_eq!(entries[0].name, "alpha");
311 assert_eq!(entries[0].object_header_address, 0x1000);
312 assert_eq!(entries[1].name, "beta");
313 assert_eq!(entries[1].object_header_address, 0x2000);
314 }
315
316 #[test]
317 fn resolve_path_single_level() {
318 let (file, msg) = build_synthetic_group(
319 &[("child1", 0x3000, 0), ("child2", 0x4000, 0)],
320 8,
321 8,
322 );
323 let addr = resolve_path(&file, &msg, "child1", 8, 8).unwrap();
324 assert_eq!(addr, 0x3000);
325 }
326
327 #[test]
328 fn resolve_path_not_found() {
329 let (file, msg) = build_synthetic_group(&[("x", 0x100, 0)], 8, 8);
330 let err = resolve_path(&file, &msg, "nonexistent", 8, 8).unwrap_err();
331 assert!(matches!(err, FormatError::PathNotFound(_)));
332 }
333
334 fn extract_dataset(
336 file_data: &[u8],
337 hdr: &crate::object_header::ObjectHeader,
338 offset_size: u8,
339 length_size: u8,
340 ) -> (
341 crate::datatype::Datatype,
342 crate::dataspace::Dataspace,
343 crate::data_layout::DataLayout,
344 ) {
345 let dt_data = &hdr.messages.iter().find(|m| m.msg_type == MessageType::Datatype).unwrap().data;
346 let ds_data = &hdr.messages.iter().find(|m| m.msg_type == MessageType::Dataspace).unwrap().data;
347 let dl_data = &hdr.messages.iter().find(|m| m.msg_type == MessageType::DataLayout).unwrap().data;
348 let (dt, _) = crate::datatype::Datatype::parse(dt_data).unwrap();
349 let ds = crate::dataspace::Dataspace::parse(ds_data, length_size).unwrap();
350 let dl = crate::data_layout::DataLayout::parse(dl_data, offset_size, length_size).unwrap();
351 (dt, ds, dl)
352 }
353
354 fn get_root_sym_table(
355 file_data: &[u8],
356 sb: &crate::superblock::Superblock,
357 ) -> SymbolTableMessage {
358 let root_header = ObjectHeader::parse(
359 file_data, sb.root_group_address as usize, sb.offset_size, sb.length_size,
360 ).unwrap();
361 let sym_msg = root_header.messages.iter()
362 .find(|m| m.msg_type == MessageType::SymbolTable).unwrap();
363 SymbolTableMessage::parse(&sym_msg.data, sb.offset_size).unwrap()
364 }
365
366 #[test]
369 fn integration_simple_dataset_full_traversal() {
370 let file_data: &[u8] = include_bytes!("../tests/fixtures/simple_dataset.h5");
371 let sig_offset = crate::signature::find_signature(file_data).unwrap();
372 let sb = crate::superblock::Superblock::parse(file_data, sig_offset).unwrap();
373 let root_sym = get_root_sym_table(file_data, &sb);
374
375 let entries = resolve_v1_group_entries(file_data, &root_sym, sb.offset_size, sb.length_size).unwrap();
376 let data_entry = entries.iter().find(|e| e.name == "data").expect("should have 'data'");
377
378 let hdr = ObjectHeader::parse(file_data, data_entry.object_header_address as usize, sb.offset_size, sb.length_size).unwrap();
379 let (dt, ds, dl) = extract_dataset(file_data, &hdr, sb.offset_size, sb.length_size);
380 let raw = crate::data_read::read_raw_data(file_data, &dl, &ds, &dt).unwrap();
381 let values = crate::data_read::read_as_f64(&raw, &dt).unwrap();
382 assert_eq!(values, vec![1.0, 2.0, 3.0]);
383 }
384
385 #[test]
386 fn integration_two_groups_group1_values() {
387 let file_data: &[u8] = include_bytes!("../tests/fixtures/two_groups.h5");
388 let sig_offset = crate::signature::find_signature(file_data).unwrap();
389 let sb = crate::superblock::Superblock::parse(file_data, sig_offset).unwrap();
390 let root_sym = get_root_sym_table(file_data, &sb);
391
392 let addr = resolve_path(file_data, &root_sym, "group1/values", sb.offset_size, sb.length_size).unwrap();
393 let hdr = ObjectHeader::parse(file_data, addr as usize, sb.offset_size, sb.length_size).unwrap();
394 let (dt, ds, dl) = extract_dataset(file_data, &hdr, sb.offset_size, sb.length_size);
395 let raw = crate::data_read::read_raw_data(file_data, &dl, &ds, &dt).unwrap();
396 let values = crate::data_read::read_as_i32(&raw, &dt).unwrap();
397 assert_eq!(values, vec![10, 20, 30]);
398 }
399
400 #[test]
401 fn integration_two_groups_group2_temps() {
402 let file_data: &[u8] = include_bytes!("../tests/fixtures/two_groups.h5");
403 let sig_offset = crate::signature::find_signature(file_data).unwrap();
404 let sb = crate::superblock::Superblock::parse(file_data, sig_offset).unwrap();
405 let root_sym = get_root_sym_table(file_data, &sb);
406
407 let addr = resolve_path(file_data, &root_sym, "group2/temps", sb.offset_size, sb.length_size).unwrap();
408 let hdr = ObjectHeader::parse(file_data, addr as usize, sb.offset_size, sb.length_size).unwrap();
409 let (dt, ds, dl) = extract_dataset(file_data, &hdr, sb.offset_size, sb.length_size);
410 let raw = crate::data_read::read_raw_data(file_data, &dl, &ds, &dt).unwrap();
411 let values = crate::data_read::read_as_f32(&raw, &dt).unwrap();
412 assert!((values[0] - 98.6).abs() < 0.01);
413 assert!((values[1] - 37.0).abs() < 0.01);
414 }
415
416 #[test]
417 fn integration_nested_groups() {
418 let file_data: &[u8] = include_bytes!("../tests/fixtures/nested_groups.h5");
419 let sig_offset = crate::signature::find_signature(file_data).unwrap();
420 let sb = crate::superblock::Superblock::parse(file_data, sig_offset).unwrap();
421 let root_sym = get_root_sym_table(file_data, &sb);
422
423 let addr = resolve_path(file_data, &root_sym, "a/b/c/deep", sb.offset_size, sb.length_size).unwrap();
424 let hdr = ObjectHeader::parse(file_data, addr as usize, sb.offset_size, sb.length_size).unwrap();
425 let (dt, ds, dl) = extract_dataset(file_data, &hdr, sb.offset_size, sb.length_size);
426 let raw = crate::data_read::read_raw_data(file_data, &dl, &ds, &dt).unwrap();
427 let values = crate::data_read::read_as_f64(&raw, &dt).unwrap();
428 assert_eq!(values, vec![42.0]);
429 }
430}