1use crate::common::Position;
2use crate::common::{PageID, Tsn};
3use bitflags::bitflags;
4use byteorder::{ByteOrder, LittleEndian};
5use umadb_dcb::{DCBError, DCBResult};
6
7bitflags! {
8 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9 pub struct HeaderFlags: u16 {
10 const HAS_TRACKING_ROOT_ID = 0b0000_0001;
11 }
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct HeaderNode {
17 pub tsn: Tsn,
18 pub free_lists_tree_root_id: PageID,
19 pub events_tree_root_id: PageID,
20 pub tags_tree_root_id: PageID,
21 pub next_page_id: PageID,
22 pub next_position: Position,
23 pub schema_version: u32,
25 pub tracking_root_page_id: PageID,
26}
27
28impl Default for HeaderNode {
29 fn default() -> Self {
30 Self {
31 tsn: Tsn(0),
32 free_lists_tree_root_id: PageID(0),
33 events_tree_root_id: PageID(0),
34 tags_tree_root_id: PageID(0),
35 next_page_id: PageID(0),
36 next_position: Position(0),
37 schema_version: crate::db::DB_SCHEMA_VERSION,
38 tracking_root_page_id: PageID(0),
39 }
40 }
41}
42
43impl HeaderNode {
44 pub fn calc_serialized_size(&self) -> usize {
47 let mut required_buf = 52;
48 let mut flags = HeaderFlags::empty();
49 if self.tracking_root_page_id != PageID(0) {
50 flags |= HeaderFlags::HAS_TRACKING_ROOT_ID;
51 required_buf += 8;
52 }
53 if !flags.is_empty() {
54 required_buf += 2;
55 }
56 required_buf
57 }
58
59 pub fn serialize_into(&self, buf: &mut [u8]) -> usize {
60 let mut required_buf = 52;
61 assert!(
62 buf.len() >= required_buf,
63 "HeaderNode::serialize_into buf must be at least 52 bytes"
64 );
65 let mut flags = HeaderFlags::empty();
66
67 buf[0..8].copy_from_slice(&self.tsn.0.to_le_bytes());
69 buf[8..16].copy_from_slice(&self.next_page_id.0.to_le_bytes());
70 buf[16..24].copy_from_slice(&self.free_lists_tree_root_id.0.to_le_bytes());
71 buf[24..32].copy_from_slice(&self.events_tree_root_id.0.to_le_bytes());
72 buf[32..40].copy_from_slice(&self.tags_tree_root_id.0.to_le_bytes());
73 buf[40..48].copy_from_slice(&self.next_position.0.to_le_bytes());
74 buf[48..52].copy_from_slice(&self.schema_version.to_le_bytes());
76 if self.tracking_root_page_id != PageID(0) {
77 flags |= HeaderFlags::HAS_TRACKING_ROOT_ID;
78 required_buf += 8;
79 }
80 if flags.is_empty() {
81 return required_buf;
82 }
83 required_buf += 2;
84 assert!(
85 buf.len() >= required_buf,
86 "HeaderNode::serialize_into buf must be at least {required_buf} bytes",
87 );
88 buf[52..54].copy_from_slice(&flags.bits().to_le_bytes());
90 if flags.contains(HeaderFlags::HAS_TRACKING_ROOT_ID) {
91 buf[54..62].copy_from_slice(&self.tracking_root_page_id.0.to_le_bytes())
92 }
93 required_buf
94 }
95
96 pub fn from_slice(slice: &[u8]) -> DCBResult<Self> {
112 if slice.len() < 48 {
113 return Err(DCBError::DeserializationError(format!(
114 "Expected at least 48 bytes, got {}",
115 slice.len()
116 )));
117 }
118
119 let tsn = LittleEndian::read_u64(&slice[0..8]);
120 let next_page_id = LittleEndian::read_u64(&slice[8..16]);
121 let freetree_root_id = LittleEndian::read_u64(&slice[16..24]);
122 let position_root_id = LittleEndian::read_u64(&slice[24..32]);
123 let tags_root_id = LittleEndian::read_u64(&slice[32..40]);
124 let next_position = LittleEndian::read_u64(&slice[40..48]);
125 let schema_version: u32 = if slice.len() >= 52 {
126 LittleEndian::read_u32(&slice[48..52])
127 } else {
128 0u32
129 };
130 let flags = if slice.len() >= 54 {
131 HeaderFlags::from_bits(LittleEndian::read_u16(&slice[52..54])).ok_or(
132 DCBError::DeserializationError("unknown flag bits set".to_string()),
133 )?
134 } else {
135 HeaderFlags::empty()
136 };
137 let mut required_len = 54;
138 let mut tracking_tree_root_id = 0u64;
139
140 if flags.contains(HeaderFlags::HAS_TRACKING_ROOT_ID) {
141 required_len += 8;
142 if slice.len() < required_len {
143 return Err(DCBError::DeserializationError(format!(
144 "Expected at least {required_len} bytes, got {}",
145 slice.len()
146 )));
147 }
148 tracking_tree_root_id = LittleEndian::read_u64(&slice[required_len - 8..required_len]);
149 }
150
151 Ok(HeaderNode {
152 tsn: Tsn(tsn),
153 next_page_id: PageID(next_page_id),
154 free_lists_tree_root_id: PageID(freetree_root_id),
155 events_tree_root_id: PageID(position_root_id),
156 tags_tree_root_id: PageID(tags_root_id),
157 next_position: Position(next_position),
158 schema_version,
159 tracking_root_page_id: PageID(tracking_tree_root_id),
160 })
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 #[test]
168 fn test_header_serialize_without_tracking_root_page_id() {
169 let header_node = HeaderNode {
171 tsn: Tsn(42),
172 next_page_id: PageID(123),
173 free_lists_tree_root_id: PageID(456),
174 events_tree_root_id: PageID(789),
175 tags_tree_root_id: PageID(321),
176 next_position: Position(9876543210),
177 schema_version: crate::db::DB_SCHEMA_VERSION,
178 tracking_root_page_id: PageID(0),
179 };
180
181 assert_eq!(52, header_node.calc_serialized_size());
182
183 let mut serialized = [0u8; 52];
185 let serialized_size = header_node.serialize_into(&mut serialized);
186
187 assert_eq!(52, serialized_size);
189
190 assert_eq!(&42u64.to_le_bytes(), &serialized[0..8]);
193
194 assert_eq!(&123u64.to_le_bytes(), &serialized[8..16]);
196
197 assert_eq!(&456u64.to_le_bytes(), &serialized[16..24]);
199
200 assert_eq!(&789u64.to_le_bytes(), &serialized[24..32]);
202
203 assert_eq!(&321u64.to_le_bytes(), &serialized[32..40]);
205
206 assert_eq!(&9876543210u64.to_le_bytes(), &serialized[40..48]);
208
209 assert_eq!(
211 &crate::db::DB_SCHEMA_VERSION.to_le_bytes(),
212 &serialized[48..52]
213 );
214
215 let deserialized =
217 HeaderNode::from_slice(&serialized).expect("Failed to deserialize HeaderNode");
218
219 assert_eq!(header_node.tsn, deserialized.tsn);
221 assert_eq!(header_node.next_page_id, deserialized.next_page_id);
222 assert_eq!(
223 header_node.free_lists_tree_root_id,
224 deserialized.free_lists_tree_root_id
225 );
226 assert_eq!(
227 header_node.events_tree_root_id,
228 deserialized.events_tree_root_id
229 );
230 assert_eq!(header_node.next_position, deserialized.next_position);
231 assert_eq!(header_node.schema_version, deserialized.schema_version);
232 assert_eq!(
233 header_node.tracking_root_page_id,
234 deserialized.tracking_root_page_id
235 );
236 }
237
238 #[test]
239 fn test_header_serialize_with_tracking_root_page_id() {
240 let header_node = HeaderNode {
242 tsn: Tsn(42),
243 next_page_id: PageID(123),
244 free_lists_tree_root_id: PageID(456),
245 events_tree_root_id: PageID(789),
246 tags_tree_root_id: PageID(321),
247 next_position: Position(9876543210),
248 schema_version: crate::db::DB_SCHEMA_VERSION,
249 tracking_root_page_id: PageID(953),
250 };
251
252 assert_eq!(62, header_node.calc_serialized_size());
253
254 let mut serialized = [0u8; 62];
256 let serialized_size = header_node.serialize_into(&mut serialized);
257
258 assert_eq!(62, serialized_size);
260
261 assert_eq!(&42u64.to_le_bytes(), &serialized[0..8]);
264
265 assert_eq!(&123u64.to_le_bytes(), &serialized[8..16]);
267
268 assert_eq!(&456u64.to_le_bytes(), &serialized[16..24]);
270
271 assert_eq!(&789u64.to_le_bytes(), &serialized[24..32]);
273
274 assert_eq!(&321u64.to_le_bytes(), &serialized[32..40]);
276
277 assert_eq!(&9876543210u64.to_le_bytes(), &serialized[40..48]);
279
280 assert_eq!(
282 &crate::db::DB_SCHEMA_VERSION.to_le_bytes(),
283 &serialized[48..52]
284 );
285
286 assert_eq!(&1u16.to_le_bytes(), &serialized[52..54]);
288
289 assert_eq!(&953u64.to_le_bytes(), &serialized[54..62]);
291
292 let deserialized =
294 HeaderNode::from_slice(&serialized).expect("Failed to deserialize HeaderNode");
295
296 assert_eq!(header_node.tsn, deserialized.tsn);
298 assert_eq!(header_node.next_page_id, deserialized.next_page_id);
299 assert_eq!(
300 header_node.free_lists_tree_root_id,
301 deserialized.free_lists_tree_root_id
302 );
303 assert_eq!(
304 header_node.events_tree_root_id,
305 deserialized.events_tree_root_id
306 );
307 assert_eq!(header_node.next_position, deserialized.next_position);
308 assert_eq!(header_node.schema_version, deserialized.schema_version);
309 assert_eq!(
310 header_node.tracking_root_page_id,
311 deserialized.tracking_root_page_id
312 );
313 }
314}
315
316#[cfg(test)]
317mod header_node_legacy_tests {
318 use super::*;
319
320 #[test]
321 fn test_legacy_48_byte_deserialize_sets_schema_version_0() {
322 let header_node = HeaderNode {
324 tsn: Tsn(1),
325 next_page_id: PageID(2),
326 free_lists_tree_root_id: PageID(3),
327 events_tree_root_id: PageID(4),
328 tags_tree_root_id: PageID(5),
329 next_position: Position(6),
330 schema_version: crate::db::DB_SCHEMA_VERSION,
331 tracking_root_page_id: PageID(0),
332 };
333 let mut bytes52 = [0u8; 52];
334 header_node.serialize_into(&mut bytes52);
335
336 let mut bytes48 = [0u8; 48];
338 bytes48.copy_from_slice(&bytes52[..48]);
339
340 let deserialized =
342 HeaderNode::from_slice(&bytes48).expect("legacy 48-byte header should deserialize");
343 assert_eq!(deserialized.tsn, Tsn(1));
344 assert_eq!(deserialized.next_page_id, PageID(2));
345 assert_eq!(deserialized.free_lists_tree_root_id, PageID(3));
346 assert_eq!(deserialized.events_tree_root_id, PageID(4));
347 assert_eq!(deserialized.tags_tree_root_id, PageID(5));
348 assert_eq!(deserialized.next_position, Position(6));
349 assert_eq!(deserialized.schema_version, 0);
350 }
351}