reifydb_codec/log/
reader.rs1use crc32fast::Hasher;
5
6use crate::log::LogVersion;
7
8pub const HINT_BYTES: usize = 16;
9
10pub const MAGIC: u32 = u32::from_le_bytes(*b"RRDR");
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Hint {
14 pub version: LogVersion,
15}
16
17impl Hint {
18 pub fn new(version: LogVersion) -> Self {
19 Self {
20 version,
21 }
22 }
23
24 pub fn encode(&self) -> [u8; HINT_BYTES] {
25 let mut out = [0u8; HINT_BYTES];
26 out[0..4].copy_from_slice(&MAGIC.to_le_bytes());
27 out[8..16].copy_from_slice(&self.version.as_u64().to_le_bytes());
28 let checksum = checksum(&out[8..]);
29 out[4..8].copy_from_slice(&checksum.to_le_bytes());
30 out
31 }
32
33 pub fn decode(buf: &[u8; HINT_BYTES]) -> Option<Self> {
34 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != MAGIC {
35 return None;
36 }
37 if u32::from_le_bytes(buf[4..8].try_into().unwrap()) != checksum(&buf[8..]) {
38 return None;
39 }
40 Some(Self {
41 version: LogVersion::new(u64::from_le_bytes(buf[8..16].try_into().unwrap())),
42 })
43 }
44}
45
46fn checksum(bytes: &[u8]) -> u32 {
47 let mut hasher = Hasher::new();
48 hasher.update(bytes);
49 hasher.finalize()
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn a_hint_lays_its_fields_out_at_the_documented_offsets() {
58 let raw = Hint::new(LogVersion::new(0x0102030405060708)).encode();
61
62 assert_eq!(raw.len(), HINT_BYTES);
63 assert_eq!(&raw[0..4], &MAGIC.to_le_bytes());
64 assert_eq!(&raw[4..8], &0xa5cced25u32.to_le_bytes());
65 assert_eq!(&raw[8..16], &0x0102030405060708u64.to_le_bytes());
66 }
67
68 #[test]
69 fn a_hint_round_trips_through_its_bytes() {
70 let hint = Hint::new(LogVersion::new(4096));
71
72 assert_eq!(Hint::decode(&hint.encode()), Some(hint));
73 }
74
75 #[test]
76 fn a_hint_of_all_zeros_does_not_decode() {
77 assert_eq!(Hint::decode(&[0u8; HINT_BYTES]), None);
81 }
82
83 #[test]
84 fn a_flipped_bit_anywhere_in_the_version_is_caught_by_the_checksum() {
85 for at in 8..HINT_BYTES {
89 let mut raw = Hint::new(LogVersion::new(500_000)).encode();
90 raw[at] ^= 0x01;
91
92 assert_eq!(Hint::decode(&raw), None, "a flip at byte {at} decoded anyway");
93 }
94 }
95
96 #[test]
97 fn a_flipped_bit_in_the_reserved_word_is_caught_too() {
98 for at in 4..8 {
101 let mut raw = Hint::new(LogVersion::new(500_000)).encode();
102 raw[at] ^= 0x01;
103
104 assert_eq!(Hint::decode(&raw), None, "a flip at byte {at} decoded anyway");
105 }
106 }
107
108 #[test]
109 fn a_foreign_file_is_refused_by_its_magic() {
110 let mut raw = Hint::new(LogVersion::new(7)).encode();
111 raw[0..4].copy_from_slice(&u32::from_le_bytes(*b"RIDX").to_le_bytes());
112
113 assert_eq!(Hint::decode(&raw), None);
114 }
115}