1use crc32fast::Hasher;
5use reifydb_value::{reifydb_assertions, value::datetime::DateTime};
6
7use crate::log::{LogIndex, LogVersion, RecordKind, Term};
8
9pub const HEADER_BYTES: usize = 48;
10
11pub const MIN_LENGTH: u32 = 44;
12
13pub const RESERVED: u32 = 0;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Record {
17 pub version: LogVersion,
18 pub index: LogIndex,
19 pub term: Term,
20 pub timestamp: DateTime,
21 pub kind: RecordKind,
22 pub payload: Vec<u8>,
23}
24
25impl Record {
26 pub fn new(
27 version: LogVersion,
28 index: LogIndex,
29 term: Term,
30 timestamp: DateTime,
31 kind: RecordKind,
32 payload: Vec<u8>,
33 ) -> Self {
34 Self {
35 version,
36 index,
37 term,
38 timestamp,
39 kind,
40 payload,
41 }
42 }
43
44 pub fn encoded_len(&self) -> usize {
45 HEADER_BYTES + self.payload.len()
46 }
47
48 pub fn encode(&self) -> Vec<u8> {
49 let length = MIN_LENGTH as usize + self.payload.len();
50 reifydb_assertions! {
51 assert!(
52 length <= u32::MAX as usize,
53 "a payload of {} bytes overflows the four byte length field, which wraps silently and \
54 frames the record at a length no scan can follow (payload limit={})",
55 self.payload.len(),
56 u32::MAX as usize - MIN_LENGTH as usize
57 );
58 }
59 let mut out = Vec::with_capacity(HEADER_BYTES + self.payload.len());
60 out.extend_from_slice(&(length as u32).to_le_bytes());
61 out.extend_from_slice(&0u32.to_le_bytes());
62 out.extend_from_slice(&self.version.as_u64().to_le_bytes());
63 out.extend_from_slice(&self.index.as_u64().to_le_bytes());
64 out.extend_from_slice(&self.term.as_u64().to_le_bytes());
65 out.extend_from_slice(&self.timestamp.to_bits().to_le_bytes());
66 out.extend_from_slice(&self.kind.as_u32().to_le_bytes());
67 out.extend_from_slice(&RESERVED.to_le_bytes());
68 out.extend_from_slice(&self.payload);
69 let checksum = checksum(&out[8..]);
70 out[4..8].copy_from_slice(&checksum.to_le_bytes());
71 out
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct Header {
77 pub length: u32,
78 pub checksum: u32,
79 pub version: LogVersion,
80 pub index: LogIndex,
81 pub term: Term,
82 pub timestamp: DateTime,
83 pub kind: RecordKind,
84 pub reserved: u32,
85}
86
87impl Header {
88 pub fn decode(buf: &[u8; HEADER_BYTES]) -> Self {
89 Self {
90 length: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
91 checksum: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
92 version: LogVersion::new(u64::from_le_bytes(buf[8..16].try_into().unwrap())),
93 index: LogIndex::new(u64::from_le_bytes(buf[16..24].try_into().unwrap())),
94 term: Term::new(u64::from_le_bytes(buf[24..32].try_into().unwrap())),
95 timestamp: DateTime::from_bits(u64::from_le_bytes(buf[32..40].try_into().unwrap())),
96 kind: RecordKind::new(u32::from_le_bytes(buf[40..44].try_into().unwrap())),
97 reserved: u32::from_le_bytes(buf[44..48].try_into().unwrap()),
98 }
99 }
100
101 pub fn is_end(&self) -> bool {
102 self.length == 0
103 }
104
105 pub fn payload_len(&self) -> Option<usize> {
106 if self.length < MIN_LENGTH {
107 return None;
108 }
109 Some((self.length - MIN_LENGTH) as usize)
110 }
111
112 pub fn verify(&self, payload: &[u8]) -> bool {
113 let mut hasher = Hasher::new();
114 hasher.update(&self.version.as_u64().to_le_bytes());
115 hasher.update(&self.index.as_u64().to_le_bytes());
116 hasher.update(&self.term.as_u64().to_le_bytes());
117 hasher.update(&self.timestamp.to_bits().to_le_bytes());
118 hasher.update(&self.kind.as_u32().to_le_bytes());
119 hasher.update(&self.reserved.to_le_bytes());
120 hasher.update(payload);
121 hasher.finalize() == self.checksum
122 }
123
124 pub fn into_record(self, payload: Vec<u8>) -> Record {
125 Record {
126 version: self.version,
127 index: self.index,
128 term: self.term,
129 timestamp: self.timestamp,
130 kind: self.kind,
131 payload,
132 }
133 }
134}
135
136fn checksum(bytes: &[u8]) -> u32 {
137 let mut hasher = Hasher::new();
138 hasher.update(bytes);
139 hasher.finalize()
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn header_of(bytes: &[u8]) -> Header {
147 Header::decode(bytes[..HEADER_BYTES].try_into().unwrap())
148 }
149
150 fn record(version: u64, index: u64, term: u64, timestamp: u64, kind: u32, payload: Vec<u8>) -> Record {
151 Record::new(
152 LogVersion::new(version),
153 LogIndex::new(index),
154 Term::new(term),
155 DateTime::from_bits(timestamp),
156 RecordKind::new(kind),
157 payload,
158 )
159 }
160
161 #[test]
162 fn encode_lays_the_fields_out_at_the_documented_offsets() {
163 let bytes = record(
166 0x0102030405060708,
167 0x2122232425262728,
168 0x3132333435363738,
169 0x1112131415161718,
170 0x41424344,
171 vec![0xaa, 0xbb],
172 )
173 .encode();
174
175 assert_eq!(bytes.len(), HEADER_BYTES + 2);
176 assert_eq!(&bytes[0..4], &46u32.to_le_bytes());
177 assert_eq!(&bytes[8..16], &0x0102030405060708u64.to_le_bytes());
178 assert_eq!(&bytes[16..24], &0x2122232425262728u64.to_le_bytes());
179 assert_eq!(&bytes[24..32], &0x3132333435363738u64.to_le_bytes());
180 assert_eq!(&bytes[32..40], &0x1112131415161718u64.to_le_bytes());
181 assert_eq!(&bytes[40..44], &0x41424344u32.to_le_bytes());
182 assert_eq!(&bytes[44..48], &0u32.to_le_bytes());
183 assert_eq!(&bytes[48..50], &[0xaa, 0xbb]);
184 }
185
186 #[test]
187 fn length_counts_the_bytes_after_itself() {
188 let record = record(1, 2, 3, 4, 0, vec![0u8; 100]);
191 let bytes = record.encode();
192 let header = header_of(&bytes);
193
194 assert_eq!(header.length as usize, bytes.len() - 4);
195 assert_eq!(header.payload_len(), Some(100));
196 }
197
198 #[test]
199 fn a_roundtrip_returns_the_payload_byte_identical() {
200 let payload: Vec<u8> = (0..=255u8).collect();
201 let record = record(7, 9, 11, 13, 1, payload.clone());
202 let bytes = record.encode();
203 let header = header_of(&bytes);
204
205 assert!(header.verify(&bytes[HEADER_BYTES..]));
206 assert_eq!(header.into_record(bytes[HEADER_BYTES..].to_vec()), record);
207 }
208
209 #[test]
210 fn a_roundtrip_returns_the_index_term_and_kind() {
211 let header = header_of(&record(500, 7, 3, 1234, 1, vec![0x11, 0x22]).encode());
214
215 assert_eq!(header.version, LogVersion::new(500));
216 assert_eq!(header.index, LogIndex::new(7));
217 assert_eq!(header.term, Term::new(3));
218 assert_eq!(header.timestamp, DateTime::from_bits(1234));
219 assert_eq!(header.kind, RecordKind::new(1));
220 assert_eq!(header.reserved, RESERVED);
221 }
222
223 #[test]
224 fn an_empty_payload_is_a_valid_record_and_not_a_terminator() {
225 let bytes = record(3, 4, 5, 6, 0, Vec::new()).encode();
228 let header = header_of(&bytes);
229
230 assert_eq!(header.length, MIN_LENGTH);
231 assert!(!header.is_end());
232 assert_eq!(header.payload_len(), Some(0));
233 assert!(header.verify(&[]));
234 }
235
236 #[test]
237 fn a_zeroed_header_reads_as_the_end_of_the_written_region() {
238 let header = Header::decode(&[0u8; HEADER_BYTES]);
241
242 assert!(header.is_end());
243 }
244
245 #[test]
246 fn a_flipped_payload_bit_fails_verification() {
247 let mut bytes = record(1, 2, 3, 4, 0, vec![0x55; 64]).encode();
248 bytes[HEADER_BYTES + 30] ^= 0x01;
249 let header = header_of(&bytes);
250
251 assert!(!header.verify(&bytes[HEADER_BYTES..]));
252 }
253
254 #[test]
255 fn a_flipped_version_bit_fails_verification() {
256 let mut bytes = record(1, 2, 3, 4, 0, vec![0x55; 8]).encode();
259 bytes[8] ^= 0x01;
260 let header = header_of(&bytes);
261
262 assert!(!header.verify(&bytes[HEADER_BYTES..]));
263 }
264
265 #[test]
266 fn a_flipped_timestamp_bit_fails_verification() {
267 let mut bytes = record(1, 2, 3, 4, 0, vec![0x55; 8]).encode();
268 bytes[32] ^= 0x01;
269 let header = header_of(&bytes);
270
271 assert!(!header.verify(&bytes[HEADER_BYTES..]));
272 }
273
274 #[test]
275 fn a_flipped_bit_anywhere_past_the_length_fails_verification() {
276 for byte in 8..HEADER_BYTES {
279 let mut bytes = record(1, 2, 3, 4, 1, vec![0x55; 8]).encode();
280 bytes[byte] ^= 0x01;
281 let header = header_of(&bytes);
282
283 assert!(!header.verify(&bytes[HEADER_BYTES..]), "byte {byte} is outside the checksum");
284 }
285 }
286
287 #[test]
288 fn a_length_below_the_minimum_has_no_payload_length() {
289 for length in 1..MIN_LENGTH {
292 let mut buf = [0u8; HEADER_BYTES];
293 buf[0..4].copy_from_slice(&length.to_le_bytes());
294
295 assert_eq!(Header::decode(&buf).payload_len(), None, "length {length}");
296 }
297 }
298
299 #[test]
300 fn two_records_differing_only_in_payload_have_different_checksums() {
301 let a = header_of(&record(1, 2, 3, 4, 0, vec![0x00]).encode());
302 let b = header_of(&record(1, 2, 3, 4, 0, vec![0x01]).encode());
303
304 assert_ne!(a.checksum, b.checksum);
305 }
306
307 #[test]
308 fn two_records_differing_only_in_a_raft_field_have_different_checksums() {
309 let base = header_of(&record(1, 2, 3, 4, 0, vec![0x00]).encode());
312
313 assert_ne!(base.checksum, header_of(&record(1, 9, 3, 4, 0, vec![0x00]).encode()).checksum);
314 assert_ne!(base.checksum, header_of(&record(1, 2, 9, 4, 0, vec![0x00]).encode()).checksum);
315 assert_ne!(base.checksum, header_of(&record(1, 2, 3, 4, 9, vec![0x00]).encode()).checksum);
316 }
317
318 #[test]
319 fn encoded_len_matches_what_encode_produces() {
320 for size in [0usize, 1, 511, 512, 513, 4096] {
323 let record = record(1, 2, 3, 4, 0, vec![0u8; size]);
324
325 assert_eq!(record.encoded_len(), record.encode().len());
326 }
327 }
328}