Skip to main content

feoxdb/storage/
metadata.rs

1use crate::constants::*;
2use crate::error::{FeoxError, Result};
3use crate::storage::seq_token::crc32c;
4
5/// Metadata version - increment this when changing the metadata structure
6/// Version 1: Initial format
7/// Version 2: Added TTL support (8 bytes ttl_expiry field in records)
8/// Version 3: Checksummed metadata and sector-bound record tokens
9const METADATA_VERSION: u32 = 3;
10const METADATA_ENCODED_SIZE: usize = 136;
11const VERSION_OFFSET: usize = 8;
12const TOTAL_RECORDS_OFFSET: usize = 16;
13const TOTAL_SIZE_OFFSET: usize = 24;
14const DEVICE_SIZE_OFFSET: usize = 32;
15const BLOCK_SIZE_OFFSET: usize = 40;
16const FRAGMENTATION_OFFSET: usize = 44;
17const CREATION_TIME_OFFSET: usize = 48;
18const LAST_UPDATE_TIME_OFFSET: usize = 56;
19const RESERVED_OFFSET: usize = 64;
20const RESERVED_SIZE: usize = 68;
21const CHECKSUM_MAGIC: &[u8; 4] = b"FM3C";
22const CHECKSUM_OFFSET: usize = 4;
23const CHECKSUM_COMPLEMENT_OFFSET: usize = 8;
24const CHECKSUM_DATA_OFFSET: usize = 12;
25const GENERATION_OFFSET: usize = 12;
26
27#[derive(Debug, Clone, Copy)]
28pub struct Metadata {
29    pub signature: [u8; FEOX_SIGNATURE_SIZE],
30    pub version: u32,
31    pub total_records: u64,
32    pub total_size: u64,
33    pub device_size: u64,
34    pub block_size: u32,
35    pub fragmentation: u32,
36    pub creation_time: u64,
37    pub last_update_time: u64,
38    reserved: [u8; RESERVED_SIZE],
39}
40
41impl Default for Metadata {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl Metadata {
48    pub fn new() -> Self {
49        let now = std::time::SystemTime::now()
50            .duration_since(std::time::UNIX_EPOCH)
51            .unwrap_or_else(|_| std::time::Duration::from_secs(0))
52            .as_secs();
53
54        let mut metadata = Self {
55            signature: *FEOX_SIGNATURE,
56            version: METADATA_VERSION,
57            total_records: 0,
58            total_size: 0,
59            device_size: 0,
60            block_size: FEOX_BLOCK_SIZE as u32,
61            fragmentation: 0,
62            creation_time: now,
63            last_update_time: now,
64            reserved: [0; RESERVED_SIZE],
65        };
66        metadata.refresh_checksum();
67        metadata
68    }
69
70    pub fn validate(&self) -> bool {
71        if self.signature != *FEOX_SIGNATURE {
72            return false;
73        }
74
75        if self.block_size != FEOX_BLOCK_SIZE as u32 {
76            return false;
77        }
78
79        if self.version == 0 || self.version > METADATA_VERSION {
80            return false;
81        }
82
83        if self.device_size == 0 || self.device_size > MAX_DEVICE_SIZE {
84            return false;
85        }
86
87        let has_checksum = &self.reserved[..CHECKSUM_MAGIC.len()] == CHECKSUM_MAGIC;
88        if self.version >= 3 && !has_checksum {
89            return false;
90        }
91        if !has_checksum {
92            return true;
93        }
94
95        let checksum = u32::from_le_bytes(
96            self.reserved[CHECKSUM_OFFSET..CHECKSUM_OFFSET + 4]
97                .try_into()
98                .unwrap(),
99        );
100        let complement = u32::from_le_bytes(
101            self.reserved[CHECKSUM_COMPLEMENT_OFFSET..CHECKSUM_COMPLEMENT_OFFSET + 4]
102                .try_into()
103                .unwrap(),
104        );
105        complement == !checksum && checksum == self.checksum()
106    }
107
108    pub fn update(&mut self) {
109        self.last_update_time = std::time::SystemTime::now()
110            .duration_since(std::time::UNIX_EPOCH)
111            .unwrap_or_else(|_| std::time::Duration::from_secs(0))
112            .as_secs();
113        self.refresh_checksum();
114    }
115
116    pub fn encode(&self) -> [u8; METADATA_ENCODED_SIZE] {
117        let mut bytes = [0; METADATA_ENCODED_SIZE];
118        bytes[..FEOX_SIGNATURE_SIZE].copy_from_slice(&self.signature);
119        bytes[VERSION_OFFSET..VERSION_OFFSET + 4].copy_from_slice(&self.version.to_le_bytes());
120        bytes[TOTAL_RECORDS_OFFSET..TOTAL_RECORDS_OFFSET + 8]
121            .copy_from_slice(&self.total_records.to_le_bytes());
122        bytes[TOTAL_SIZE_OFFSET..TOTAL_SIZE_OFFSET + 8]
123            .copy_from_slice(&self.total_size.to_le_bytes());
124        bytes[DEVICE_SIZE_OFFSET..DEVICE_SIZE_OFFSET + 8]
125            .copy_from_slice(&self.device_size.to_le_bytes());
126        bytes[BLOCK_SIZE_OFFSET..BLOCK_SIZE_OFFSET + 4]
127            .copy_from_slice(&self.block_size.to_le_bytes());
128        bytes[FRAGMENTATION_OFFSET..FRAGMENTATION_OFFSET + 4]
129            .copy_from_slice(&self.fragmentation.to_le_bytes());
130        bytes[CREATION_TIME_OFFSET..CREATION_TIME_OFFSET + 8]
131            .copy_from_slice(&self.creation_time.to_le_bytes());
132        bytes[LAST_UPDATE_TIME_OFFSET..LAST_UPDATE_TIME_OFFSET + 8]
133            .copy_from_slice(&self.last_update_time.to_le_bytes());
134        bytes[RESERVED_OFFSET..RESERVED_OFFSET + RESERVED_SIZE].copy_from_slice(&self.reserved);
135        bytes
136    }
137
138    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
139        if bytes.len() < METADATA_ENCODED_SIZE {
140            return None;
141        }
142
143        let metadata = Self {
144            signature: bytes[..FEOX_SIGNATURE_SIZE].try_into().ok()?,
145            version: u32::from_le_bytes(bytes[VERSION_OFFSET..VERSION_OFFSET + 4].try_into().ok()?),
146            total_records: u64::from_le_bytes(
147                bytes[TOTAL_RECORDS_OFFSET..TOTAL_RECORDS_OFFSET + 8]
148                    .try_into()
149                    .ok()?,
150            ),
151            total_size: u64::from_le_bytes(
152                bytes[TOTAL_SIZE_OFFSET..TOTAL_SIZE_OFFSET + 8]
153                    .try_into()
154                    .ok()?,
155            ),
156            device_size: u64::from_le_bytes(
157                bytes[DEVICE_SIZE_OFFSET..DEVICE_SIZE_OFFSET + 8]
158                    .try_into()
159                    .ok()?,
160            ),
161            block_size: u32::from_le_bytes(
162                bytes[BLOCK_SIZE_OFFSET..BLOCK_SIZE_OFFSET + 4]
163                    .try_into()
164                    .ok()?,
165            ),
166            fragmentation: u32::from_le_bytes(
167                bytes[FRAGMENTATION_OFFSET..FRAGMENTATION_OFFSET + 4]
168                    .try_into()
169                    .ok()?,
170            ),
171            creation_time: u64::from_le_bytes(
172                bytes[CREATION_TIME_OFFSET..CREATION_TIME_OFFSET + 8]
173                    .try_into()
174                    .ok()?,
175            ),
176            last_update_time: u64::from_le_bytes(
177                bytes[LAST_UPDATE_TIME_OFFSET..LAST_UPDATE_TIME_OFFSET + 8]
178                    .try_into()
179                    .ok()?,
180            ),
181            reserved: bytes[RESERVED_OFFSET..RESERVED_OFFSET + RESERVED_SIZE]
182                .try_into()
183                .ok()?,
184        };
185
186        if metadata.validate() {
187            Some(metadata)
188        } else {
189            None
190        }
191    }
192
193    pub(crate) fn generation(&self) -> u64 {
194        u64::from_le_bytes(
195            self.reserved[GENERATION_OFFSET..GENERATION_OFFSET + 8]
196                .try_into()
197                .unwrap(),
198        )
199    }
200
201    pub(crate) fn advance_generation(&mut self) -> Result<()> {
202        let generation = self
203            .generation()
204            .checked_add(1)
205            .ok_or(FeoxError::InvalidMetadata)?;
206        self.reserved[GENERATION_OFFSET..GENERATION_OFFSET + 8]
207            .copy_from_slice(&generation.to_le_bytes());
208        self.refresh_checksum();
209        Ok(())
210    }
211
212    fn refresh_checksum(&mut self) {
213        self.reserved[..CHECKSUM_MAGIC.len()].copy_from_slice(CHECKSUM_MAGIC);
214        let checksum = self.checksum();
215        self.reserved[CHECKSUM_OFFSET..CHECKSUM_OFFSET + 4]
216            .copy_from_slice(&checksum.to_le_bytes());
217        self.reserved[CHECKSUM_COMPLEMENT_OFFSET..CHECKSUM_COMPLEMENT_OFFSET + 4]
218            .copy_from_slice(&(!checksum).to_le_bytes());
219    }
220
221    fn checksum(&self) -> u32 {
222        let mut checksum = crc32c(0, &self.signature);
223        checksum = crc32c(checksum, &self.version.to_le_bytes());
224        checksum = crc32c(checksum, &self.total_records.to_le_bytes());
225        checksum = crc32c(checksum, &self.total_size.to_le_bytes());
226        checksum = crc32c(checksum, &self.device_size.to_le_bytes());
227        checksum = crc32c(checksum, &self.block_size.to_le_bytes());
228        checksum = crc32c(checksum, &self.fragmentation.to_le_bytes());
229        checksum = crc32c(checksum, &self.creation_time.to_le_bytes());
230        checksum = crc32c(checksum, &self.last_update_time.to_le_bytes());
231        crc32c(checksum, &self.reserved[CHECKSUM_DATA_OFFSET..])
232    }
233}