Coverage Report

Created: 2026-07-31 00:13

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