1use crate::common::Position;
2use crate::common::{PageID, Tsn};
3use crate::slice_reader::SliceReader;
4use bitflags::bitflags;
5use std::io::{Cursor, Write};
6use umadb_dcb::{DcbError, DcbResult};
7
8bitflags! {
9 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10 pub struct HeaderFlags: u16 {
11 const HAS_TRACKING_ROOT_ID = 0b0000_0001;
12 }
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct HeaderNode {
18 pub tsn: Tsn,
19 pub free_lists_tree_root_id: PageID,
20 pub events_tree_root_id: PageID,
21 pub tags_tree_root_id: PageID,
22 pub next_page_id: PageID,
23 pub next_position: Position,
24 pub schema_version: u32,
26 pub tracking_root_page_id: PageID,
27}
28
29impl Default for HeaderNode {
30 fn default() -> Self {
31 Self {
32 tsn: Tsn(0),
33 free_lists_tree_root_id: PageID(0),
34 events_tree_root_id: PageID(0),
35 tags_tree_root_id: PageID(0),
36 next_page_id: PageID(0),
37 next_position: Position(0),
38 schema_version: crate::db::DB_SCHEMA_VERSION,
39 tracking_root_page_id: PageID(0),
40 }
41 }
42}
43
44impl HeaderNode {
45 pub fn calc_serialized_size(&self) -> usize {
48 let mut required_buf = 52;
49 let mut flags = HeaderFlags::empty();
50 if self.tracking_root_page_id != PageID(0) {
51 flags |= HeaderFlags::HAS_TRACKING_ROOT_ID;
52 required_buf += 8;
53 }
54 if !flags.is_empty() {
55 required_buf += 2;
56 }
57 required_buf
58 }
59
60 pub fn serialize_into(&self, buf: &mut [u8]) -> DcbResult<usize> {
61 let mut cursor = Cursor::new(buf);
62
63 cursor.write_all(&self.tsn.0.to_le_bytes())?;
65 cursor.write_all(&self.next_page_id.0.to_le_bytes())?;
66 cursor.write_all(&self.free_lists_tree_root_id.0.to_le_bytes())?;
67 cursor.write_all(&self.events_tree_root_id.0.to_le_bytes())?;
68 cursor.write_all(&self.tags_tree_root_id.0.to_le_bytes())?;
69 cursor.write_all(&self.next_position.0.to_le_bytes())?;
70
71 cursor.write_all(&self.schema_version.to_le_bytes())?;
73
74 let mut flags = HeaderFlags::empty();
76 if self.tracking_root_page_id != PageID(0) {
77 flags |= HeaderFlags::HAS_TRACKING_ROOT_ID;
78 }
79
80 if !flags.is_empty() {
82 cursor.write_all(&flags.bits().to_le_bytes())?;
84
85 if flags.contains(HeaderFlags::HAS_TRACKING_ROOT_ID) {
87 cursor.write_all(&self.tracking_root_page_id.0.to_le_bytes())?;
88 }
89 }
90
91 Ok(cursor.position() as usize)
93 }
94
95 pub fn from_slice(slice: &[u8]) -> DcbResult<Self> {
124 let mut reader = SliceReader::new(slice);
125
126 let tsn = reader.read_u64()?;
128 let next_page_id = reader.read_u64()?;
129 let freetree_root_id = reader.read_u64()?;
130 let position_root_id = reader.read_u64()?;
131 let tags_root_id = reader.read_u64()?;
132 let next_position = reader.read_u64()?;
133
134 let schema_version = if reader.remaining() >= 4 {
136 reader.read_u32()?
137 } else {
138 0
139 };
140
141 let flags = if reader.remaining() >= 2 {
143 let bits = reader.read_u16()?;
144 HeaderFlags::from_bits(bits).ok_or_else(|| {
145 DcbError::DeserializationError("unknown flag bits set".to_string())
146 })?
147 } else {
148 HeaderFlags::empty()
149 };
150
151 let tracking_tree_root_id = if flags.contains(HeaderFlags::HAS_TRACKING_ROOT_ID) {
153 reader.read_u64()?
154 } else {
155 0
156 };
157
158 Ok(HeaderNode {
159 tsn: Tsn(tsn),
160 next_page_id: PageID(next_page_id),
161 free_lists_tree_root_id: PageID(freetree_root_id),
162 events_tree_root_id: PageID(position_root_id),
163 tags_tree_root_id: PageID(tags_root_id),
164 next_position: Position(next_position),
165 schema_version,
166 tracking_root_page_id: PageID(tracking_tree_root_id),
167 })
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 #[test]
175 fn test_header_serialize_without_tracking_root_page_id() {
176 let header_node = HeaderNode {
178 tsn: Tsn(42),
179 next_page_id: PageID(123),
180 free_lists_tree_root_id: PageID(456),
181 events_tree_root_id: PageID(789),
182 tags_tree_root_id: PageID(321),
183 next_position: Position(9876543210),
184 schema_version: crate::db::DB_SCHEMA_VERSION,
185 tracking_root_page_id: PageID(0),
186 };
187
188 assert_eq!(52, header_node.calc_serialized_size());
189
190 let mut serialized = [0u8; 52];
192 let serialized_size = header_node.serialize_into(&mut serialized).unwrap();
193
194 assert_eq!(52, serialized_size);
196
197 assert_eq!(&42u64.to_le_bytes(), &serialized[0..8]);
200
201 assert_eq!(&123u64.to_le_bytes(), &serialized[8..16]);
203
204 assert_eq!(&456u64.to_le_bytes(), &serialized[16..24]);
206
207 assert_eq!(&789u64.to_le_bytes(), &serialized[24..32]);
209
210 assert_eq!(&321u64.to_le_bytes(), &serialized[32..40]);
212
213 assert_eq!(&9876543210u64.to_le_bytes(), &serialized[40..48]);
215
216 assert_eq!(
218 &crate::db::DB_SCHEMA_VERSION.to_le_bytes(),
219 &serialized[48..52]
220 );
221
222 let deserialized =
224 HeaderNode::from_slice(&serialized).expect("Failed to deserialize HeaderNode");
225
226 assert_eq!(header_node.tsn, deserialized.tsn);
228 assert_eq!(header_node.next_page_id, deserialized.next_page_id);
229 assert_eq!(
230 header_node.free_lists_tree_root_id,
231 deserialized.free_lists_tree_root_id
232 );
233 assert_eq!(
234 header_node.events_tree_root_id,
235 deserialized.events_tree_root_id
236 );
237 assert_eq!(header_node.next_position, deserialized.next_position);
238 assert_eq!(header_node.schema_version, deserialized.schema_version);
239 assert_eq!(
240 header_node.tracking_root_page_id,
241 deserialized.tracking_root_page_id
242 );
243 }
244
245 #[test]
246 fn test_header_serialize_with_tracking_root_page_id() {
247 let header_node = HeaderNode {
249 tsn: Tsn(42),
250 next_page_id: PageID(123),
251 free_lists_tree_root_id: PageID(456),
252 events_tree_root_id: PageID(789),
253 tags_tree_root_id: PageID(321),
254 next_position: Position(9876543210),
255 schema_version: crate::db::DB_SCHEMA_VERSION,
256 tracking_root_page_id: PageID(953),
257 };
258
259 assert_eq!(62, header_node.calc_serialized_size());
260
261 let mut serialized = [0u8; 62];
263 let serialized_size = header_node.serialize_into(&mut serialized).unwrap();
264
265 assert_eq!(62, serialized_size);
267
268 assert_eq!(&42u64.to_le_bytes(), &serialized[0..8]);
271
272 assert_eq!(&123u64.to_le_bytes(), &serialized[8..16]);
274
275 assert_eq!(&456u64.to_le_bytes(), &serialized[16..24]);
277
278 assert_eq!(&789u64.to_le_bytes(), &serialized[24..32]);
280
281 assert_eq!(&321u64.to_le_bytes(), &serialized[32..40]);
283
284 assert_eq!(&9876543210u64.to_le_bytes(), &serialized[40..48]);
286
287 assert_eq!(
289 &crate::db::DB_SCHEMA_VERSION.to_le_bytes(),
290 &serialized[48..52]
291 );
292
293 assert_eq!(&1u16.to_le_bytes(), &serialized[52..54]);
295
296 assert_eq!(&953u64.to_le_bytes(), &serialized[54..62]);
298
299 let deserialized =
301 HeaderNode::from_slice(&serialized).expect("Failed to deserialize HeaderNode");
302
303 assert_eq!(header_node.tsn, deserialized.tsn);
305 assert_eq!(header_node.next_page_id, deserialized.next_page_id);
306 assert_eq!(
307 header_node.free_lists_tree_root_id,
308 deserialized.free_lists_tree_root_id
309 );
310 assert_eq!(
311 header_node.events_tree_root_id,
312 deserialized.events_tree_root_id
313 );
314 assert_eq!(header_node.next_position, deserialized.next_position);
315 assert_eq!(header_node.schema_version, deserialized.schema_version);
316 assert_eq!(
317 header_node.tracking_root_page_id,
318 deserialized.tracking_root_page_id
319 );
320 }
321}
322
323#[cfg(test)]
324mod header_node_legacy_tests {
325 use super::*;
326
327 #[test]
328 fn test_legacy_48_byte_deserialize_sets_schema_version_0() {
329 let header_node = HeaderNode {
331 tsn: Tsn(1),
332 next_page_id: PageID(2),
333 free_lists_tree_root_id: PageID(3),
334 events_tree_root_id: PageID(4),
335 tags_tree_root_id: PageID(5),
336 next_position: Position(6),
337 schema_version: crate::db::DB_SCHEMA_VERSION,
338 tracking_root_page_id: PageID(0),
339 };
340 let mut bytes52 = [0u8; 52];
341 header_node.serialize_into(&mut bytes52).unwrap();
342
343 let mut bytes48 = [0u8; 48];
345 bytes48.copy_from_slice(&bytes52[..48]);
346
347 let deserialized =
349 HeaderNode::from_slice(&bytes48).expect("legacy 48-byte header should deserialize");
350 assert_eq!(deserialized.tsn, Tsn(1));
351 assert_eq!(deserialized.next_page_id, PageID(2));
352 assert_eq!(deserialized.free_lists_tree_root_id, PageID(3));
353 assert_eq!(deserialized.events_tree_root_id, PageID(4));
354 assert_eq!(deserialized.tags_tree_root_id, PageID(5));
355 assert_eq!(deserialized.next_position, Position(6));
356 assert_eq!(deserialized.schema_version, 0);
357 }
358}