modelvault_core/
superblock.rs1use crate::checksum::{crc32c, CHECKSUM_KIND_CRC32C};
4use crate::error::{DbError, FormatError};
5
6pub const SUPERBLOCK_SIZE: usize = 4096;
7pub const SUPERBLOCK_MAGIC: [u8; 4] = *b"TSB0";
8pub const SUPERBLOCK_VERSION_V0: u16 = 0;
9pub const SUPERBLOCK_VERSION_V1: u16 = 1;
10pub const SUPERBLOCK_VERSION: u16 = SUPERBLOCK_VERSION_V1;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Superblock {
15 pub generation: u64,
16 pub manifest_offset: u64,
17 pub manifest_len: u32,
18 pub checkpoint_offset: u64,
19 pub checkpoint_len: u32,
20 pub checksum_kind: u8,
21}
22
23impl Superblock {
24 pub fn empty() -> Self {
25 Self {
26 generation: 0,
27 manifest_offset: 0,
28 manifest_len: 0,
29 checkpoint_offset: 0,
30 checkpoint_len: 0,
31 checksum_kind: CHECKSUM_KIND_CRC32C,
32 }
33 }
34
35 pub fn encode(self) -> [u8; SUPERBLOCK_SIZE] {
36 let mut buf = [0u8; SUPERBLOCK_SIZE];
37 buf[0..4].copy_from_slice(&SUPERBLOCK_MAGIC);
38 buf[4..6].copy_from_slice(&SUPERBLOCK_VERSION.to_le_bytes());
39
40 buf[8..16].copy_from_slice(&self.generation.to_le_bytes());
41 buf[16..24].copy_from_slice(&self.manifest_offset.to_le_bytes());
42 buf[24..28].copy_from_slice(&self.manifest_len.to_le_bytes());
43 buf[28] = self.checksum_kind;
44
45 buf[36..44].copy_from_slice(&self.checkpoint_offset.to_le_bytes());
46 buf[44..48].copy_from_slice(&self.checkpoint_len.to_le_bytes());
47
48 let crc = crc32c(&buf[0..48]);
49 buf[48..52].copy_from_slice(&crc.to_le_bytes());
50 buf
51 }
52}
53
54pub fn decode_superblock(bytes: &[u8]) -> Result<Superblock, DbError> {
55 if bytes.len() < SUPERBLOCK_SIZE {
56 return Err(DbError::Format(FormatError::TruncatedSuperblock {
57 got: bytes.len(),
58 expected: SUPERBLOCK_SIZE,
59 }));
60 }
61
62 if bytes[0..4] != SUPERBLOCK_MAGIC {
63 let mut got = [0u8; 4];
64 got.copy_from_slice(&bytes[0..4]);
65 return Err(DbError::Format(FormatError::BadSuperblockMagic { got }));
66 }
67
68 let version = u16::from_le_bytes([bytes[4], bytes[5]]);
69 if version != SUPERBLOCK_VERSION_V0 && version != SUPERBLOCK_VERSION_V1 {
70 return Err(DbError::Format(FormatError::UnsupportedVersion {
71 major: 0,
72 minor: version,
73 }));
74 }
75
76 let checksum_kind = bytes[28];
77 if checksum_kind != CHECKSUM_KIND_CRC32C {
78 return Err(DbError::Format(FormatError::UnsupportedVersion {
79 major: 0,
80 minor: checksum_kind as u16,
81 }));
82 }
83
84 let (expected_crc, actual_crc) = if version == SUPERBLOCK_VERSION_V0 {
85 (
86 u32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
87 crc32c(&bytes[0..32]),
88 )
89 } else {
90 (
91 u32::from_le_bytes([bytes[48], bytes[49], bytes[50], bytes[51]]),
92 crc32c(&bytes[0..48]),
93 )
94 };
95 if expected_crc != actual_crc {
96 return Err(DbError::Format(FormatError::BadSuperblockChecksum));
97 }
98
99 let generation = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
100 let manifest_offset = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
101 let manifest_len = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
102
103 let (checkpoint_offset, checkpoint_len) = if version == SUPERBLOCK_VERSION_V0 {
104 (0, 0)
105 } else {
106 (
107 u64::from_le_bytes(bytes[36..44].try_into().unwrap()),
108 u32::from_le_bytes(bytes[44..48].try_into().unwrap()),
109 )
110 };
111
112 Ok(Superblock {
113 generation,
114 manifest_offset,
115 manifest_len,
116 checkpoint_offset,
117 checkpoint_len,
118 checksum_kind,
119 })
120}
121
122pub fn peek_superblock_generation(bytes: &[u8]) -> Option<u64> {
124 if bytes.len() < SUPERBLOCK_SIZE || bytes[0..4] != SUPERBLOCK_MAGIC {
125 return None;
126 }
127 let version = u16::from_le_bytes([bytes[4], bytes[5]]);
128 if version != SUPERBLOCK_VERSION_V0 && version != SUPERBLOCK_VERSION_V1 {
129 return None;
130 }
131 Some(u64::from_le_bytes(bytes[8..16].try_into().ok()?))
132}
133
134pub fn select_superblock_from_pair(
139 decode_a: Result<Superblock, DbError>,
140 decode_b: Result<Superblock, DbError>,
141 peek_a: Option<u64>,
142 peek_b: Option<u64>,
143) -> Result<Superblock, DbError> {
144 match (decode_a, decode_b) {
145 (Ok(sa), Ok(sb)) => Ok(if sa.generation >= sb.generation {
146 sa
147 } else {
148 sb
149 }),
150 (Ok(sa), Err(_)) => {
151 if peek_b.is_some_and(|g| g > sa.generation) {
152 Err(DbError::Format(FormatError::BadSuperblockChecksum))
153 } else {
154 Ok(sa)
155 }
156 }
157 (Err(_), Ok(sb)) => {
158 if peek_a.is_some_and(|g| g > sb.generation) {
159 Err(DbError::Format(FormatError::BadSuperblockChecksum))
160 } else {
161 Ok(sb)
162 }
163 }
164 (Err(_), Err(_)) => Err(DbError::Format(FormatError::BadSuperblockChecksum)),
165 }
166}