reifydb_codec/log/
index.rs1use crc32fast::Hasher;
5use reifydb_value::{byte_size::ByteSize, reifydb_assertions, value::datetime::DateTime};
6
7use crate::log::{LogIndex, LogVersion, Position};
8
9pub const HEADER_BYTES: usize = 40;
10
11pub const ENTRY_BYTES: usize = 12;
12
13pub const MAGIC: u32 = u32::from_le_bytes(*b"RIDX");
14
15pub const DEFAULT_INTERVAL: ByteSize = ByteSize::from_kib(4);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct TimestampRange {
19 pub min: DateTime,
20 pub max: DateTime,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Header {
25 pub magic: u32,
26 pub base_version: LogVersion,
27 pub base_index: LogIndex,
28 pub timestamps: Option<TimestampRange>,
29}
30
31impl Header {
32 pub fn new(base_version: LogVersion, base_index: LogIndex) -> Self {
33 Self {
34 magic: MAGIC,
35 base_version,
36 base_index,
37 timestamps: None,
38 }
39 }
40
41 pub fn decode(buf: &[u8; HEADER_BYTES]) -> Option<Self> {
42 let magic = u32::from_le_bytes(buf[0..4].try_into().unwrap());
43 if magic != MAGIC {
44 return None;
45 }
46 let found = u32::from_le_bytes(buf[4..8].try_into().unwrap());
47 if found != checksum_bytes(&buf[8..]) {
48 return None;
49 }
50 let min = DateTime::from_order(u64::from_le_bytes(buf[24..32].try_into().unwrap()));
51 let max = DateTime::from_order(u64::from_le_bytes(buf[32..40].try_into().unwrap()));
52 Some(Self {
53 magic,
54 base_version: LogVersion::new(u64::from_le_bytes(buf[8..16].try_into().unwrap())),
55 base_index: LogIndex::new(u64::from_le_bytes(buf[16..24].try_into().unwrap())),
56 timestamps: (min <= max).then_some(TimestampRange {
57 min,
58 max,
59 }),
60 })
61 }
62
63 pub fn encode(&self) -> [u8; HEADER_BYTES] {
64 let (min, max) = match self.timestamps {
65 Some(range) => (range.min, range.max),
66 None => (DateTime::MAX, DateTime::MIN),
67 };
68 let mut out = [0u8; HEADER_BYTES];
69 out[0..4].copy_from_slice(&self.magic.to_le_bytes());
70 out[8..16].copy_from_slice(&self.base_version.as_u64().to_le_bytes());
71 out[16..24].copy_from_slice(&self.base_index.as_u64().to_le_bytes());
72 out[24..32].copy_from_slice(&min.to_order().to_le_bytes());
73 out[32..40].copy_from_slice(&max.to_order().to_le_bytes());
74 let checksum = checksum_bytes(&out[8..]);
75 out[4..8].copy_from_slice(&checksum.to_le_bytes());
76 out
77 }
78}
79
80fn checksum_bytes(bytes: &[u8]) -> u32 {
81 let mut hasher = Hasher::new();
82 hasher.update(bytes);
83 hasher.finalize()
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct Entry {
88 pub version: LogVersion,
89 pub index: LogIndex,
90 pub position: Position,
91}
92
93pub fn encode_entry(header: &Header, entry: Entry) -> [u8; ENTRY_BYTES] {
94 reifydb_assertions! {
95 assert!(
96 entry.version >= header.base_version,
97 "version {} is below the base version {} the index was created with, and the delta wraps \
98 silently into an entry no lookup can follow",
99 entry.version,
100 header.base_version
101 );
102 assert!(
103 entry.version.as_u64() - header.base_version.as_u64() <= u32::MAX as u64,
104 "a delta of {} overflows the four byte delta field (limit={})",
105 entry.version.as_u64() - header.base_version.as_u64(),
106 u32::MAX
107 );
108 assert!(
109 entry.index >= header.base_index,
110 "index {} is below the base index {} the index was created with, and the delta wraps \
111 silently into an entry no lookup can follow",
112 entry.index,
113 header.base_index
114 );
115 assert!(
116 entry.index.as_u64() - header.base_index.as_u64() <= u32::MAX as u64,
117 "an index delta of {} overflows the four byte delta field (limit={})",
118 entry.index.as_u64() - header.base_index.as_u64(),
119 u32::MAX
120 );
121 assert!(
122 entry.position.as_u64() <= u32::MAX as u64,
123 "a position of {} overflows the four byte position field, so the segment must stay under \
124 {} bytes",
125 entry.position,
126 u32::MAX
127 );
128 }
129 let mut out = [0u8; ENTRY_BYTES];
130 out[0..4].copy_from_slice(&((entry.version.as_u64() - header.base_version.as_u64()) as u32).to_le_bytes());
131 out[4..8].copy_from_slice(&((entry.index.as_u64() - header.base_index.as_u64()) as u32).to_le_bytes());
132 out[8..12].copy_from_slice(&(entry.position.as_u64() as u32).to_le_bytes());
133 out
134}
135
136pub fn decode_entry(header: &Header, buf: &[u8; ENTRY_BYTES]) -> Option<Entry> {
137 Some(Entry {
138 version: LogVersion::new(
139 header.base_version
140 .as_u64()
141 .checked_add(u32::from_le_bytes(buf[0..4].try_into().unwrap()) as u64)?,
142 ),
143 index: LogIndex::new(
144 header.base_index
145 .as_u64()
146 .checked_add(u32::from_le_bytes(buf[4..8].try_into().unwrap()) as u64)?,
147 ),
148 position: Position::new(u32::from_le_bytes(buf[8..12].try_into().unwrap()) as u64),
149 })
150}