1use crate::format::superblock::{decode_symbol_table_entry, SymbolTableEntry};
17use crate::format::{FormatError, FormatResult};
18
19pub const SNOD_SIGNATURE: [u8; 4] = *b"SNOD";
21
22#[derive(Debug, Clone)]
24pub struct SymbolTableNode {
25 pub entries: Vec<SymbolTableEntry>,
27}
28
29impl SymbolTableNode {
30 pub fn encode(
41 &self,
42 node_size: usize,
43 sizeof_addr: usize,
44 sizeof_size: usize,
45 ) -> FormatResult<Vec<u8>> {
46 let entry_size =
47 crate::format::superblock::symbol_table_entry_size(sizeof_addr, sizeof_size);
48 let needed = 8 + self.entries.len() * entry_size;
49 if needed > node_size {
50 return Err(FormatError::InvalidData(format!(
51 "symbol table node holds {} entries, {needed} bytes, more than the \
52 {node_size}-byte record the file's 'sym_leaf_k' allows",
53 self.entries.len()
54 )));
55 }
56 let Ok(num_symbols) = u16::try_from(self.entries.len()) else {
57 return Err(FormatError::InvalidData(format!(
58 "symbol table node holds {} entries, over the 2-byte count field",
59 self.entries.len()
60 )));
61 };
62
63 let mut buf = Vec::with_capacity(node_size);
64 buf.extend_from_slice(&SNOD_SIGNATURE);
65 buf.push(1); buf.push(0); buf.extend_from_slice(&num_symbols.to_le_bytes());
68 for entry in &self.entries {
69 crate::format::superblock::encode_symbol_table_entry(
70 &mut buf,
71 entry,
72 sizeof_addr,
73 sizeof_size,
74 );
75 }
76 buf.resize(node_size, 0);
77 Ok(buf)
78 }
79
80 pub fn decode(
88 buf: &[u8],
89 sizeof_addr: usize,
90 sizeof_size: usize,
91 max_entries: u16,
92 ) -> FormatResult<Self> {
93 if buf.len() < 8 {
94 return Err(FormatError::BufferTooShort {
95 needed: 8,
96 available: buf.len(),
97 });
98 }
99
100 if buf[0..4] != SNOD_SIGNATURE {
101 return Err(FormatError::InvalidSignature);
102 }
103
104 let version = buf[4];
105 if version != 1 {
106 return Err(FormatError::InvalidVersion(version));
107 }
108
109 let num_symbols = u16::from_le_bytes([buf[6], buf[7]]) as usize;
111 if num_symbols > max_entries as usize {
112 return Err(FormatError::InvalidData(format!(
113 "symbol table node declares {num_symbols} entries, capacity is {max_entries}"
114 )));
115 }
116
117 let entry_size = sizeof_size + sizeof_addr + 4 + 4 + 16;
119 let needed = 8 + num_symbols * entry_size;
120 if buf.len() < needed {
121 return Err(FormatError::BufferTooShort {
122 needed,
123 available: buf.len(),
124 });
125 }
126
127 let mut pos = 8;
128 let mut entries = Vec::with_capacity(num_symbols);
129
130 for _ in 0..num_symbols {
131 let entry = decode_symbol_table_entry(buf, &mut pos, sizeof_addr, sizeof_size)?;
132 entries.push(entry);
133 }
134
135 Ok(SymbolTableNode { entries })
136 }
137}
138
139#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::format::superblock::SymbolTableCache;
145 use crate::format::UNDEF_ADDR;
146
147 fn build_snod(
148 entries: &[(u64, u64, u32, u64, u64)],
149 sizeof_addr: usize,
150 sizeof_size: usize,
151 ) -> Vec<u8> {
152 let mut buf = Vec::new();
153 buf.extend_from_slice(&SNOD_SIGNATURE);
154 buf.push(1); buf.push(0); buf.extend_from_slice(&(entries.len() as u16).to_le_bytes());
157
158 for &(name_offset, obj_header_addr, cache_type, btree_addr, heap_addr) in entries {
159 buf.extend_from_slice(&name_offset.to_le_bytes()[..sizeof_size]);
161 buf.extend_from_slice(&obj_header_addr.to_le_bytes()[..sizeof_addr]);
163 buf.extend_from_slice(&cache_type.to_le_bytes());
165 buf.extend_from_slice(&0u32.to_le_bytes());
167 let mut scratch = Vec::new();
169 match cache_type {
170 1 => {
171 scratch.extend_from_slice(&btree_addr.to_le_bytes()[..sizeof_addr]);
172 scratch.extend_from_slice(&heap_addr.to_le_bytes()[..sizeof_addr]);
173 }
174 2 => scratch.extend_from_slice(&(btree_addr as u32).to_le_bytes()),
177 _ => {}
178 }
179 scratch.resize(16, 0);
180 buf.extend_from_slice(&scratch);
181 }
182
183 buf
184 }
185
186 #[test]
187 fn decode_basic() {
188 let snod = build_snod(
189 &[
190 (8, 0x100, 0, UNDEF_ADDR, UNDEF_ADDR), (16, 0x200, 1, 0x300, 0x400), ],
193 8,
194 8,
195 );
196 let node = SymbolTableNode::decode(&snod, 8, 8, 8).unwrap();
197 assert_eq!(node.entries.len(), 2);
198 assert_eq!(node.entries[0].name_offset, 8);
199 assert_eq!(node.entries[0].obj_header_addr, 0x100);
200 assert_eq!(node.entries[0].cache, SymbolTableCache::Nothing);
201 assert_eq!(
202 node.entries[1].cache,
203 SymbolTableCache::SymbolTable {
204 btree_addr: 0x300,
205 heap_addr: 0x400,
206 }
207 );
208 }
209
210 #[test]
216 fn decode_soft_link_entry() {
217 let snod = build_snod(&[(16, UNDEF_ADDR, 2, 24, 0)], 8, 8);
218 let node = SymbolTableNode::decode(
219 &snod,
220 8,
221 8,
222 crate::format::btree_v1::BTreeV1Config::default().sym_leaf_max_entries(),
223 )
224 .unwrap();
225 assert_eq!(node.entries.len(), 1);
226 assert_eq!(node.entries[0].name_offset, 16);
227 assert_eq!(node.entries[0].obj_header_addr, UNDEF_ADDR);
228 assert_eq!(
229 node.entries[0].cache,
230 SymbolTableCache::SoftLink { value_offset: 24 }
231 );
232 assert_eq!(node.entries[0].cached_symbol_table(), None);
233 }
234
235 #[test]
236 fn decode_empty() {
237 let snod = build_snod(&[], 8, 8);
238 let node = SymbolTableNode::decode(&snod, 8, 8, 8).unwrap();
239 assert!(node.entries.is_empty());
240 }
241
242 #[test]
243 fn decode_bad_sig() {
244 let mut snod = build_snod(&[], 8, 8);
245 snod[0] = b'X';
246 assert!(matches!(
247 SymbolTableNode::decode(&snod, 8, 8, 8).unwrap_err(),
248 FormatError::InvalidSignature
249 ));
250 }
251
252 #[test]
253 fn decode_bad_version() {
254 let mut snod = build_snod(&[], 8, 8);
255 snod[4] = 2;
256 assert!(matches!(
257 SymbolTableNode::decode(&snod, 8, 8, 8).unwrap_err(),
258 FormatError::InvalidVersion(2)
259 ));
260 }
261
262 #[test]
263 fn decode_too_short() {
264 assert!(matches!(
265 SymbolTableNode::decode(&[0u8; 4], 8, 8, 8).unwrap_err(),
266 FormatError::BufferTooShort { .. }
267 ));
268 }
269
270 #[test]
274 fn an_encoded_snod_matches_the_bytes_libhdf5_wrote() {
275 let node = SymbolTableNode {
276 entries: vec![
277 SymbolTableEntry {
278 name_offset: 8,
279 obj_header_addr: 0x320,
280 cache: SymbolTableCache::Nothing,
281 },
282 SymbolTableEntry {
283 name_offset: 16,
284 obj_header_addr: 0x578,
285 cache: SymbolTableCache::Nothing,
286 },
287 SymbolTableEntry {
288 name_offset: 24,
289 obj_header_addr: 0x688,
290 cache: SymbolTableCache::Nothing,
291 },
292 ],
293 };
294 let node_size =
295 crate::format::btree_v1::BTreeV1Config::default().symbol_table_node_size(8, 8);
296 assert_eq!(node_size, 328);
297 let encoded = node.encode(node_size, 8, 8).unwrap();
298 let mut expected = Vec::new();
299 expected.extend_from_slice(b"SNOD");
300 expected.extend_from_slice(&[1, 0, 3, 0]); for (name_offset, addr) in [(8u64, 0x320u64), (16, 0x578), (24, 0x688)] {
302 expected.extend_from_slice(&name_offset.to_le_bytes());
303 expected.extend_from_slice(&addr.to_le_bytes());
304 expected.extend_from_slice(&0u32.to_le_bytes()); expected.extend_from_slice(&0u32.to_le_bytes()); expected.extend_from_slice(&[0u8; 16]); }
308 assert_eq!(expected.len(), 8 + 3 * 40);
309 assert_eq!(&encoded[..expected.len()], &expected[..]);
310 assert!(encoded[expected.len()..].iter().all(|&b| b == 0));
311 assert_eq!(encoded.len(), node_size);
312 }
313
314 #[test]
318 fn an_encoded_snod_round_trips_every_scratch_pad_shape() {
319 let node = SymbolTableNode {
320 entries: vec![
321 SymbolTableEntry {
322 name_offset: 8,
323 obj_header_addr: 0x320,
324 cache: SymbolTableCache::SymbolTable {
325 btree_addr: 0x348,
326 heap_addr: 0x568,
327 },
328 },
329 SymbolTableEntry {
330 name_offset: 16,
331 obj_header_addr: UNDEF_ADDR,
332 cache: SymbolTableCache::SoftLink { value_offset: 24 },
333 },
334 ],
335 };
336 let cfg = crate::format::btree_v1::BTreeV1Config::default();
337 let node_size = cfg.symbol_table_node_size(8, 8);
338 let encoded = node.encode(node_size, 8, 8).unwrap();
339 let decoded = SymbolTableNode::decode(&encoded, 8, 8, cfg.sym_leaf_max_entries()).unwrap();
340 assert_eq!(decoded.entries.len(), 2);
341 assert_eq!(decoded.entries[0].cache, node.entries[0].cache);
342 assert_eq!(decoded.entries[1].cache, node.entries[1].cache);
343 assert_eq!(decoded.entries[1].obj_header_addr, UNDEF_ADDR);
344 }
345
346 #[test]
349 fn a_snod_refuses_more_entries_than_its_record_holds() {
350 let cfg = crate::format::btree_v1::BTreeV1Config::default();
351 let node = SymbolTableNode {
352 entries: (0..=cfg.sym_leaf_max_entries())
353 .map(|i| SymbolTableEntry {
354 name_offset: u64::from(i) * 8,
355 obj_header_addr: 0x100,
356 cache: SymbolTableCache::Nothing,
357 })
358 .collect(),
359 };
360 assert!(matches!(
361 node.encode(cfg.symbol_table_node_size(8, 8), 8, 8)
362 .unwrap_err(),
363 FormatError::InvalidData(_)
364 ));
365 }
366
367 #[test]
368 fn decode_4byte() {
369 let snod = build_snod(&[(4, 0x80, 0, UNDEF_ADDR, UNDEF_ADDR)], 4, 4);
370 let node = SymbolTableNode::decode(&snod, 4, 4, 8).unwrap();
371 assert_eq!(node.entries.len(), 1);
372 assert_eq!(node.entries[0].name_offset, 4);
373 assert_eq!(node.entries[0].obj_header_addr, 0x80);
374 }
375}