Skip to main content

rust_hdf5/format/
superblock.rs

1/// Superblock encode/decode for HDF5 files.
2///
3/// The superblock is always at offset 0 (or at a user-hint offset) and
4/// contains the file-level metadata: version, size parameters, and addresses
5/// of the root group and end-of-file.
6///
7/// This module supports:
8/// - v2/v3 superblocks (encode + decode)
9/// - v0/v1 superblocks (decode only, for reading legacy files)
10use crate::format::checksum::checksum_metadata;
11use crate::format::{FormatError, FormatResult, UNDEF_ADDR};
12
13/// The 8-byte HDF5 file signature that begins every superblock.
14pub const HDF5_SIGNATURE: [u8; 8] = [0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a];
15
16/// Superblock version 2.
17pub const SUPERBLOCK_V2: u8 = 2;
18
19/// Superblock version 3 (adds SWMR support).
20pub const SUPERBLOCK_V3: u8 = 3;
21
22/// File consistency flag: file was opened for write access.
23pub const FLAG_WRITE_ACCESS: u8 = 0x01;
24
25/// File consistency flag: file is consistent / was properly closed.
26pub const FLAG_FILE_OK: u8 = 0x02;
27
28/// File consistency flag: file was opened for single-writer/multi-reader.
29pub const FLAG_SWMR_WRITE: u8 = 0x04;
30
31/// Superblock v2/v3 structure.
32///
33/// Layout (O = sizeof_offsets):
34/// ```text
35/// [0..8]              Signature (8 bytes)
36/// [8]                 Version (1 byte)
37/// [9]                 Size of Offsets (1 byte)
38/// [10]                Size of Lengths (1 byte)
39/// [11]                File Consistency Flags (1 byte)
40/// [12..12+O]          Base Address (O bytes)
41/// [12+O..12+2O]       Superblock Extension Address (O bytes)
42/// [12+2O..12+3O]      End of File Address (O bytes)
43/// [12+3O..12+4O]      Root Group Object Header Address (O bytes)
44/// [12+4O..12+4O+4]    Checksum (4 bytes)
45/// ```
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SuperblockV2V3 {
48    /// Superblock version: 2 or 3.
49    pub version: u8,
50    /// Size of file offsets in bytes (typically 8).
51    pub sizeof_offsets: u8,
52    /// Size of file lengths in bytes (typically 8).
53    pub sizeof_lengths: u8,
54    /// File consistency flags (see `FLAG_*` constants).
55    pub file_consistency_flags: u8,
56    /// Base address of the file (usually 0).
57    pub base_address: u64,
58    /// Address of the superblock extension object header, or UNDEF.
59    pub superblock_extension_address: u64,
60    /// End-of-file address.
61    pub end_of_file_address: u64,
62    /// Address of the root group object header.
63    pub root_group_object_header_address: u64,
64}
65
66impl SuperblockV2V3 {
67    /// Returns the total encoded size in bytes: 12 + 4*O + 4 (checksum).
68    pub fn encoded_size(&self) -> usize {
69        12 + 4 * (self.sizeof_offsets as usize) + 4
70    }
71
72    /// Encode the superblock to a byte vector, including the trailing checksum.
73    pub fn encode(&self) -> Vec<u8> {
74        let size = self.encoded_size();
75        let mut buf = Vec::with_capacity(size);
76
77        // Signature
78        buf.extend_from_slice(&HDF5_SIGNATURE);
79        // Version
80        buf.push(self.version);
81        // Size of Offsets
82        buf.push(self.sizeof_offsets);
83        // Size of Lengths
84        buf.push(self.sizeof_lengths);
85        // File Consistency Flags
86        buf.push(self.file_consistency_flags);
87
88        // Addresses -- encode as little-endian with sizeof_offsets bytes
89        let o = self.sizeof_offsets as usize;
90        encode_offset(&mut buf, self.base_address, o);
91        encode_offset(&mut buf, self.superblock_extension_address, o);
92        encode_offset(&mut buf, self.end_of_file_address, o);
93        encode_offset(&mut buf, self.root_group_object_header_address, o);
94
95        // Checksum over everything before the checksum field
96        debug_assert_eq!(buf.len(), size - 4);
97        let cksum = checksum_metadata(&buf);
98        buf.extend_from_slice(&cksum.to_le_bytes());
99
100        debug_assert_eq!(buf.len(), size);
101        buf
102    }
103
104    /// Decode a superblock from a byte buffer. Verifies the signature, version,
105    /// and checksum. Returns the parsed superblock.
106    pub fn decode(buf: &[u8]) -> FormatResult<Self> {
107        // Minimum size check: we need at least the fixed 12-byte header to
108        // read sizeof_offsets before computing the full size.
109        if buf.len() < 12 {
110            return Err(FormatError::BufferTooShort {
111                needed: 12,
112                available: buf.len(),
113            });
114        }
115
116        // Signature
117        if buf[0..8] != HDF5_SIGNATURE {
118            return Err(FormatError::InvalidSignature);
119        }
120
121        // Version
122        let version = buf[8];
123        if version != SUPERBLOCK_V2 && version != SUPERBLOCK_V3 {
124            return Err(FormatError::InvalidVersion(version));
125        }
126
127        let sizeof_offsets = buf[9];
128        let sizeof_lengths = buf[10];
129        let file_consistency_flags = buf[11];
130
131        let o = sizeof_offsets as usize;
132        let total_size = 12 + 4 * o + 4;
133        if buf.len() < total_size {
134            return Err(FormatError::BufferTooShort {
135                needed: total_size,
136                available: buf.len(),
137            });
138        }
139
140        // Verify checksum
141        let data_end = total_size - 4;
142        let stored_cksum = u32::from_le_bytes([
143            buf[data_end],
144            buf[data_end + 1],
145            buf[data_end + 2],
146            buf[data_end + 3],
147        ]);
148        let computed_cksum = checksum_metadata(&buf[..data_end]);
149        if stored_cksum != computed_cksum {
150            return Err(FormatError::ChecksumMismatch {
151                expected: stored_cksum,
152                computed: computed_cksum,
153            });
154        }
155
156        // Decode addresses
157        let mut pos = 12;
158        let base_address = decode_offset(buf, &mut pos, o);
159        let superblock_extension_address = decode_offset(buf, &mut pos, o);
160        let end_of_file_address = decode_offset(buf, &mut pos, o);
161        let root_group_object_header_address = decode_offset(buf, &mut pos, o);
162
163        Ok(SuperblockV2V3 {
164            version,
165            sizeof_offsets,
166            sizeof_lengths,
167            file_consistency_flags,
168            base_address,
169            superblock_extension_address,
170            end_of_file_address,
171            root_group_object_header_address,
172        })
173    }
174}
175
176/// Encode a u64 address as `size` little-endian bytes and append to `buf`.
177fn encode_offset(buf: &mut Vec<u8>, value: u64, size: usize) {
178    let bytes = value.to_le_bytes();
179    buf.extend_from_slice(&bytes[..size]);
180}
181
182/// Decode a little-endian address of `size` bytes from `buf` at `*pos`,
183/// advancing `*pos` past the consumed bytes.
184fn decode_offset(buf: &[u8], pos: &mut usize, size: usize) -> u64 {
185    let mut bytes = [0u8; 8];
186    bytes[..size].copy_from_slice(&buf[*pos..*pos + size]);
187    *pos += size;
188    u64::from_le_bytes(bytes)
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::format::UNDEF_ADDR;
195
196    #[test]
197    fn test_encoded_size() {
198        let sb = SuperblockV2V3 {
199            version: SUPERBLOCK_V3,
200            sizeof_offsets: 8,
201            sizeof_lengths: 8,
202            file_consistency_flags: 0,
203            base_address: 0,
204            superblock_extension_address: UNDEF_ADDR,
205            end_of_file_address: 4096,
206            root_group_object_header_address: 48,
207        };
208        // 12 + 4*8 + 4 = 48
209        assert_eq!(sb.encoded_size(), 48);
210    }
211
212    #[test]
213    fn test_roundtrip_v3_offset8() {
214        let original = SuperblockV2V3 {
215            version: SUPERBLOCK_V3,
216            sizeof_offsets: 8,
217            sizeof_lengths: 8,
218            file_consistency_flags: FLAG_FILE_OK,
219            base_address: 0,
220            superblock_extension_address: UNDEF_ADDR,
221            end_of_file_address: 0x1_0000,
222            root_group_object_header_address: 48,
223        };
224
225        let encoded = original.encode();
226        assert_eq!(encoded.len(), original.encoded_size());
227
228        // Verify signature
229        assert_eq!(&encoded[..8], &HDF5_SIGNATURE);
230
231        let decoded = SuperblockV2V3::decode(&encoded).expect("decode failed");
232        assert_eq!(decoded, original);
233    }
234
235    #[test]
236    fn test_roundtrip_v2_offset4() {
237        let original = SuperblockV2V3 {
238            version: SUPERBLOCK_V2,
239            sizeof_offsets: 4,
240            sizeof_lengths: 4,
241            file_consistency_flags: 0,
242            base_address: 0,
243            superblock_extension_address: 0xFFFF_FFFF,
244            end_of_file_address: 8192,
245            root_group_object_header_address: 28,
246        };
247
248        let encoded = original.encode();
249        // 12 + 4*4 + 4 = 32
250        assert_eq!(encoded.len(), 32);
251
252        let decoded = SuperblockV2V3::decode(&encoded).expect("decode failed");
253        assert_eq!(decoded, original);
254    }
255
256    #[test]
257    fn test_decode_bad_signature() {
258        let mut data = vec![0u8; 48];
259        // Wrong signature
260        data[0] = 0x00;
261        let err = SuperblockV2V3::decode(&data).unwrap_err();
262        assert!(matches!(err, FormatError::InvalidSignature));
263    }
264
265    #[test]
266    fn test_decode_bad_version() {
267        let sb = SuperblockV2V3 {
268            version: SUPERBLOCK_V3,
269            sizeof_offsets: 8,
270            sizeof_lengths: 8,
271            file_consistency_flags: 0,
272            base_address: 0,
273            superblock_extension_address: UNDEF_ADDR,
274            end_of_file_address: 4096,
275            root_group_object_header_address: 48,
276        };
277        let mut encoded = sb.encode();
278        // Corrupt version to 1
279        encoded[8] = 1;
280        let err = SuperblockV2V3::decode(&encoded).unwrap_err();
281        assert!(matches!(err, FormatError::InvalidVersion(1)));
282    }
283
284    #[test]
285    fn test_decode_checksum_mismatch() {
286        let sb = SuperblockV2V3 {
287            version: SUPERBLOCK_V3,
288            sizeof_offsets: 8,
289            sizeof_lengths: 8,
290            file_consistency_flags: 0,
291            base_address: 0,
292            superblock_extension_address: UNDEF_ADDR,
293            end_of_file_address: 4096,
294            root_group_object_header_address: 48,
295        };
296        let mut encoded = sb.encode();
297        // Corrupt a data byte
298        encoded[12] = 0xFF;
299        let err = SuperblockV2V3::decode(&encoded).unwrap_err();
300        assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
301    }
302
303    #[test]
304    fn test_decode_buffer_too_short() {
305        let err = SuperblockV2V3::decode(&[0u8; 4]).unwrap_err();
306        assert!(matches!(err, FormatError::BufferTooShort { .. }));
307    }
308
309    #[test]
310    fn test_flags() {
311        let sb = SuperblockV2V3 {
312            version: SUPERBLOCK_V3,
313            sizeof_offsets: 8,
314            sizeof_lengths: 8,
315            file_consistency_flags: FLAG_WRITE_ACCESS | FLAG_SWMR_WRITE,
316            base_address: 0,
317            superblock_extension_address: UNDEF_ADDR,
318            end_of_file_address: 4096,
319            root_group_object_header_address: 48,
320        };
321        let encoded = sb.encode();
322        let decoded = SuperblockV2V3::decode(&encoded).unwrap();
323        assert_eq!(
324            decoded.file_consistency_flags,
325            FLAG_WRITE_ACCESS | FLAG_SWMR_WRITE
326        );
327    }
328
329    #[test]
330    fn test_roundtrip_with_extra_trailing_data() {
331        // decode should succeed even if the buffer is longer than needed
332        let sb = SuperblockV2V3 {
333            version: SUPERBLOCK_V3,
334            sizeof_offsets: 8,
335            sizeof_lengths: 8,
336            file_consistency_flags: 0,
337            base_address: 0,
338            superblock_extension_address: UNDEF_ADDR,
339            end_of_file_address: 4096,
340            root_group_object_header_address: 48,
341        };
342        let mut encoded = sb.encode();
343        encoded.extend_from_slice(&[0xAA; 100]); // trailing garbage
344        let decoded = SuperblockV2V3::decode(&encoded).unwrap();
345        assert_eq!(decoded, sb);
346    }
347}
348
349// =========================================================================
350// Superblock v0/v1 — decode only (for reading legacy HDF5 files)
351// =========================================================================
352
353/// Symbol table entry, as stored in the root group's superblock (v0/v1).
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct SymbolTableEntry {
356    /// Offset of the name in the local heap.
357    pub name_offset: u64,
358    /// Address of the object header.
359    pub obj_header_addr: u64,
360    /// Cache type: 0 = nothing cached, 1 = symbol table (group).
361    pub cache_type: u32,
362    /// If cache_type == 1: B-tree address for group children.
363    pub btree_addr: u64,
364    /// If cache_type == 1: local heap address for group names.
365    pub heap_addr: u64,
366}
367
368/// Superblock v0/v1 structure (decode only).
369///
370/// Layout after 8-byte signature:
371/// ```text
372/// Byte 0: superblock version (0 or 1)
373/// Byte 1: free-space version (0)
374/// Byte 2: root group STE version (0)
375/// Byte 3: reserved (0)
376/// Byte 4: shared header version (0)
377/// Byte 5: sizeof_addr
378/// Byte 6: sizeof_size
379/// Byte 7: reserved (0)
380/// Bytes 8-9: sym_leaf_k (u16 LE)
381/// Bytes 10-11: btree_internal_k (u16 LE)
382/// Bytes 12-15: file_consistency_flags (u32 LE)
383/// [v1 only: bytes 16-17: indexed_storage_k (u16 LE), bytes 18-19: reserved]
384/// Then: base_addr(O), extension_addr(O), eof_addr(O), driver_addr(O)
385/// Then: root group symbol table entry
386/// ```
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct SuperblockV0V1 {
389    pub version: u8,
390    pub sizeof_offsets: u8,
391    pub sizeof_lengths: u8,
392    pub file_consistency_flags: u32,
393    pub sym_leaf_k: u16,
394    pub btree_internal_k: u16,
395    pub indexed_storage_k: Option<u16>,
396    pub base_address: u64,
397    pub superblock_extension_address: u64,
398    pub end_of_file_address: u64,
399    pub driver_info_address: u64,
400    pub root_symbol_table_entry: SymbolTableEntry,
401}
402
403impl SuperblockV0V1 {
404    /// Decode a v0/v1 superblock from `buf`. The buffer must start at the
405    /// 8-byte HDF5 signature. Returns the parsed superblock.
406    pub fn decode(buf: &[u8]) -> FormatResult<Self> {
407        // Minimum: 8 (sig) + 8 (fixed header before addresses) = 16
408        if buf.len() < 16 {
409            return Err(FormatError::BufferTooShort {
410                needed: 16,
411                available: buf.len(),
412            });
413        }
414
415        // Signature
416        if buf[0..8] != HDF5_SIGNATURE {
417            return Err(FormatError::InvalidSignature);
418        }
419
420        let version = buf[8];
421        if version != 0 && version != 1 {
422            return Err(FormatError::InvalidVersion(version));
423        }
424
425        // buf[9] = free-space version (must be 0)
426        // buf[10] = root group STE version (must be 0)
427        // buf[11] = reserved
428        // buf[12] = shared header version (must be 0)
429        let sizeof_offsets = buf[13];
430        let sizeof_lengths = buf[14];
431        // buf[15] = reserved
432
433        let o = sizeof_offsets as usize;
434        let mut pos = 16;
435
436        // Check we have enough for the remaining fixed fields
437        if buf.len() < pos + 4 {
438            return Err(FormatError::BufferTooShort {
439                needed: pos + 4,
440                available: buf.len(),
441            });
442        }
443
444        let sym_leaf_k = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
445        pos += 2;
446        let btree_internal_k = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
447        pos += 2;
448
449        if buf.len() < pos + 4 {
450            return Err(FormatError::BufferTooShort {
451                needed: pos + 4,
452                available: buf.len(),
453            });
454        }
455        let file_consistency_flags =
456            u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
457        pos += 4;
458
459        let indexed_storage_k = if version == 1 {
460            if buf.len() < pos + 4 {
461                return Err(FormatError::BufferTooShort {
462                    needed: pos + 4,
463                    available: buf.len(),
464                });
465            }
466            let k = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
467            pos += 2;
468            // 2 bytes reserved
469            pos += 2;
470            Some(k)
471        } else {
472            None
473        };
474
475        // 4 addresses, each sizeof_offsets bytes
476        let needed = pos + 4 * o;
477        if buf.len() < needed {
478            return Err(FormatError::BufferTooShort {
479                needed,
480                available: buf.len(),
481            });
482        }
483
484        let base_address = decode_offset(buf, &mut pos, o);
485        let superblock_extension_address = decode_offset(buf, &mut pos, o);
486        let end_of_file_address = decode_offset(buf, &mut pos, o);
487        let driver_info_address = decode_offset(buf, &mut pos, o);
488
489        // Root group symbol table entry
490        let ste = decode_symbol_table_entry(buf, &mut pos, o, sizeof_lengths as usize)?;
491
492        Ok(SuperblockV0V1 {
493            version,
494            sizeof_offsets,
495            sizeof_lengths,
496            file_consistency_flags,
497            sym_leaf_k,
498            btree_internal_k,
499            indexed_storage_k,
500            base_address,
501            superblock_extension_address,
502            end_of_file_address,
503            driver_info_address,
504            root_symbol_table_entry: ste,
505        })
506    }
507}
508
509/// Decode a symbol table entry from buf at the given position.
510pub fn decode_symbol_table_entry(
511    buf: &[u8],
512    pos: &mut usize,
513    sizeof_addr: usize,
514    sizeof_size: usize,
515) -> FormatResult<SymbolTableEntry> {
516    let needed = *pos + sizeof_size + sizeof_addr + 4 + 4 + 16;
517    if buf.len() < needed {
518        return Err(FormatError::BufferTooShort {
519            needed,
520            available: buf.len(),
521        });
522    }
523
524    let name_offset = decode_offset(buf, pos, sizeof_size);
525    let obj_header_addr = decode_offset(buf, pos, sizeof_addr);
526    let cache_type = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
527    *pos += 4;
528    // reserved u32
529    *pos += 4;
530
531    // Scratch pad: 16 bytes
532    let (btree_addr, heap_addr) = if cache_type == 1 {
533        let btree = decode_offset(buf, pos, sizeof_addr);
534        let heap = decode_offset(buf, pos, sizeof_addr);
535        // Skip remaining scratch pad bytes
536        let used = 2 * sizeof_addr;
537        if used < 16 {
538            *pos += 16 - used;
539        }
540        (btree, heap)
541    } else {
542        *pos += 16;
543        (UNDEF_ADDR, UNDEF_ADDR)
544    };
545
546    Ok(SymbolTableEntry {
547        name_offset,
548        obj_header_addr,
549        cache_type,
550        btree_addr,
551        heap_addr,
552    })
553}
554
555/// Detect the superblock version from the first 9+ bytes of a file.
556/// Returns the version byte (0, 1, 2, or 3).
557pub fn detect_superblock_version(buf: &[u8]) -> FormatResult<u8> {
558    if buf.len() < 9 {
559        return Err(FormatError::BufferTooShort {
560            needed: 9,
561            available: buf.len(),
562        });
563    }
564    if buf[0..8] != HDF5_SIGNATURE {
565        return Err(FormatError::InvalidSignature);
566    }
567    Ok(buf[8])
568}
569
570#[cfg(test)]
571mod tests_v0v1 {
572    use super::*;
573
574    /// Build a minimal v0 superblock for testing.
575    fn build_v0_superblock(
576        root_obj_header_addr: u64,
577        btree_addr: u64,
578        heap_addr: u64,
579        eof: u64,
580    ) -> Vec<u8> {
581        let sizeof_addr: usize = 8;
582        let sizeof_size: usize = 8;
583        let mut buf = Vec::new();
584
585        // Signature (8 bytes)
586        buf.extend_from_slice(&HDF5_SIGNATURE);
587        // Version 0
588        buf.push(0);
589        // Free-space version
590        buf.push(0);
591        // Root group STE version
592        buf.push(0);
593        // Reserved
594        buf.push(0);
595        // Shared header version
596        buf.push(0);
597        // sizeof_addr
598        buf.push(sizeof_addr as u8);
599        // sizeof_size
600        buf.push(sizeof_size as u8);
601        // Reserved
602        buf.push(0);
603
604        // sym_leaf_k = 4
605        buf.extend_from_slice(&4u16.to_le_bytes());
606        // btree_internal_k = 32
607        buf.extend_from_slice(&32u16.to_le_bytes());
608        // file_consistency_flags = 0
609        buf.extend_from_slice(&0u32.to_le_bytes());
610
611        // base_addr = 0
612        buf.extend_from_slice(&0u64.to_le_bytes()[..sizeof_addr]);
613        // extension_addr = UNDEF
614        buf.extend_from_slice(&UNDEF_ADDR.to_le_bytes()[..sizeof_addr]);
615        // eof_addr
616        buf.extend_from_slice(&eof.to_le_bytes()[..sizeof_addr]);
617        // driver_info_addr = UNDEF
618        buf.extend_from_slice(&UNDEF_ADDR.to_le_bytes()[..sizeof_addr]);
619
620        // Root group symbol table entry:
621        // name_offset (sizeof_size)
622        buf.extend_from_slice(&0u64.to_le_bytes()[..sizeof_size]);
623        // obj_header_addr (sizeof_addr)
624        buf.extend_from_slice(&root_obj_header_addr.to_le_bytes()[..sizeof_addr]);
625        // cache_type = 1 (stab)
626        buf.extend_from_slice(&1u32.to_le_bytes());
627        // reserved
628        buf.extend_from_slice(&0u32.to_le_bytes());
629        // scratch pad: btree_addr + heap_addr
630        buf.extend_from_slice(&btree_addr.to_le_bytes()[..sizeof_addr]);
631        buf.extend_from_slice(&heap_addr.to_le_bytes()[..sizeof_addr]);
632
633        buf
634    }
635
636    #[test]
637    fn test_decode_v0() {
638        let buf = build_v0_superblock(0x100, 0x200, 0x300, 0x1000);
639        let sb = SuperblockV0V1::decode(&buf).expect("decode failed");
640        assert_eq!(sb.version, 0);
641        assert_eq!(sb.sizeof_offsets, 8);
642        assert_eq!(sb.sizeof_lengths, 8);
643        assert_eq!(sb.sym_leaf_k, 4);
644        assert_eq!(sb.btree_internal_k, 32);
645        assert_eq!(sb.file_consistency_flags, 0);
646        assert_eq!(sb.indexed_storage_k, None);
647        assert_eq!(sb.base_address, 0);
648        assert_eq!(sb.end_of_file_address, 0x1000);
649        assert_eq!(sb.root_symbol_table_entry.obj_header_addr, 0x100);
650        assert_eq!(sb.root_symbol_table_entry.cache_type, 1);
651        assert_eq!(sb.root_symbol_table_entry.btree_addr, 0x200);
652        assert_eq!(sb.root_symbol_table_entry.heap_addr, 0x300);
653    }
654
655    #[test]
656    fn test_decode_v1() {
657        // Build a v1 superblock (includes indexed_storage_k)
658        let sizeof_addr: usize = 8;
659        let sizeof_size: usize = 8;
660        let mut buf = Vec::new();
661        buf.extend_from_slice(&HDF5_SIGNATURE);
662        buf.push(1); // version 1
663        buf.push(0);
664        buf.push(0);
665        buf.push(0);
666        buf.push(0);
667        buf.push(sizeof_addr as u8);
668        buf.push(sizeof_size as u8);
669        buf.push(0);
670        buf.extend_from_slice(&4u16.to_le_bytes());
671        buf.extend_from_slice(&32u16.to_le_bytes());
672        buf.extend_from_slice(&0u32.to_le_bytes());
673        // indexed_storage_k = 16
674        buf.extend_from_slice(&16u16.to_le_bytes());
675        // reserved
676        buf.extend_from_slice(&0u16.to_le_bytes());
677        // addresses
678        buf.extend_from_slice(&0u64.to_le_bytes()[..sizeof_addr]);
679        buf.extend_from_slice(&UNDEF_ADDR.to_le_bytes()[..sizeof_addr]);
680        buf.extend_from_slice(&0x2000u64.to_le_bytes()[..sizeof_addr]);
681        buf.extend_from_slice(&UNDEF_ADDR.to_le_bytes()[..sizeof_addr]);
682        // STE
683        buf.extend_from_slice(&0u64.to_le_bytes()[..sizeof_size]);
684        buf.extend_from_slice(&0x100u64.to_le_bytes()[..sizeof_addr]);
685        buf.extend_from_slice(&1u32.to_le_bytes());
686        buf.extend_from_slice(&0u32.to_le_bytes());
687        buf.extend_from_slice(&0x200u64.to_le_bytes()[..sizeof_addr]);
688        buf.extend_from_slice(&0x300u64.to_le_bytes()[..sizeof_addr]);
689
690        let sb = SuperblockV0V1::decode(&buf).expect("decode failed");
691        assert_eq!(sb.version, 1);
692        assert_eq!(sb.indexed_storage_k, Some(16));
693        assert_eq!(sb.root_symbol_table_entry.btree_addr, 0x200);
694    }
695
696    #[test]
697    fn test_detect_version() {
698        let v0 = build_v0_superblock(0x100, 0x200, 0x300, 0x1000);
699        assert_eq!(detect_superblock_version(&v0).unwrap(), 0);
700
701        let sb_v3 = SuperblockV2V3 {
702            version: SUPERBLOCK_V3,
703            sizeof_offsets: 8,
704            sizeof_lengths: 8,
705            file_consistency_flags: 0,
706            base_address: 0,
707            superblock_extension_address: UNDEF_ADDR,
708            end_of_file_address: 4096,
709            root_group_object_header_address: 48,
710        };
711        let v3 = sb_v3.encode();
712        assert_eq!(detect_superblock_version(&v3).unwrap(), 3);
713    }
714
715    #[test]
716    fn test_bad_sig() {
717        let mut buf = build_v0_superblock(0x100, 0x200, 0x300, 0x1000);
718        buf[0] = 0;
719        assert!(matches!(
720            SuperblockV0V1::decode(&buf).unwrap_err(),
721            FormatError::InvalidSignature
722        ));
723    }
724
725    #[test]
726    fn test_bad_version() {
727        let mut buf = build_v0_superblock(0x100, 0x200, 0x300, 0x1000);
728        buf[8] = 5; // invalid version
729        assert!(matches!(
730            SuperblockV0V1::decode(&buf).unwrap_err(),
731            FormatError::InvalidVersion(5)
732        ));
733    }
734}