1use crate::common::PageID;
2use crate::node::Node;
3use std::ops::Range;
4use umadb_dcb::{DCBError, DCBResult};
5
6#[derive(Debug, Clone)]
8pub struct Page {
9 pub page_id: PageID,
10 pub node: Node,
11}
12
13pub const PAGE_HEADER_SIZE: usize = 9;
15const HEADER_LAYOUT_NODE_TYPE_BYTE: usize = 0;
16const HEADER_LAYOUT_CRC_BYTES: Range<usize> = 1..5;
17const HEADER_LAYOUT_BODY_LEN_BYTES: Range<usize> = 5..9;
18
19impl Page {
21 pub fn new(page_id: PageID, node: Node) -> Self {
22 Self { page_id, node }
23 }
24
25 #[inline]
26 pub fn calc_serialized_size(&self) -> usize {
27 PAGE_HEADER_SIZE + self.node.calc_serialized_size()
29 }
30
31 pub fn serialize_into(&self, buf: &mut [u8]) -> DCBResult<()> {
33 serialize_page_into(buf, &self.node)?;
34 Ok(())
35 }
36
37 #[inline]
38 pub fn deserialize(page_id: PageID, page_data: &[u8]) -> DCBResult<Self> {
39 if page_data.len() < PAGE_HEADER_SIZE {
40 return Err(DCBError::DatabaseCorrupted(
41 "Page data too short".to_string(),
42 ));
43 }
44
45 let header = &page_data[..PAGE_HEADER_SIZE];
47 let node_type = header[HEADER_LAYOUT_NODE_TYPE_BYTE];
48 let crc = u32::from_le_bytes(header[HEADER_LAYOUT_CRC_BYTES].try_into().unwrap());
49 let data_len =
50 u32::from_le_bytes(header[HEADER_LAYOUT_BODY_LEN_BYTES].try_into().unwrap()) as usize;
51
52 if PAGE_HEADER_SIZE + data_len > page_data.len() {
53 return Err(DCBError::DatabaseCorrupted(
54 "Page data length mismatch".to_string(),
55 ));
56 }
57
58 let data = &page_data[PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + data_len];
60
61 let calculated_crc = calc_crc(data);
63
64 if calculated_crc != crc {
65 return Err(DCBError::DatabaseCorrupted(format!(
66 "CRC mismatch (page ID: {page_id:?})"
67 )));
68 }
69
70 let node = Node::deserialize(node_type, data)?;
72
73 Ok(Self { page_id, node })
74 }
75}
76
77pub fn serialize_page_into(buf: &mut [u8], node_ref: &Node) -> Result<(), DCBError> {
78 let body_len = serialize_page_node_into(buf, node_ref)?;
79 serialize_page_header_into(buf, body_len, node_ref.get_type_byte());
80 Ok(())
81}
82
83#[inline(always)]
84fn serialize_page_node_into(buf: &mut [u8], node_ref: &Node) -> Result<usize, DCBError> {
85 let body_len = {
87 let body_slice = &mut buf[PAGE_HEADER_SIZE..];
88 node_ref.serialize_into(body_slice)?
89 };
90
91 let tail_start = PAGE_HEADER_SIZE + body_len;
93 if tail_start < buf.len() {
94 buf[tail_start..].fill(0);
95 }
96 Ok(body_len)
97}
98
99#[inline(always)]
100fn serialize_page_header_into(buf: &mut [u8], body_len: usize, node_type_byte: u8) {
101 let crc = calc_crc(&buf[PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + body_len]);
103
104 buf[HEADER_LAYOUT_NODE_TYPE_BYTE] = node_type_byte;
106 buf[HEADER_LAYOUT_CRC_BYTES].copy_from_slice(&crc.to_le_bytes());
107 buf[HEADER_LAYOUT_BODY_LEN_BYTES].copy_from_slice(&(body_len as u32).to_le_bytes());
108}
109
110#[inline(always)]
112pub fn calc_crc(data: &[u8]) -> u32 {
113 crc32fast::hash(data)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::common::Position;
120 use crate::common::{PageID, Tsn};
121 use crate::header_node::HeaderNode;
122
123 #[test]
124 fn test_page_serialization_and_size() {
125 let node = Node::Header(HeaderNode {
127 tsn: Tsn(42),
128 next_page_id: PageID(123),
129 free_lists_tree_root_id: PageID(456),
130 events_tree_root_id: PageID(789),
131 tags_tree_root_id: PageID(1011),
132 next_position: Position(1234),
133 });
134
135 let page_id = PageID(1);
137 let page = Page::new(page_id, node);
138
139 let calculated_size = page.calc_serialized_size();
141
142 let mut page_buf = vec![0u8; crate::db::DEFAULT_PAGE_SIZE];
144 page.serialize_into(&mut page_buf)
145 .expect("Failed to serialize page into buffer");
146
147 let body_len = u32::from_le_bytes(page_buf[5..9].try_into().unwrap()) as usize;
149 let effective_len = PAGE_HEADER_SIZE + body_len;
150 assert_eq!(
151 calculated_size, effective_len,
152 "Calculated size {} should match effective serialized size {}",
153 calculated_size, effective_len
154 );
155
156 let deserialized =
158 Page::deserialize(page_id, &page_buf).expect("Failed to deserialize page");
159
160 assert_eq!(
162 page.page_id, deserialized.page_id,
163 "Original page_id {:?} should match deserialized page_id {:?}",
164 page.page_id, deserialized.page_id
165 );
166 assert_eq!(
167 page.node, deserialized.node,
168 "Original node {:?} should match deserialized node {:?}",
169 page.node, deserialized.node
170 );
171 }
172}