commonware_runtime/utils/buffer/paged/
mod.rs1use crate::{Blob, Buf, BufMut, Error, IoBuf};
27use commonware_codec::{EncodeFixed, FixedSize, Read as CodecRead, ReadExt, Write};
28use commonware_cryptography::{crc32, Crc32};
29
30mod append;
31mod cache;
32mod read;
33
34pub use append::Append;
35pub use cache::CacheRef;
36pub use read::Replay;
37use tracing::{debug, error};
38
39const CHECKSUM_SIZE: u64 = Checksum::SIZE as u64;
41
42async fn get_page_from_blob(
46 blob: &impl Blob,
47 page_num: u64,
48 logical_page_size: u64,
49) -> Result<IoBuf, Error> {
50 let physical_page_size = logical_page_size + CHECKSUM_SIZE;
51 let physical_page_start = page_num * physical_page_size;
52
53 let page = blob
54 .read_at(physical_page_start, physical_page_size as usize)
55 .await?
56 .coalesce();
57
58 let Some(record) = Checksum::validate_page(page.as_ref()) else {
59 return Err(Error::InvalidChecksum);
60 };
61 let (len, _) = record.get_crc();
62
63 Ok(page.freeze().slice(..len as usize))
64}
65
66#[derive(Clone)]
72struct Checksum {
73 len1: u16,
74 crc1: u32,
75 len2: u16,
76 crc2: u32,
77}
78
79impl Checksum {
80 const fn new(len: u16, crc: u32) -> Self {
83 Self {
84 len1: len,
85 crc1: crc,
86 len2: 0,
87 crc2: 0,
88 }
89 }
90
91 fn validate_page(buf: &[u8]) -> Option<Self> {
96 let page_size = buf.len() as u64;
97 if page_size < CHECKSUM_SIZE {
98 error!(
99 page_size,
100 required = CHECKSUM_SIZE,
101 "read page smaller than CRC record"
102 );
103 return None;
104 }
105
106 let crc_start_idx = (page_size - CHECKSUM_SIZE) as usize;
107 let mut crc_bytes = &buf[crc_start_idx..];
108 let mut crc_record = Self::read(&mut crc_bytes).expect("CRC record read should not fail");
109 let (len, crc) = crc_record.get_crc();
110
111 let len_usize = len as usize;
114 if len_usize == 0 {
115 debug!("Invalid CRC: len==0");
117 return None;
118 }
119
120 if len_usize > crc_start_idx {
121 debug!("Invalid CRC: len too long. Using fallback CRC");
123 if crc_record.validate_fallback(buf, crc_start_idx) {
124 return Some(crc_record);
125 }
126 return None;
127 }
128
129 let computed_crc = Crc32::checksum(&buf[..len_usize]);
130 if computed_crc != crc {
131 debug!("Invalid CRC: doesn't match page contents. Using fallback CRC");
132 if crc_record.validate_fallback(buf, crc_start_idx) {
133 return Some(crc_record);
134 }
135 return None;
136 }
137
138 Some(crc_record)
139 }
140
141 fn validate_fallback(&mut self, buf: &[u8], crc_start_idx: usize) -> bool {
145 let (len, crc) = self.get_fallback_crc();
146 if len == 0 {
147 debug!("Invalid fallback CRC: len==0");
149 return false;
150 }
151
152 let len_usize = len as usize;
153
154 if len_usize > crc_start_idx {
155 debug!("Invalid fallback CRC: len too long.");
157 return false;
158 }
159
160 let computed_crc = Crc32::checksum(&buf[..len_usize]);
161 if computed_crc != crc {
162 debug!("Invalid fallback CRC: doesn't match page contents.");
163 return false;
164 }
165
166 true
167 }
168
169 const fn get_crc(&self) -> (u16, u32) {
173 if self.len1 >= self.len2 {
174 (self.len1, self.crc1)
175 } else {
176 (self.len2, self.crc2)
177 }
178 }
179
180 const fn get_fallback_crc(&mut self) -> (u16, u32) {
184 if self.len1 >= self.len2 {
185 self.len1 = 0;
187 self.crc1 = 0;
188 (self.len2, self.crc2)
189 } else {
190 self.len2 = 0;
192 self.crc2 = 0;
193 (self.len1, self.crc1)
194 }
195 }
196
197 fn to_bytes(&self) -> [u8; CHECKSUM_SIZE as usize] {
199 self.encode_fixed()
200 }
201}
202
203impl Write for Checksum {
204 fn write(&self, buf: &mut impl BufMut) {
205 self.len1.write(buf);
206 self.crc1.write(buf);
207 self.len2.write(buf);
208 self.crc2.write(buf);
209 }
210}
211
212impl CodecRead for Checksum {
213 type Cfg = ();
214
215 fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
216 Ok(Self {
217 len1: u16::read(buf)?,
218 crc1: u32::read(buf)?,
219 len2: u16::read(buf)?,
220 crc2: u32::read(buf)?,
221 })
222 }
223}
224
225impl FixedSize for Checksum {
226 const SIZE: usize = 2 * u16::SIZE + 2 * crc32::Digest::SIZE;
227}
228
229#[cfg(feature = "arbitrary")]
230impl arbitrary::Arbitrary<'_> for Checksum {
231 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
232 Ok(Self {
233 len1: u.arbitrary()?,
234 crc1: u.arbitrary()?,
235 len2: u.arbitrary()?,
236 crc2: u.arbitrary()?,
237 })
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_crc_record_encode_read_roundtrip() {
247 let record = Checksum {
248 len1: 0x1234,
249 crc1: 0xAABBCCDD,
250 len2: 0x5678,
251 crc2: 0x11223344,
252 };
253
254 let bytes = record.to_bytes();
255 let restored = Checksum::read(&mut &bytes[..]).unwrap();
256
257 assert_eq!(restored.len1, 0x1234);
258 assert_eq!(restored.crc1, 0xAABBCCDD);
259 assert_eq!(restored.len2, 0x5678);
260 assert_eq!(restored.crc2, 0x11223344);
261 }
262
263 #[test]
264 fn test_crc_record_encoding() {
265 let record = Checksum {
266 len1: 0x0102,
267 crc1: 0x03040506,
268 len2: 0x0708,
269 crc2: 0x090A0B0C,
270 };
271
272 let bytes = record.to_bytes();
273 assert_eq!(
275 bytes,
276 [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]
277 );
278 }
279
280 #[test]
281 fn test_crc_record_get_crc_len1_larger() {
282 let record = Checksum {
283 len1: 200,
284 crc1: 0xAAAAAAAA,
285 len2: 100,
286 crc2: 0xBBBBBBBB,
287 };
288
289 let (len, crc) = record.get_crc();
290 assert_eq!(len, 200);
291 assert_eq!(crc, 0xAAAAAAAA);
292 }
293
294 #[test]
295 fn test_crc_record_get_crc_len2_larger() {
296 let record = Checksum {
297 len1: 100,
298 crc1: 0xAAAAAAAA,
299 len2: 200,
300 crc2: 0xBBBBBBBB,
301 };
302
303 let (len, crc) = record.get_crc();
304 assert_eq!(len, 200);
305 assert_eq!(crc, 0xBBBBBBBB);
306 }
307
308 #[test]
309 fn test_crc_record_get_crc_equal_lengths() {
310 let record = Checksum {
312 len1: 100,
313 crc1: 0xAAAAAAAA,
314 len2: 100,
315 crc2: 0xBBBBBBBB,
316 };
317
318 let (len, crc) = record.get_crc();
319 assert_eq!(len, 100);
320 assert_eq!(crc, 0xAAAAAAAA);
321 }
322
323 #[test]
324 fn test_validate_page_valid() {
325 let logical_page_size = 64usize;
326 let physical_page_size = logical_page_size + Checksum::SIZE;
327 let mut page = vec![0u8; physical_page_size];
328
329 let data = b"hello world";
331 page[..data.len()].copy_from_slice(data);
332
333 let crc = Crc32::checksum(&page[..data.len()]);
335 let record = Checksum::new(data.len() as u16, crc);
336
337 let crc_start = physical_page_size - Checksum::SIZE;
339 page[crc_start..].copy_from_slice(&record.to_bytes());
340
341 let validated = Checksum::validate_page(&page);
343 assert!(validated.is_some());
344 let (len, _) = validated.unwrap().get_crc();
345 assert_eq!(len as usize, data.len());
346 }
347
348 #[test]
349 fn test_validate_page_invalid_crc() {
350 let logical_page_size = 64usize;
351 let physical_page_size = logical_page_size + Checksum::SIZE;
352 let mut page = vec![0u8; physical_page_size];
353
354 let data = b"hello world";
356 page[..data.len()].copy_from_slice(data);
357
358 let wrong_crc = 0xBADBADBA;
360 let record = Checksum::new(data.len() as u16, wrong_crc);
361
362 let crc_start = physical_page_size - Checksum::SIZE;
363 page[crc_start..].copy_from_slice(&record.to_bytes());
364
365 let validated = Checksum::validate_page(&page);
367 assert!(validated.is_none());
368 }
369
370 #[test]
371 fn test_validate_page_corrupted_data() {
372 let logical_page_size = 64usize;
373 let physical_page_size = logical_page_size + Checksum::SIZE;
374 let mut page = vec![0u8; physical_page_size];
375
376 let data = b"hello world";
378 page[..data.len()].copy_from_slice(data);
379 let crc = Crc32::checksum(&page[..data.len()]);
380 let record = Checksum::new(data.len() as u16, crc);
381
382 let crc_start = physical_page_size - Checksum::SIZE;
383 page[crc_start..].copy_from_slice(&record.to_bytes());
384
385 page[0] = 0xFF;
387
388 let validated = Checksum::validate_page(&page);
390 assert!(validated.is_none());
391 }
392
393 #[test]
394 fn test_validate_page_uses_larger_len() {
395 let logical_page_size = 64usize;
396 let physical_page_size = logical_page_size + Checksum::SIZE;
397 let mut page = vec![0u8; physical_page_size];
398
399 let data = b"hello world, this is longer";
401 page[..data.len()].copy_from_slice(data);
402 let crc = Crc32::checksum(&page[..data.len()]);
403
404 let record = Checksum {
406 len1: 5,
407 crc1: 0xDEADBEEF, len2: data.len() as u16,
409 crc2: crc,
410 };
411
412 let crc_start = physical_page_size - Checksum::SIZE;
413 page[crc_start..].copy_from_slice(&record.to_bytes());
414
415 let validated = Checksum::validate_page(&page);
417 assert!(validated.is_some());
418 let (len, _) = validated.unwrap().get_crc();
419 assert_eq!(len as usize, data.len());
420 }
421
422 #[test]
423 fn test_validate_page_uses_fallback() {
424 let logical_page_size = 64usize;
425 let physical_page_size = logical_page_size + Checksum::SIZE;
426 let mut page = vec![0u8; physical_page_size];
427
428 let data = b"fallback data";
430 page[..data.len()].copy_from_slice(data);
431 let valid_crc = Crc32::checksum(&page[..data.len()]);
432 let valid_len = data.len() as u16;
433
434 let record = Checksum {
438 len1: valid_len + 10, crc1: 0xBAD1DEA, len2: valid_len, crc2: valid_crc, };
443
444 let crc_start = physical_page_size - Checksum::SIZE;
445 page[crc_start..].copy_from_slice(&record.to_bytes());
446
447 let validated = Checksum::validate_page(&page);
449
450 assert!(validated.is_some(), "Should have validated using fallback");
451 let validated = validated.unwrap();
452 let (len, crc) = validated.get_crc();
453 assert_eq!(len, valid_len);
454 assert_eq!(crc, valid_crc);
455
456 assert_eq!(validated.len1, 0);
458 assert_eq!(validated.crc1, 0);
459 }
460
461 #[test]
462 fn test_validate_page_no_fallback_available() {
463 let logical_page_size = 64usize;
464 let physical_page_size = logical_page_size + Checksum::SIZE;
465 let mut page = vec![0u8; physical_page_size];
466
467 let data = b"some data";
469 page[..data.len()].copy_from_slice(data);
470
471 let record = Checksum {
475 len1: data.len() as u16,
476 crc1: 0xBAD1DEA, len2: 0, crc2: 0,
479 };
480
481 let crc_start = physical_page_size - Checksum::SIZE;
482 page[crc_start..].copy_from_slice(&record.to_bytes());
483
484 let validated = Checksum::validate_page(&page);
486 assert!(
487 validated.is_none(),
488 "Should fail when primary is invalid and fallback has len=0"
489 );
490 }
491
492 #[cfg(feature = "arbitrary")]
493 mod conformance {
494 use super::*;
495 use commonware_codec::conformance::CodecConformance;
496
497 commonware_conformance::conformance_tests! {
498 CodecConformance<Checksum>,
499 }
500 }
501}