#![forbid(unsafe_code)]
use crate::pager::page::{Page, PAGE_SIZE, PAGE_TRAILER_SIZE};
#[must_use]
pub fn crc32c(bytes: &[u8]) -> u32 {
crc32c::crc32c(bytes)
}
#[must_use]
pub fn crc32c_append(crc: u32, bytes: &[u8]) -> u32 {
crc32c::crc32c_append(crc, bytes)
}
pub const TRAILER_OFFSET: usize = PAGE_SIZE - PAGE_TRAILER_SIZE;
pub const V1_CRC_MASK: u32 = 0x7FFF_FFFF;
pub const V1_FLAG_MASK: u32 = 0x8000_0000;
pub fn write_page_trailer(page: &mut Page) {
let buf = page.as_bytes_mut();
let crc = crc32c(&buf[..TRAILER_OFFSET]);
buf[TRAILER_OFFSET..].copy_from_slice(&crc.to_le_bytes());
}
#[must_use]
pub fn page_trailer_valid(page: &Page) -> bool {
let buf = page.as_bytes();
let stored = read_trailer_u32(buf);
let computed = crc32c(&buf[..TRAILER_OFFSET]);
stored == computed
}
pub fn write_page_trailer_v1(page: &mut Page, compressed: bool) {
let buf = page.as_bytes_mut();
let crc = crc32c(&buf[..TRAILER_OFFSET]) & V1_CRC_MASK;
let flag = if compressed { V1_FLAG_MASK } else { 0 };
let trailer = crc | flag;
buf[TRAILER_OFFSET..].copy_from_slice(&trailer.to_le_bytes());
}
#[must_use]
pub fn page_trailer_valid_v1(page: &Page) -> bool {
let buf = page.as_bytes();
let stored = read_trailer_u32(buf) & V1_CRC_MASK;
let computed = crc32c(&buf[..TRAILER_OFFSET]) & V1_CRC_MASK;
stored == computed
}
#[must_use]
pub fn page_trailer_flag_v1(page: &Page) -> bool {
let buf = page.as_bytes();
(read_trailer_u32(buf) & V1_FLAG_MASK) != 0
}
fn read_trailer_u32(buf: &[u8; PAGE_SIZE]) -> u32 {
let mut t = [0u8; PAGE_TRAILER_SIZE];
t.copy_from_slice(&buf[TRAILER_OFFSET..]);
u32::from_le_bytes(t)
}
#[cfg(test)]
mod tests {
use super::{
page_trailer_flag_v1, page_trailer_valid, page_trailer_valid_v1, write_page_trailer,
write_page_trailer_v1, V1_FLAG_MASK,
};
use crate::pager::page::Page;
#[test]
fn trailer_round_trip() {
let mut p = Page::zeroed();
for (i, b) in p.as_bytes_mut().iter_mut().enumerate().take(64) {
*b = u8::try_from(i).expect("i < 64");
}
write_page_trailer(&mut p);
assert!(page_trailer_valid(&p));
}
#[test]
fn flipping_any_body_byte_invalidates_trailer() {
let mut p = Page::zeroed();
p.as_bytes_mut()[100] = 0xAA;
write_page_trailer(&mut p);
assert!(page_trailer_valid(&p));
p.as_bytes_mut()[42] ^= 0x01;
assert!(!page_trailer_valid(&p));
}
#[test]
fn flipping_trailer_byte_invalidates_trailer() {
let mut p = Page::zeroed();
write_page_trailer(&mut p);
assert!(page_trailer_valid(&p));
let len = p.as_bytes().len();
p.as_bytes_mut()[len - 1] ^= 0x80;
assert!(!page_trailer_valid(&p));
}
#[test]
fn v1_trailer_round_trip_uncompressed() {
let mut p = Page::zeroed();
p.as_bytes_mut()[10] = 0xAB;
write_page_trailer_v1(&mut p, false);
assert!(page_trailer_valid_v1(&p));
assert!(!page_trailer_flag_v1(&p));
}
#[test]
fn v1_trailer_round_trip_compressed() {
let mut p = Page::zeroed();
p.as_bytes_mut()[10] = 0xCD;
write_page_trailer_v1(&mut p, true);
assert!(page_trailer_valid_v1(&p));
assert!(page_trailer_flag_v1(&p));
}
#[test]
fn v1_trailer_flag_independent_of_crc() {
let mut p = Page::zeroed();
p.as_bytes_mut()[10] = 0xAB;
write_page_trailer_v1(&mut p, false);
assert!(page_trailer_valid_v1(&p));
assert!(!page_trailer_flag_v1(&p));
let len = p.as_bytes().len();
let trailer_off = len - 4;
let mut t = [0u8; 4];
t.copy_from_slice(&p.as_bytes()[trailer_off..]);
let mut trailer = u32::from_le_bytes(t);
trailer ^= V1_FLAG_MASK;
p.as_bytes_mut()[trailer_off..].copy_from_slice(&trailer.to_le_bytes());
assert!(page_trailer_valid_v1(&p));
assert!(page_trailer_flag_v1(&p));
}
#[test]
fn v1_trailer_flipping_body_invalidates() {
let mut p = Page::zeroed();
write_page_trailer_v1(&mut p, true);
assert!(page_trailer_valid_v1(&p));
p.as_bytes_mut()[42] ^= 0x01;
assert!(!page_trailer_valid_v1(&p));
}
}