use crate::{Error, Result};
#[allow(dead_code)]
pub(crate) mod btree;
#[allow(dead_code)]
pub(crate) mod free_list;
#[allow(dead_code)]
pub(crate) mod header;
#[cfg(feature = "mmap")]
#[allow(dead_code)]
pub(crate) mod mmap;
#[allow(dead_code)]
pub(crate) mod pager;
#[allow(dead_code)]
pub(crate) mod value;
pub(crate) const PAGE_SIZE: usize = 4096;
pub(crate) const PAGE_HEADER_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub(crate) struct PageId(u64);
impl PageId {
#[must_use]
pub(crate) const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub(crate) const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum PageType {
Header = 0,
BTreeNode = 1,
ValueLeaf = 2,
FreeList = 3,
Overflow = 4,
}
impl PageType {
fn from_u8(raw: u8) -> Result<Self> {
match raw {
0 => Ok(Self::Header),
1 => Ok(Self::BTreeNode),
2 => Ok(Self::ValueLeaf),
3 => Ok(Self::FreeList),
4 => Ok(Self::Overflow),
_ => Err(Error::Corrupted {
offset: 0,
reason: "invalid page type",
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PageHeader {
pub(crate) page_type: PageType,
pub(crate) flags: u8,
pub(crate) lsn: u64,
pub(crate) page_crc: u32,
}
impl PageHeader {
#[must_use]
pub(crate) const fn new(page_type: PageType) -> Self {
Self {
page_type,
flags: 0,
lsn: 0,
page_crc: 0,
}
}
fn encode_into(self, out: &mut [u8]) {
debug_assert!(out.len() >= PAGE_HEADER_LEN);
out[..PAGE_HEADER_LEN].fill(0);
out[0] = self.page_type as u8;
out[1] = self.flags;
out[4..12].copy_from_slice(&self.lsn.to_le_bytes());
out[12..16].copy_from_slice(&self.page_crc.to_le_bytes());
}
fn decode_from(input: &[u8]) -> Result<Self> {
if input.len() < PAGE_HEADER_LEN {
return Err(Error::Corrupted {
offset: 0,
reason: "page header truncated",
});
}
let mut lsn = [0_u8; 8];
lsn.copy_from_slice(&input[4..12]);
let mut crc = [0_u8; 4];
crc.copy_from_slice(&input[12..16]);
Ok(Self {
page_type: PageType::from_u8(input[0])?,
flags: input[1],
lsn: u64::from_le_bytes(lsn),
page_crc: u32::from_le_bytes(crc),
})
}
}
#[derive(Debug, Clone)]
#[repr(C, align(4096))]
pub(crate) struct Page {
bytes: [u8; PAGE_SIZE],
}
impl Page {
#[must_use]
pub(crate) fn new(header: PageHeader) -> Self {
let mut page = Self {
bytes: [0_u8; PAGE_SIZE],
};
page.set_header(header);
page
}
#[must_use]
pub(crate) fn from_bytes(bytes: [u8; PAGE_SIZE]) -> Self {
Self { bytes }
}
pub(crate) fn header(&self) -> Result<PageHeader> {
PageHeader::decode_from(&self.bytes[..PAGE_HEADER_LEN])
}
pub(crate) fn set_header(&mut self, header: PageHeader) {
header.encode_into(&mut self.bytes[..PAGE_HEADER_LEN]);
}
#[must_use]
pub(crate) fn as_bytes(&self) -> &[u8; PAGE_SIZE] {
&self.bytes
}
#[must_use]
pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8; PAGE_SIZE] {
&mut self.bytes
}
pub(crate) fn refresh_crc(&mut self) -> Result<u32> {
let mut header = self.header()?;
header.page_crc = page_crc(&self.bytes);
self.set_header(header);
Ok(header.page_crc)
}
pub(crate) fn validate_crc(&self) -> Result<()> {
let header = self.header()?;
let actual = page_crc(&self.bytes);
if header.page_crc == actual {
return Ok(());
}
Err(Error::Corrupted {
offset: 0,
reason: "page crc mismatch",
})
}
}
#[must_use]
pub(crate) fn page_crc(bytes: &[u8; PAGE_SIZE]) -> u32 {
let mut hasher = crc32fast::Hasher::new();
hasher.update(&bytes[PAGE_HEADER_LEN..]);
hasher.finalize()
}
#[cfg(test)]
mod tests {
use core::mem::{align_of, size_of};
use super::{page_crc, Page, PageHeader, PageId, PageType, PAGE_HEADER_LEN, PAGE_SIZE};
use crate::Error;
#[test]
fn test_page_id_round_trip() {
let page_id = PageId::new(42);
assert_eq!(page_id.get(), 42);
}
#[test]
fn test_page_type_round_trip() {
let variants = [
(PageType::Header, 0_u8),
(PageType::BTreeNode, 1_u8),
(PageType::ValueLeaf, 2_u8),
(PageType::FreeList, 3_u8),
(PageType::Overflow, 4_u8),
];
for (page_type, raw) in variants {
let header = PageHeader {
page_type,
flags: 7,
lsn: 11,
page_crc: 13,
};
let mut encoded = [0_u8; PAGE_HEADER_LEN];
header.encode_into(&mut encoded);
assert_eq!(encoded[0], raw);
let decoded = PageHeader::decode_from(&encoded);
assert!(decoded.is_ok());
assert!(matches!(
decoded,
Ok(PageHeader {
page_type: value,
..
}) if value == page_type
));
}
}
#[test]
fn test_page_header_round_trip() {
let header = PageHeader {
page_type: PageType::BTreeNode,
flags: 0xA5,
lsn: 0x0102_0304_0506_0708,
page_crc: 0x1122_3344,
};
let mut encoded = [0_u8; PAGE_HEADER_LEN];
header.encode_into(&mut encoded);
let decoded = PageHeader::decode_from(&encoded);
assert!(matches!(decoded, Ok(value) if value == header));
}
#[test]
fn test_page_round_trip_with_crc() {
let mut page = Page::new(PageHeader::new(PageType::ValueLeaf));
page.as_mut_bytes()[PAGE_HEADER_LEN..PAGE_HEADER_LEN + 5].copy_from_slice(b"value");
let refreshed = page.refresh_crc();
assert!(refreshed.is_ok());
let refreshed = match refreshed {
Ok(value) => value,
Err(err) => panic!("page crc refresh should succeed: {err}"),
};
assert!(page.validate_crc().is_ok());
let header = page.header();
assert!(header.is_ok());
assert!(matches!(
header,
Ok(PageHeader {
page_type: PageType::ValueLeaf,
page_crc,
..
}) if page_crc == refreshed
));
}
#[test]
fn test_page_crc_detects_payload_corruption() {
let mut page = Page::new(PageHeader::new(PageType::Overflow));
page.as_mut_bytes()[PAGE_HEADER_LEN] = 9;
let refreshed = page.refresh_crc();
assert!(refreshed.is_ok());
page.as_mut_bytes()[PAGE_HEADER_LEN] ^= 0xFF;
let validated = page.validate_crc();
assert!(matches!(
validated,
Err(Error::Corrupted {
reason: "page crc mismatch",
..
})
));
}
#[test]
fn test_page_alignment_and_size() {
assert_eq!(size_of::<Page>(), PAGE_SIZE);
assert_eq!(align_of::<Page>(), PAGE_SIZE);
let empty = [0_u8; PAGE_SIZE];
assert_eq!(page_crc(&empty), crc32fast::hash(&empty[PAGE_HEADER_LEN..]));
}
}