use crate::common::PageID;
use crate::node::Node;
use std::ops::Range;
use umadb_dcb::{DCBError, DCBResult};
#[derive(Debug, Clone)]
pub struct Page {
pub page_id: PageID,
pub node: Node,
}
pub const PAGE_HEADER_SIZE: usize = 9;
const HEADER_LAYOUT_NODE_TYPE_BYTE: usize = 0;
const HEADER_LAYOUT_CRC_BYTES: Range<usize> = 1..5;
const HEADER_LAYOUT_BODY_LEN_BYTES: Range<usize> = 5..9;
impl Page {
pub fn new(page_id: PageID, node: Node) -> Self {
Self { page_id, node }
}
#[inline]
pub fn calc_serialized_size(&self) -> usize {
PAGE_HEADER_SIZE + self.node.calc_serialized_size()
}
pub fn serialize_into(&self, buf: &mut [u8]) -> DCBResult<()> {
serialize_page_into(buf, &self.node)?;
Ok(())
}
#[inline]
pub fn deserialize(page_id: PageID, page_data: &[u8]) -> DCBResult<Self> {
if page_data.len() < PAGE_HEADER_SIZE {
return Err(DCBError::DatabaseCorrupted(
"Page data too short".to_string(),
));
}
let header = &page_data[..PAGE_HEADER_SIZE];
let node_type = header[HEADER_LAYOUT_NODE_TYPE_BYTE];
let crc = u32::from_le_bytes(header[HEADER_LAYOUT_CRC_BYTES].try_into().unwrap());
let data_len =
u32::from_le_bytes(header[HEADER_LAYOUT_BODY_LEN_BYTES].try_into().unwrap()) as usize;
if PAGE_HEADER_SIZE + data_len > page_data.len() {
return Err(DCBError::DatabaseCorrupted(
"Page data length mismatch".to_string(),
));
}
let data = &page_data[PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + data_len];
let calculated_crc = calc_crc(data);
if calculated_crc != crc {
return Err(DCBError::DatabaseCorrupted(format!(
"CRC mismatch (page ID: {page_id:?})"
)));
}
let node = Node::deserialize(node_type, data)?;
Ok(Self { page_id, node })
}
}
pub fn serialize_page_into(buf: &mut [u8], node_ref: &Node) -> Result<(), DCBError> {
let body_len = serialize_page_node_into(buf, node_ref)?;
serialize_page_header_into(buf, body_len, node_ref.get_type_byte());
Ok(())
}
#[inline(always)]
fn serialize_page_node_into(buf: &mut [u8], node_ref: &Node) -> Result<usize, DCBError> {
let body_len = {
let body_slice = &mut buf[PAGE_HEADER_SIZE..];
node_ref.serialize_into(body_slice)?
};
let tail_start = PAGE_HEADER_SIZE + body_len;
if tail_start < buf.len() {
buf[tail_start..].fill(0);
}
Ok(body_len)
}
#[inline(always)]
fn serialize_page_header_into(buf: &mut [u8], body_len: usize, node_type_byte: u8) {
let crc = calc_crc(&buf[PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + body_len]);
buf[HEADER_LAYOUT_NODE_TYPE_BYTE] = node_type_byte;
buf[HEADER_LAYOUT_CRC_BYTES].copy_from_slice(&crc.to_le_bytes());
buf[HEADER_LAYOUT_BODY_LEN_BYTES].copy_from_slice(&(body_len as u32).to_le_bytes());
}
#[inline(always)]
pub fn calc_crc(data: &[u8]) -> u32 {
crc32fast::hash(data)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::Position;
use crate::common::{PageID, Tsn};
use crate::header_node::HeaderNode;
#[test]
fn test_page_serialization_and_size() {
let node = Node::Header(HeaderNode {
tsn: Tsn(42),
next_page_id: PageID(123),
free_lists_tree_root_id: PageID(456),
events_tree_root_id: PageID(789),
tags_tree_root_id: PageID(1011),
next_position: Position(1234),
});
let page_id = PageID(1);
let page = Page::new(page_id, node);
let calculated_size = page.calc_serialized_size();
let mut page_buf = vec![0u8; crate::db::DEFAULT_PAGE_SIZE];
page.serialize_into(&mut page_buf)
.expect("Failed to serialize page into buffer");
let body_len = u32::from_le_bytes(page_buf[5..9].try_into().unwrap()) as usize;
let effective_len = PAGE_HEADER_SIZE + body_len;
assert_eq!(
calculated_size, effective_len,
"Calculated size {} should match effective serialized size {}",
calculated_size, effective_len
);
let deserialized =
Page::deserialize(page_id, &page_buf).expect("Failed to deserialize page");
assert_eq!(
page.page_id, deserialized.page_id,
"Original page_id {:?} should match deserialized page_id {:?}",
page.page_id, deserialized.page_id
);
assert_eq!(
page.node, deserialized.node,
"Original node {:?} should match deserialized node {:?}",
page.node, deserialized.node
);
}
}