Skip to main content

rust_hdf5/format/
symbol_table.rs

1//! Symbol Table Node (SNOD) decode (for reading legacy HDF5 files).
2//!
3//! In v0/v1 groups, child objects are stored in a B-tree that points to
4//! symbol table nodes (SNODs). Each SNOD contains an array of symbol
5//! table entries, each describing one child object.
6//!
7//! Layout:
8//! ```text
9//! "SNOD" (4 bytes)
10//! version: 1 byte (1)
11//! reserved: 1 byte
12//! num_symbols: u16 LE
13//! entries: num_symbols * symbol_table_entry
14//! ```
15
16use crate::format::superblock::{decode_symbol_table_entry, SymbolTableEntry};
17use crate::format::{FormatError, FormatResult};
18
19/// The 4-byte SNOD signature.
20pub const SNOD_SIGNATURE: [u8; 4] = *b"SNOD";
21
22/// A decoded symbol table node.
23#[derive(Debug, Clone)]
24pub struct SymbolTableNode {
25    /// The symbol table entries in this node.
26    pub entries: Vec<SymbolTableEntry>,
27}
28
29impl SymbolTableNode {
30    /// Decode a symbol table node from `buf`.
31    ///
32    /// `sizeof_addr` and `sizeof_size` come from the superblock.
33    pub fn decode(buf: &[u8], sizeof_addr: usize, sizeof_size: usize) -> FormatResult<Self> {
34        if buf.len() < 8 {
35            return Err(FormatError::BufferTooShort {
36                needed: 8,
37                available: buf.len(),
38            });
39        }
40
41        if buf[0..4] != SNOD_SIGNATURE {
42            return Err(FormatError::InvalidSignature);
43        }
44
45        let version = buf[4];
46        if version != 1 {
47            return Err(FormatError::InvalidVersion(version));
48        }
49
50        // buf[5] reserved
51        let num_symbols = u16::from_le_bytes([buf[6], buf[7]]) as usize;
52
53        // Each entry: sizeof_size + sizeof_addr + 4 + 4 + 16 bytes
54        let entry_size = sizeof_size + sizeof_addr + 4 + 4 + 16;
55        let needed = 8 + num_symbols * entry_size;
56        if buf.len() < needed {
57            return Err(FormatError::BufferTooShort {
58                needed,
59                available: buf.len(),
60            });
61        }
62
63        let mut pos = 8;
64        let mut entries = Vec::with_capacity(num_symbols);
65
66        for _ in 0..num_symbols {
67            let entry = decode_symbol_table_entry(buf, &mut pos, sizeof_addr, sizeof_size)?;
68            entries.push(entry);
69        }
70
71        Ok(SymbolTableNode { entries })
72    }
73}
74
75// ======================================================================= tests
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::format::UNDEF_ADDR;
81
82    fn build_snod(
83        entries: &[(u64, u64, u32, u64, u64)],
84        sizeof_addr: usize,
85        sizeof_size: usize,
86    ) -> Vec<u8> {
87        let mut buf = Vec::new();
88        buf.extend_from_slice(&SNOD_SIGNATURE);
89        buf.push(1); // version
90        buf.push(0); // reserved
91        buf.extend_from_slice(&(entries.len() as u16).to_le_bytes());
92
93        for &(name_offset, obj_header_addr, cache_type, btree_addr, heap_addr) in entries {
94            // name_offset
95            buf.extend_from_slice(&name_offset.to_le_bytes()[..sizeof_size]);
96            // obj_header_addr
97            buf.extend_from_slice(&obj_header_addr.to_le_bytes()[..sizeof_addr]);
98            // cache_type
99            buf.extend_from_slice(&cache_type.to_le_bytes());
100            // reserved
101            buf.extend_from_slice(&0u32.to_le_bytes());
102            // scratch pad (16 bytes)
103            if cache_type == 1 {
104                buf.extend_from_slice(&btree_addr.to_le_bytes()[..sizeof_addr]);
105                buf.extend_from_slice(&heap_addr.to_le_bytes()[..sizeof_addr]);
106                let used = 2 * sizeof_addr;
107                if used < 16 {
108                    buf.extend_from_slice(&vec![0u8; 16 - used]);
109                }
110            } else {
111                buf.extend_from_slice(&[0u8; 16]);
112            }
113        }
114
115        buf
116    }
117
118    #[test]
119    fn decode_basic() {
120        let snod = build_snod(
121            &[
122                (8, 0x100, 0, UNDEF_ADDR, UNDEF_ADDR), // dataset
123                (16, 0x200, 1, 0x300, 0x400),          // group
124            ],
125            8,
126            8,
127        );
128        let node = SymbolTableNode::decode(&snod, 8, 8).unwrap();
129        assert_eq!(node.entries.len(), 2);
130        assert_eq!(node.entries[0].name_offset, 8);
131        assert_eq!(node.entries[0].obj_header_addr, 0x100);
132        assert_eq!(node.entries[0].cache_type, 0);
133        assert_eq!(node.entries[1].cache_type, 1);
134        assert_eq!(node.entries[1].btree_addr, 0x300);
135        assert_eq!(node.entries[1].heap_addr, 0x400);
136    }
137
138    #[test]
139    fn decode_empty() {
140        let snod = build_snod(&[], 8, 8);
141        let node = SymbolTableNode::decode(&snod, 8, 8).unwrap();
142        assert!(node.entries.is_empty());
143    }
144
145    #[test]
146    fn decode_bad_sig() {
147        let mut snod = build_snod(&[], 8, 8);
148        snod[0] = b'X';
149        assert!(matches!(
150            SymbolTableNode::decode(&snod, 8, 8).unwrap_err(),
151            FormatError::InvalidSignature
152        ));
153    }
154
155    #[test]
156    fn decode_bad_version() {
157        let mut snod = build_snod(&[], 8, 8);
158        snod[4] = 2;
159        assert!(matches!(
160            SymbolTableNode::decode(&snod, 8, 8).unwrap_err(),
161            FormatError::InvalidVersion(2)
162        ));
163    }
164
165    #[test]
166    fn decode_too_short() {
167        assert!(matches!(
168            SymbolTableNode::decode(&[0u8; 4], 8, 8).unwrap_err(),
169            FormatError::BufferTooShort { .. }
170        ));
171    }
172
173    #[test]
174    fn decode_4byte() {
175        let snod = build_snod(&[(4, 0x80, 0, UNDEF_ADDR, UNDEF_ADDR)], 4, 4);
176        let node = SymbolTableNode::decode(&snod, 4, 4).unwrap();
177        assert_eq!(node.entries.len(), 1);
178        assert_eq!(node.entries[0].name_offset, 4);
179        assert_eq!(node.entries[0].obj_header_addr, 0x80);
180    }
181}