reifydb_codec/log/
meta.rs1use crc32fast::Hasher;
5use reifydb_value::{byte_size::ByteSize, value::duration::Duration};
6
7pub const META_BYTES: usize = 48;
8
9pub const MAGIC: u32 = u32::from_le_bytes(*b"RMET");
10
11pub const FORMAT_VERSION: u32 = 2;
12
13pub const DEFAULT_PARTITIONS: u32 = 4;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Meta {
17 pub version: u32,
18 pub partitions: u32,
19 pub segment_bytes: ByteSize,
20 pub index_interval: ByteSize,
21 pub segment_age: Duration,
22}
23
24impl Meta {
25 pub fn encode(&self) -> [u8; META_BYTES] {
26 let mut out = [0u8; META_BYTES];
27 out[0..4].copy_from_slice(&MAGIC.to_le_bytes());
28 out[8..12].copy_from_slice(&self.version.to_le_bytes());
29 out[12..16].copy_from_slice(&self.partitions.to_le_bytes());
30 out[16..24].copy_from_slice(&self.segment_bytes.as_bytes().to_le_bytes());
31 out[24..32].copy_from_slice(&self.index_interval.as_bytes().to_le_bytes());
32 out[32..36].copy_from_slice(&self.segment_age.get_months().to_le_bytes());
33 out[36..40].copy_from_slice(&self.segment_age.get_days().to_le_bytes());
34 out[40..48].copy_from_slice(&self.segment_age.get_nanos().to_le_bytes());
35 let checksum = checksum(&out[8..]);
36 out[4..8].copy_from_slice(&checksum.to_le_bytes());
37 out
38 }
39
40 pub fn decode(buf: &[u8; META_BYTES]) -> Option<Self> {
41 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != MAGIC {
42 return None;
43 }
44 if u32::from_le_bytes(buf[4..8].try_into().unwrap()) != checksum(&buf[8..]) {
45 return None;
46 }
47 let segment_age = Duration::new(
48 i32::from_le_bytes(buf[32..36].try_into().unwrap()),
49 i32::from_le_bytes(buf[36..40].try_into().unwrap()),
50 i64::from_le_bytes(buf[40..48].try_into().unwrap()),
51 )
52 .ok()?;
53 Some(Self {
54 version: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
55 partitions: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
56 segment_bytes: ByteSize::from_bytes(u64::from_le_bytes(buf[16..24].try_into().unwrap())),
57 index_interval: ByteSize::from_bytes(u64::from_le_bytes(buf[24..32].try_into().unwrap())),
58 segment_age,
59 })
60 }
61}
62
63fn checksum(bytes: &[u8]) -> u32 {
64 let mut hasher = Hasher::new();
65 hasher.update(bytes);
66 hasher.finalize()
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 fn meta() -> Meta {
74 Meta {
75 version: FORMAT_VERSION,
76 partitions: DEFAULT_PARTITIONS,
77 segment_bytes: ByteSize::from_mib(256),
78 index_interval: ByteSize::from_kib(4),
79 segment_age: Duration::from_seconds_const(60),
80 }
81 }
82
83 #[test]
84 fn meta_lays_its_fields_out_at_the_documented_offsets() {
85 let raw = Meta {
88 version: 0x01020304,
89 partitions: 0x11121314,
90 segment_bytes: ByteSize::from_bytes(0x2122232425262728),
91 index_interval: ByteSize::from_bytes(0x3132333435363738),
92 segment_age: Duration::new(0x41424344, 0x51525354, 0x1122334455).unwrap(),
93 }
94 .encode();
95
96 assert_eq!(raw.len(), META_BYTES);
97 assert_eq!(&raw[0..4], &MAGIC.to_le_bytes());
98 assert_eq!(&raw[8..12], &0x01020304u32.to_le_bytes());
99 assert_eq!(&raw[12..16], &0x11121314u32.to_le_bytes());
100 assert_eq!(&raw[16..24], &0x2122232425262728u64.to_le_bytes());
101 assert_eq!(&raw[24..32], &0x3132333435363738u64.to_le_bytes());
102 assert_eq!(&raw[32..36], &0x41424344i32.to_le_bytes());
103 assert_eq!(&raw[36..40], &0x51525354i32.to_le_bytes());
104 assert_eq!(&raw[40..48], &0x1122334455i64.to_le_bytes());
105 }
106
107 #[test]
108 fn a_meta_round_trips_through_its_bytes() {
109 assert_eq!(Meta::decode(&meta().encode()), Some(meta()));
110 }
111
112 #[test]
113 fn a_duration_with_a_months_component_survives_the_round_trip() {
114 let with_months = Meta {
117 segment_age: Duration::new(3, 2, 1).unwrap(),
118 ..meta()
119 };
120
121 let back = Meta::decode(&with_months.encode()).unwrap();
122
123 assert_eq!(back.segment_age.get_months(), 3);
124 assert_eq!(back.segment_age.get_days(), 2);
125 assert_eq!(back.segment_age.get_nanos(), 1);
126 }
127
128 #[test]
129 fn a_foreign_file_is_refused_by_its_magic() {
130 let mut raw = meta().encode();
131 raw[0..4].copy_from_slice(&u32::from_le_bytes(*b"RIDX").to_le_bytes());
132
133 assert_eq!(Meta::decode(&raw), None);
134 }
135
136 #[test]
137 fn a_flipped_bit_in_the_partition_count_is_caught_by_the_checksum() {
138 let mut raw = meta().encode();
141 raw[12] ^= 0x01;
142
143 assert_eq!(Meta::decode(&raw), None);
144 }
145
146 #[test]
147 fn a_flipped_bit_in_the_segment_size_is_caught_by_the_checksum() {
148 let mut raw = meta().encode();
149 raw[16] ^= 0x01;
150
151 assert_eq!(Meta::decode(&raw), None);
152 }
153
154 #[test]
155 fn an_unconstructible_duration_is_refused_rather_than_normalised() {
156 let mut raw = meta().encode();
159 raw[36..40].copy_from_slice(&1i32.to_le_bytes());
160 raw[40..48].copy_from_slice(&(-1i64).to_le_bytes());
161 let fixed = checksum(&raw[8..]);
162 raw[4..8].copy_from_slice(&fixed.to_le_bytes());
163
164 assert_eq!(Meta::decode(&raw), None);
165 }
166}