Skip to main content

rustyhdf5_format/
superblock.rs

1//! HDF5 Superblock parsing for versions 0, 1, 2, and 3.
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use byteorder::{ByteOrder, LittleEndian};
7
8use crate::error::FormatError;
9use crate::signature::HDF5_SIGNATURE;
10
11/// Parsed HDF5 superblock (all versions).
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Superblock {
14    /// Superblock version (0–3).
15    pub version: u8,
16    /// Size of offsets in bytes (2, 4, or 8).
17    pub offset_size: u8,
18    /// Size of lengths in bytes (2, 4, or 8).
19    pub length_size: u8,
20    /// File base address.
21    pub base_address: u64,
22    /// End-of-file address.
23    pub eof_address: u64,
24    /// Root group object header address (v2/v3) or from symbol table entry (v0/v1).
25    pub root_group_address: u64,
26    /// Group leaf node K (v0/v1 only).
27    pub group_leaf_node_k: Option<u16>,
28    /// Group internal node K (v0/v1 only).
29    pub group_internal_node_k: Option<u16>,
30    /// Indexed storage internal node K (v1 only).
31    pub indexed_storage_internal_node_k: Option<u16>,
32    /// Free space address (v0/v1 only).
33    pub free_space_address: Option<u64>,
34    /// Driver info block address (v0/v1 only).
35    pub driver_info_address: Option<u64>,
36    /// File consistency flags.
37    pub consistency_flags: u32,
38    /// Superblock extension address (v2/v3 only).
39    pub superblock_extension_address: Option<u64>,
40    /// CRC32C checksum (v2/v3 only).
41    pub checksum: Option<u32>,
42}
43
44/// Read an unsigned integer of `size` bytes (LE) from `data` at `pos`.
45fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
46    let s = size as usize;
47    if pos + s > data.len() {
48        return Err(FormatError::UnexpectedEof {
49            expected: pos + s,
50            available: data.len(),
51        });
52    }
53    let slice = &data[pos..pos + s];
54    Ok(match size {
55        2 => LittleEndian::read_u16(slice) as u64,
56        4 => LittleEndian::read_u32(slice) as u64,
57        8 => LittleEndian::read_u64(slice),
58        _ => unreachable!(), // validated before calling
59    })
60}
61
62fn validate_sizes(offset_size: u8, length_size: u8) -> Result<(), FormatError> {
63    if !matches!(offset_size, 2 | 4 | 8) {
64        return Err(FormatError::InvalidOffsetSize(offset_size));
65    }
66    if !matches!(length_size, 2 | 4 | 8) {
67        return Err(FormatError::InvalidLengthSize(length_size));
68    }
69    Ok(())
70}
71
72fn ensure_len(data: &[u8], needed: usize) -> Result<(), FormatError> {
73    if data.len() < needed {
74        Err(FormatError::UnexpectedEof {
75            expected: needed,
76            available: data.len(),
77        })
78    } else {
79        Ok(())
80    }
81}
82
83impl Superblock {
84    /// Serialize this superblock to bytes.
85    ///
86    /// Always writes v2/v3 format. Computes and appends Jenkins lookup3 checksum.
87    pub fn serialize(&self) -> Vec<u8> {
88        let mut buf = Vec::with_capacity(48);
89        buf.extend_from_slice(&HDF5_SIGNATURE);
90        buf.push(self.version);
91        buf.push(self.offset_size);
92        buf.push(self.length_size);
93        buf.push(self.consistency_flags as u8);
94        // base_address
95        Self::write_offset(&mut buf, self.base_address, self.offset_size);
96        // superblock extension address
97        let ext_addr = self.superblock_extension_address.unwrap_or(u64::MAX);
98        Self::write_offset(&mut buf, ext_addr, self.offset_size);
99        // eof_address
100        Self::write_offset(&mut buf, self.eof_address, self.offset_size);
101        // root_group_address
102        Self::write_offset(&mut buf, self.root_group_address, self.offset_size);
103        // checksum
104        let checksum = crate::checksum::jenkins_lookup3(&buf);
105        buf.extend_from_slice(&checksum.to_le_bytes());
106        buf
107    }
108
109    fn write_offset(buf: &mut Vec<u8>, val: u64, size: u8) {
110        match size {
111            2 => buf.extend_from_slice(&(val as u16).to_le_bytes()),
112            4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
113            8 => buf.extend_from_slice(&val.to_le_bytes()),
114            _ => {}
115        }
116    }
117
118    /// Parse a superblock from `data` starting at `signature_offset`.
119    ///
120    /// The signature must be present at the given offset.
121    pub fn parse(data: &[u8], signature_offset: usize) -> Result<Superblock, FormatError> {
122        let d = &data[signature_offset..];
123        ensure_len(d, 9)?; // signature(8) + version(1)
124
125        // Verify signature
126        if d[..8] != HDF5_SIGNATURE {
127            return Err(FormatError::SignatureNotFound);
128        }
129
130        let version = d[8];
131        match version {
132            0 => Self::parse_v0(d),
133            1 => Self::parse_v1(d),
134            2 | 3 => Self::parse_v2v3(d, version),
135            v => Err(FormatError::UnsupportedVersion(v)),
136        }
137    }
138
139    fn parse_v0(d: &[u8]) -> Result<Superblock, FormatError> {
140        // sig(8) + version(1) + free_space_ver(1) + root_grp_ver(1) + reserved(1)
141        // + shared_hdr_ver(1) + offset_size(1) + length_size(1) + reserved(1)
142        // + group_leaf_k(2) + group_internal_k(2) + consistency_flags(4)
143        // = 24 bytes before variable-sized fields
144        ensure_len(d, 24)?;
145
146        let offset_size = d[13];
147        let length_size = d[14];
148        validate_sizes(offset_size, length_size)?;
149
150        let group_leaf_node_k = LittleEndian::read_u16(&d[16..18]);
151        let group_internal_node_k = LittleEndian::read_u16(&d[18..20]);
152        let consistency_flags = LittleEndian::read_u32(&d[20..24]);
153
154        let os = offset_size as usize;
155        // 4 addresses + root symbol table entry
156        let var_start = 24;
157        let sym_entry_size = os + os + 4 + 4 + 16; // link_name_off, obj_hdr_addr, cache_type, reserved, scratch
158        let total = var_start + 4 * os + sym_entry_size;
159        ensure_len(d, total)?;
160
161        let mut pos = var_start;
162        let base_address = read_offset(d, pos, offset_size)?;
163        pos += os;
164        let free_space_address = read_offset(d, pos, offset_size)?;
165        pos += os;
166        let eof_address = read_offset(d, pos, offset_size)?;
167        pos += os;
168        let driver_info_address = read_offset(d, pos, offset_size)?;
169        pos += os;
170
171        // Root symbol table entry
172        let _link_name_offset = read_offset(d, pos, offset_size)?;
173        pos += os;
174        let object_header_addr = read_offset(d, pos, offset_size)?;
175
176        Ok(Superblock {
177            version: 0,
178            offset_size,
179            length_size,
180            base_address,
181            eof_address,
182            root_group_address: object_header_addr,
183            group_leaf_node_k: Some(group_leaf_node_k),
184            group_internal_node_k: Some(group_internal_node_k),
185            indexed_storage_internal_node_k: None,
186            free_space_address: Some(free_space_address),
187            driver_info_address: Some(driver_info_address),
188            consistency_flags,
189            superblock_extension_address: None,
190            checksum: None,
191        })
192    }
193
194    fn parse_v1(d: &[u8]) -> Result<Superblock, FormatError> {
195        // Same as v0 but adds indexed_storage_internal_node_k(2) + reserved(2) after group_internal_k
196        // sig(8) + version(1) + free_space_ver(1) + root_grp_ver(1) + reserved(1)
197        // + shared_hdr_ver(1) + offset_size(1) + length_size(1) + reserved(1)
198        // + group_leaf_k(2) + group_internal_k(2) + indexed_storage_k(2) + reserved(2)
199        // + consistency_flags(4) = 28
200        ensure_len(d, 28)?;
201
202        let offset_size = d[13];
203        let length_size = d[14];
204        validate_sizes(offset_size, length_size)?;
205
206        let group_leaf_node_k = LittleEndian::read_u16(&d[16..18]);
207        let group_internal_node_k = LittleEndian::read_u16(&d[18..20]);
208        let indexed_storage_internal_node_k = LittleEndian::read_u16(&d[20..22]);
209        // d[22..24] reserved
210        let consistency_flags = LittleEndian::read_u32(&d[24..28]);
211
212        let os = offset_size as usize;
213        let var_start = 28;
214        let sym_entry_size = os + os + 4 + 4 + 16;
215        let total = var_start + 4 * os + sym_entry_size;
216        ensure_len(d, total)?;
217
218        let mut pos = var_start;
219        let base_address = read_offset(d, pos, offset_size)?;
220        pos += os;
221        let free_space_address = read_offset(d, pos, offset_size)?;
222        pos += os;
223        let eof_address = read_offset(d, pos, offset_size)?;
224        pos += os;
225        let driver_info_address = read_offset(d, pos, offset_size)?;
226        pos += os;
227
228        // Root symbol table entry
229        let _link_name_offset = read_offset(d, pos, offset_size)?;
230        pos += os;
231        let object_header_addr = read_offset(d, pos, offset_size)?;
232
233        Ok(Superblock {
234            version: 1,
235            offset_size,
236            length_size,
237            base_address,
238            eof_address,
239            root_group_address: object_header_addr,
240            group_leaf_node_k: Some(group_leaf_node_k),
241            group_internal_node_k: Some(group_internal_node_k),
242            indexed_storage_internal_node_k: Some(indexed_storage_internal_node_k),
243            free_space_address: Some(free_space_address),
244            driver_info_address: Some(driver_info_address),
245            consistency_flags,
246            superblock_extension_address: None,
247            checksum: None,
248        })
249    }
250
251    fn parse_v2v3(d: &[u8], version: u8) -> Result<Superblock, FormatError> {
252        // sig(8) + version(1) + offset_size(1) + length_size(1) + consistency_flags(1) = 12
253        ensure_len(d, 12)?;
254
255        let offset_size = d[9];
256        let length_size = d[10];
257        validate_sizes(offset_size, length_size)?;
258        let consistency_flags = d[11] as u32;
259
260        let os = offset_size as usize;
261        // 4 addresses + checksum(4)
262        let total = 12 + 4 * os + 4;
263        ensure_len(d, total)?;
264
265        let mut pos = 12;
266        let base_address = read_offset(d, pos, offset_size)?;
267        pos += os;
268        let superblock_extension_address = read_offset(d, pos, offset_size)?;
269        pos += os;
270        let eof_address = read_offset(d, pos, offset_size)?;
271        pos += os;
272        let root_group_address = read_offset(d, pos, offset_size)?;
273        pos += os;
274
275        let stored_checksum = LittleEndian::read_u32(&d[pos..pos + 4]);
276
277        // Validate checksum if feature enabled
278        #[cfg(feature = "checksum")]
279        {
280            let computed = crate::checksum::jenkins_lookup3(&d[..pos]);
281            if computed != stored_checksum {
282                return Err(FormatError::ChecksumMismatch {
283                    expected: stored_checksum,
284                    computed,
285                });
286            }
287        }
288
289        Ok(Superblock {
290            version,
291            offset_size,
292            length_size,
293            base_address,
294            eof_address,
295            root_group_address,
296            group_leaf_node_k: None,
297            group_internal_node_k: None,
298            indexed_storage_internal_node_k: None,
299            free_space_address: None,
300            driver_info_address: None,
301            consistency_flags,
302            superblock_extension_address: Some(superblock_extension_address),
303            checksum: Some(stored_checksum),
304        })
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    /// Helper to build a v0 superblock byte buffer with 8-byte offsets.
313    fn build_v0_bytes(offset_size: u8) -> Vec<u8> {
314        let mut buf = Vec::new();
315        buf.extend_from_slice(&HDF5_SIGNATURE); // 0..8
316        buf.push(0); // version = 0
317        buf.push(0); // free_space_version
318        buf.push(0); // root_group_version
319        buf.push(0); // reserved
320        buf.push(0); // shared_header_version
321        buf.push(offset_size); // offset_size
322        buf.push(offset_size); // length_size (same for simplicity)
323        buf.push(0); // reserved
324        buf.extend_from_slice(&4u16.to_le_bytes()); // group_leaf_node_k
325        buf.extend_from_slice(&16u16.to_le_bytes()); // group_internal_node_k
326        buf.extend_from_slice(&0u32.to_le_bytes()); // consistency_flags
327        // base_address
328        write_offset(&mut buf, 0, offset_size);
329        // free_space_address
330        write_offset(&mut buf, 0xFFFFFFFFFFFFFFFF, offset_size);
331        // eof_address
332        write_offset(&mut buf, 4096, offset_size);
333        // driver_info_address
334        write_offset(&mut buf, 0xFFFFFFFFFFFFFFFF, offset_size);
335        // Root symbol table entry
336        write_offset(&mut buf, 0, offset_size); // link_name_offset
337        write_offset(&mut buf, 96, offset_size); // object_header_addr (root group)
338        buf.extend_from_slice(&0u32.to_le_bytes()); // cache_type
339        buf.extend_from_slice(&0u32.to_le_bytes()); // reserved
340        buf.extend_from_slice(&[0u8; 16]); // scratch pad
341        buf
342    }
343
344    fn write_offset(buf: &mut Vec<u8>, val: u64, size: u8) {
345        match size {
346            2 => buf.extend_from_slice(&(val as u16).to_le_bytes()),
347            4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
348            8 => buf.extend_from_slice(&val.to_le_bytes()),
349            _ => panic!("bad test offset size"),
350        }
351    }
352
353    fn build_v1_bytes(offset_size: u8) -> Vec<u8> {
354        let mut buf = Vec::new();
355        buf.extend_from_slice(&HDF5_SIGNATURE);
356        buf.push(1); // version
357        buf.push(0); // free_space_version
358        buf.push(0); // root_group_version
359        buf.push(0); // reserved
360        buf.push(0); // shared_header_version
361        buf.push(offset_size);
362        buf.push(offset_size);
363        buf.push(0); // reserved
364        buf.extend_from_slice(&4u16.to_le_bytes()); // group_leaf_node_k
365        buf.extend_from_slice(&16u16.to_le_bytes()); // group_internal_node_k
366        buf.extend_from_slice(&32u16.to_le_bytes()); // indexed_storage_internal_node_k
367        buf.extend_from_slice(&0u16.to_le_bytes()); // reserved
368        buf.extend_from_slice(&0u32.to_le_bytes()); // consistency_flags
369        write_offset(&mut buf, 0, offset_size); // base
370        write_offset(&mut buf, 0xFFFFFFFFFFFFFFFF, offset_size); // free space
371        write_offset(&mut buf, 8192, offset_size); // eof
372        write_offset(&mut buf, 0xFFFFFFFFFFFFFFFF, offset_size); // driver info
373        // Root symbol table entry
374        write_offset(&mut buf, 0, offset_size);
375        write_offset(&mut buf, 200, offset_size); // root group addr
376        buf.extend_from_slice(&0u32.to_le_bytes());
377        buf.extend_from_slice(&0u32.to_le_bytes());
378        buf.extend_from_slice(&[0u8; 16]);
379        buf
380    }
381
382    fn build_v2_bytes(offset_size: u8, version: u8) -> Vec<u8> {
383        let mut buf = Vec::new();
384        buf.extend_from_slice(&HDF5_SIGNATURE);
385        buf.push(version);
386        buf.push(offset_size);
387        buf.push(offset_size); // length_size
388        buf.push(0); // consistency_flags
389        write_offset(&mut buf, 0, offset_size); // base_address
390        write_offset(&mut buf, 0xFFFFFFFFFFFFFFFF, offset_size); // superblock ext
391        write_offset(&mut buf, 2048, offset_size); // eof
392        write_offset(&mut buf, 48, offset_size); // root group obj hdr
393
394        // Compute CRC32C of everything so far
395        let checksum = crate::checksum::jenkins_lookup3(&buf);
396        buf.extend_from_slice(&checksum.to_le_bytes());
397        buf
398    }
399
400    #[test]
401    fn parse_v0_8byte_offsets() {
402        let data = build_v0_bytes(8);
403        let sb = Superblock::parse(&data, 0).unwrap();
404        assert_eq!(sb.version, 0);
405        assert_eq!(sb.offset_size, 8);
406        assert_eq!(sb.base_address, 0);
407        assert_eq!(sb.eof_address, 4096);
408        assert_eq!(sb.root_group_address, 96);
409        assert_eq!(sb.group_leaf_node_k, Some(4));
410        assert_eq!(sb.group_internal_node_k, Some(16));
411        assert_eq!(sb.indexed_storage_internal_node_k, None);
412        assert_eq!(sb.free_space_address, Some(0xFFFFFFFFFFFFFFFF));
413        assert_eq!(sb.driver_info_address, Some(0xFFFFFFFFFFFFFFFF));
414        assert_eq!(sb.checksum, None);
415    }
416
417    #[test]
418    fn parse_v0_4byte_offsets() {
419        let data = build_v0_bytes(4);
420        let sb = Superblock::parse(&data, 0).unwrap();
421        assert_eq!(sb.version, 0);
422        assert_eq!(sb.offset_size, 4);
423        assert_eq!(sb.eof_address, 4096);
424        assert_eq!(sb.root_group_address, 96);
425    }
426
427    #[test]
428    fn parse_v1_8byte_offsets() {
429        let data = build_v1_bytes(8);
430        let sb = Superblock::parse(&data, 0).unwrap();
431        assert_eq!(sb.version, 1);
432        assert_eq!(sb.offset_size, 8);
433        assert_eq!(sb.eof_address, 8192);
434        assert_eq!(sb.root_group_address, 200);
435        assert_eq!(sb.indexed_storage_internal_node_k, Some(32));
436        assert_eq!(sb.group_leaf_node_k, Some(4));
437    }
438
439    #[test]
440    fn parse_v1_4byte_offsets() {
441        let data = build_v1_bytes(4);
442        let sb = Superblock::parse(&data, 0).unwrap();
443        assert_eq!(sb.version, 1);
444        assert_eq!(sb.offset_size, 4);
445    }
446
447    #[test]
448    fn parse_v2_8byte_offsets() {
449        let data = build_v2_bytes(8, 2);
450        let sb = Superblock::parse(&data, 0).unwrap();
451        assert_eq!(sb.version, 2);
452        assert_eq!(sb.offset_size, 8);
453        assert_eq!(sb.eof_address, 2048);
454        assert_eq!(sb.root_group_address, 48);
455        assert!(sb.checksum.is_some());
456        assert_eq!(sb.group_leaf_node_k, None);
457    }
458
459    #[test]
460    fn parse_v2_4byte_offsets() {
461        let data = build_v2_bytes(4, 2);
462        let sb = Superblock::parse(&data, 0).unwrap();
463        assert_eq!(sb.version, 2);
464        assert_eq!(sb.offset_size, 4);
465    }
466
467    #[test]
468    fn parse_v3() {
469        let data = build_v2_bytes(8, 3);
470        let sb = Superblock::parse(&data, 0).unwrap();
471        assert_eq!(sb.version, 3);
472    }
473
474    #[test]
475    fn checksum_mismatch_v2() {
476        let mut data = build_v2_bytes(8, 2);
477        // Corrupt the checksum
478        let len = data.len();
479        data[len - 1] ^= 0xFF;
480        let err = Superblock::parse(&data, 0).unwrap_err();
481        matches!(err, FormatError::ChecksumMismatch { .. });
482    }
483
484    #[test]
485    fn unsupported_version() {
486        let mut data = vec![0u8; 64];
487        data[..8].copy_from_slice(&HDF5_SIGNATURE);
488        data[8] = 99;
489        assert_eq!(
490            Superblock::parse(&data, 0),
491            Err(FormatError::UnsupportedVersion(99))
492        );
493    }
494
495    #[test]
496    fn truncated_data() {
497        let data = HDF5_SIGNATURE.to_vec(); // Just the signature, no version
498        // Only 8 bytes, need at least 9
499        assert!(matches!(
500            Superblock::parse(&data, 0),
501            Err(FormatError::UnexpectedEof { .. })
502        ));
503    }
504
505    #[test]
506    fn truncated_v0() {
507        let mut data = vec![0u8; 20]; // Too short for v0
508        data[..8].copy_from_slice(&HDF5_SIGNATURE);
509        data[8] = 0; // version 0
510        data[13] = 8; // offset_size
511        data[14] = 8; // length_size
512        assert!(matches!(
513            Superblock::parse(&data, 0),
514            Err(FormatError::UnexpectedEof { .. })
515        ));
516    }
517
518    #[test]
519    fn invalid_offset_size() {
520        let mut data = vec![0u8; 64];
521        data[..8].copy_from_slice(&HDF5_SIGNATURE);
522        data[8] = 0; // version 0
523        data[13] = 3; // invalid offset_size
524        data[14] = 8;
525        assert_eq!(
526            Superblock::parse(&data, 0),
527            Err(FormatError::InvalidOffsetSize(3))
528        );
529    }
530
531    #[test]
532    fn invalid_length_size() {
533        let mut data = vec![0u8; 64];
534        data[..8].copy_from_slice(&HDF5_SIGNATURE);
535        data[8] = 0;
536        data[13] = 8;
537        data[14] = 5; // invalid length_size
538        assert_eq!(
539            Superblock::parse(&data, 0),
540            Err(FormatError::InvalidLengthSize(5))
541        );
542    }
543
544    #[test]
545    fn parse_at_nonzero_offset() {
546        let mut data = vec![0u8; 1024];
547        let v0 = build_v0_bytes(8);
548        data[512..512 + v0.len()].copy_from_slice(&v0);
549        let sb = Superblock::parse(&data, 512).unwrap();
550        assert_eq!(sb.version, 0);
551        assert_eq!(sb.root_group_address, 96);
552    }
553
554    #[test]
555    fn v2_2byte_offsets() {
556        let data = build_v2_bytes(2, 2);
557        let sb = Superblock::parse(&data, 0).unwrap();
558        assert_eq!(sb.offset_size, 2);
559        assert_eq!(sb.eof_address, 2048);
560    }
561}