Skip to main content

rustyhdf5_format/
symbol_table.rs

1//! HDF5 Symbol Table Message and Symbol Table Node (SNOD) parsing.
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::FormatError;
7
8/// Symbol Table message (type 0x0011) found in v1 group object headers.
9#[derive(Debug, Clone, PartialEq)]
10pub struct SymbolTableMessage {
11    /// Address of B-tree v1 (type 0) for this group.
12    pub btree_address: u64,
13    /// Address of the local heap for this group.
14    pub local_heap_address: u64,
15}
16
17fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
18    let s = size as usize;
19    if pos + s > data.len() {
20        return Err(FormatError::UnexpectedEof {
21            expected: pos + s,
22            available: data.len(),
23        });
24    }
25    let slice = &data[pos..pos + s];
26    Ok(match size {
27        2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
28        4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
29        8 => u64::from_le_bytes([
30            slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
31        ]),
32        _ => return Err(FormatError::InvalidOffsetSize(size)),
33    })
34}
35
36impl SymbolTableMessage {
37    /// Parse a Symbol Table message from raw message data bytes.
38    pub fn parse(data: &[u8], offset_size: u8) -> Result<SymbolTableMessage, FormatError> {
39        let os = offset_size as usize;
40        if data.len() < os * 2 {
41            return Err(FormatError::UnexpectedEof {
42                expected: os * 2,
43                available: data.len(),
44            });
45        }
46        let btree_address = read_offset(data, 0, offset_size)?;
47        let local_heap_address = read_offset(data, os, offset_size)?;
48        Ok(SymbolTableMessage {
49            btree_address,
50            local_heap_address,
51        })
52    }
53}
54
55/// A single entry in a Symbol Table Node (SNOD).
56#[derive(Debug, Clone)]
57pub struct SymbolTableEntry {
58    /// Byte offset of the link name in the local heap.
59    pub link_name_offset: u64,
60    /// Address of the child object's header.
61    pub object_header_address: u64,
62    /// Cache type: 0=none, 1=group, 2=symbolic link.
63    pub cache_type: u32,
64    /// 16-byte scratch pad (cached data).
65    pub scratch_pad: [u8; 16],
66}
67
68/// A parsed Symbol Table Node (SNOD).
69#[derive(Debug, Clone)]
70pub struct SymbolTableNode {
71    /// The symbol table entries.
72    pub entries: Vec<SymbolTableEntry>,
73}
74
75impl SymbolTableNode {
76    /// Parse a Symbol Table Node at the given offset in the file data.
77    pub fn parse(
78        file_data: &[u8],
79        offset: usize,
80        offset_size: u8,
81    ) -> Result<SymbolTableNode, FormatError> {
82        // signature(4) + version(1) + reserved(1) + number_of_symbols(2) = 8
83        if offset + 8 > file_data.len() {
84            return Err(FormatError::UnexpectedEof {
85                expected: offset + 8,
86                available: file_data.len(),
87            });
88        }
89
90        if &file_data[offset..offset + 4] != b"SNOD" {
91            return Err(FormatError::InvalidSymbolTableNodeSignature);
92        }
93
94        let version = file_data[offset + 4];
95        if version != 1 {
96            return Err(FormatError::InvalidSymbolTableNodeVersion(version));
97        }
98
99        let num_symbols =
100            u16::from_le_bytes([file_data[offset + 6], file_data[offset + 7]]) as usize;
101
102        let os = offset_size as usize;
103        // Each entry: link_name_offset(os) + obj_hdr_addr(os) + cache_type(4) + reserved(4) + scratch(16)
104        let entry_size = os + os + 4 + 4 + 16;
105        let entries_start = offset + 8;
106        let needed = entries_start + num_symbols * entry_size;
107        if needed > file_data.len() {
108            return Err(FormatError::UnexpectedEof {
109                expected: needed,
110                available: file_data.len(),
111            });
112        }
113
114        let mut entries = Vec::with_capacity(num_symbols);
115        let mut pos = entries_start;
116        for _ in 0..num_symbols {
117            let link_name_offset = read_offset(file_data, pos, offset_size)?;
118            pos += os;
119            let object_header_address = read_offset(file_data, pos, offset_size)?;
120            pos += os;
121            let cache_type = u32::from_le_bytes([
122                file_data[pos],
123                file_data[pos + 1],
124                file_data[pos + 2],
125                file_data[pos + 3],
126            ]);
127            pos += 4;
128            // reserved 4 bytes
129            pos += 4;
130            let mut scratch_pad = [0u8; 16];
131            scratch_pad.copy_from_slice(&file_data[pos..pos + 16]);
132            pos += 16;
133
134            entries.push(SymbolTableEntry {
135                link_name_offset,
136                object_header_address,
137                cache_type,
138                scratch_pad,
139            });
140        }
141
142        Ok(SymbolTableNode { entries })
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn parse_symbol_table_message_offset8() {
152        let mut data = Vec::new();
153        data.extend_from_slice(&0x1000u64.to_le_bytes()); // btree
154        data.extend_from_slice(&0x2000u64.to_le_bytes()); // heap
155        let msg = SymbolTableMessage::parse(&data, 8).unwrap();
156        assert_eq!(msg.btree_address, 0x1000);
157        assert_eq!(msg.local_heap_address, 0x2000);
158    }
159
160    #[test]
161    fn parse_symbol_table_message_offset4() {
162        let mut data = Vec::new();
163        data.extend_from_slice(&0x800u32.to_le_bytes());
164        data.extend_from_slice(&0x900u32.to_le_bytes());
165        let msg = SymbolTableMessage::parse(&data, 4).unwrap();
166        assert_eq!(msg.btree_address, 0x800);
167        assert_eq!(msg.local_heap_address, 0x900);
168    }
169
170    fn build_snod(entries: &[(u64, u64, u32)], offset_size: u8) -> Vec<u8> {
171        let mut buf = Vec::new();
172        // Pad so SNOD is at offset 0
173        buf.extend_from_slice(b"SNOD");
174        buf.push(1); // version
175        buf.push(0); // reserved
176        buf.extend_from_slice(&(entries.len() as u16).to_le_bytes());
177        for &(name_off, ohdr_addr, cache_type) in entries {
178            match offset_size {
179                4 => {
180                    buf.extend_from_slice(&(name_off as u32).to_le_bytes());
181                    buf.extend_from_slice(&(ohdr_addr as u32).to_le_bytes());
182                }
183                8 => {
184                    buf.extend_from_slice(&name_off.to_le_bytes());
185                    buf.extend_from_slice(&ohdr_addr.to_le_bytes());
186                }
187                _ => panic!("test offset_size"),
188            }
189            buf.extend_from_slice(&cache_type.to_le_bytes());
190            buf.extend_from_slice(&0u32.to_le_bytes()); // reserved
191            buf.extend_from_slice(&[0u8; 16]); // scratch pad
192        }
193        buf
194    }
195
196    #[test]
197    fn parse_snod_two_entries() {
198        let data = build_snod(&[(0, 0x100, 0), (8, 0x200, 1)], 8);
199        let snod = SymbolTableNode::parse(&data, 0, 8).unwrap();
200        assert_eq!(snod.entries.len(), 2);
201        assert_eq!(snod.entries[0].link_name_offset, 0);
202        assert_eq!(snod.entries[0].object_header_address, 0x100);
203        assert_eq!(snod.entries[0].cache_type, 0);
204        assert_eq!(snod.entries[1].link_name_offset, 8);
205        assert_eq!(snod.entries[1].object_header_address, 0x200);
206        assert_eq!(snod.entries[1].cache_type, 1);
207    }
208
209    #[test]
210    fn parse_snod_empty() {
211        let data = build_snod(&[], 8);
212        let snod = SymbolTableNode::parse(&data, 0, 8).unwrap();
213        assert_eq!(snod.entries.len(), 0);
214    }
215
216    #[test]
217    fn parse_snod_invalid_signature() {
218        let mut data = build_snod(&[], 8);
219        data[0] = b'X';
220        let err = SymbolTableNode::parse(&data, 0, 8).unwrap_err();
221        assert_eq!(err, FormatError::InvalidSymbolTableNodeSignature);
222    }
223
224    #[test]
225    fn parse_snod_invalid_version() {
226        let mut data = build_snod(&[], 8);
227        data[4] = 2; // bad version
228        let err = SymbolTableNode::parse(&data, 0, 8).unwrap_err();
229        assert_eq!(err, FormatError::InvalidSymbolTableNodeVersion(2));
230    }
231}