Files
turso/core/storage/checksum.rs
2025-10-13 13:48:07 +05:30

192 lines
5.5 KiB
Rust

#![allow(unused_variables, dead_code)]
use crate::{CompletionError, Result};
const CHECKSUM_PAGE_SIZE: usize = 4096;
const CHECKSUM_SIZE: usize = 8;
pub(crate) const CHECKSUM_REQUIRED_RESERVED_BYTES: u8 = CHECKSUM_SIZE as u8;
#[derive(Clone)]
pub struct ChecksumContext {}
impl ChecksumContext {
pub fn new() -> Self {
ChecksumContext {}
}
#[cfg(not(feature = "checksum"))]
pub fn add_checksum_to_page(&self, _page: &mut [u8], _page_id: usize) -> Result<()> {
use crate::LimboError;
Err(LimboError::InternalError(
"tursodb must be recompiled with checksum feature in order to use checksums"
.to_string(),
))
}
#[cfg(not(feature = "checksum"))]
pub fn verify_checksum(
&self,
_page: &mut [u8],
_page_id: usize,
) -> std::result::Result<(), CompletionError> {
Err(CompletionError::ChecksumNotEnabled)
}
#[cfg(feature = "checksum")]
pub fn add_checksum_to_page(&self, page: &mut [u8], _page_id: usize) -> Result<()> {
if page.len() != CHECKSUM_PAGE_SIZE {
return Ok(());
}
// compute checksum on the actual page data (excluding the reserved checksum area)
let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];
let checksum = self.compute_checksum(actual_page);
let checksum_bytes = checksum.to_le_bytes();
assert_eq!(checksum_bytes.len(), CHECKSUM_SIZE);
page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..].copy_from_slice(&checksum_bytes);
Ok(())
}
#[cfg(feature = "checksum")]
pub fn verify_checksum(
&self,
page: &mut [u8],
page_id: usize,
) -> std::result::Result<(), CompletionError> {
if page.len() != CHECKSUM_PAGE_SIZE {
return Ok(());
}
let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];
let stored_checksum_bytes = &page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..];
let stored_checksum = u64::from_le_bytes(stored_checksum_bytes.try_into().unwrap());
let computed_checksum = self.compute_checksum(actual_page);
if stored_checksum != computed_checksum {
tracing::error!(
"Checksum mismatch on page {}: expected {:x}, got {:x}",
page_id,
stored_checksum,
computed_checksum
);
return Err(CompletionError::ChecksumMismatch {
page_id,
expected: stored_checksum,
actual: computed_checksum,
});
}
Ok(())
}
fn compute_checksum(&self, data: &[u8]) -> u64 {
twox_hash::XxHash3_64::oneshot(data)
}
pub fn required_reserved_bytes(&self) -> u8 {
CHECKSUM_REQUIRED_RESERVED_BYTES
}
}
impl Default for ChecksumContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[cfg(feature = "checksum")]
mod tests {
use super::*;
fn get_random_page() -> [u8; CHECKSUM_PAGE_SIZE] {
let mut page = [0u8; CHECKSUM_PAGE_SIZE];
for (i, byte) in page
.iter_mut()
.enumerate()
.take(CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE)
{
*byte = (i % 256) as u8;
}
page
}
#[test]
fn test_add_checksum_to_page() {
let ctx = ChecksumContext::new();
let mut page = get_random_page();
let result = ctx.add_checksum_to_page(&mut page, 2);
assert!(result.is_ok());
let checksum_bytes = &page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..];
let stored_checksum = u64::from_le_bytes(checksum_bytes.try_into().unwrap());
let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];
let expected_checksum = ctx.compute_checksum(actual_page);
assert_eq!(stored_checksum, expected_checksum);
}
#[test]
fn test_verify_checksum_valid() {
let ctx = ChecksumContext::new();
let mut page = get_random_page();
ctx.add_checksum_to_page(&mut page, 2).unwrap();
let result = ctx.verify_checksum(&mut page, 2);
assert!(result.is_ok());
}
#[test]
fn test_verify_checksum_mismatch() {
let ctx = ChecksumContext::new();
let mut page = get_random_page();
ctx.add_checksum_to_page(&mut page, 2).unwrap();
// corrupt the data to cause checksum mismatch
page[0] = 255;
let result = ctx.verify_checksum(&mut page, 2);
assert!(result.is_err());
match result.unwrap_err() {
CompletionError::ChecksumMismatch {
page_id,
expected,
actual,
} => {
assert_eq!(page_id, 2);
assert_ne!(expected, actual);
}
_ => panic!("Expected ChecksumMismatch error"),
}
}
#[test]
fn test_verify_checksum_corrupted_checksum() {
let ctx = ChecksumContext::new();
let mut page = get_random_page();
ctx.add_checksum_to_page(&mut page, 2).unwrap();
// corrupt the checksum itself
page[CHECKSUM_PAGE_SIZE - 1] = 255;
let result = ctx.verify_checksum(&mut page, 2);
assert!(result.is_err());
match result.unwrap_err() {
CompletionError::ChecksumMismatch {
page_id,
expected,
actual,
} => {
assert_eq!(page_id, 2);
assert_ne!(expected, actual);
}
_ => panic!("Expected ChecksumMismatch error"),
}
}
}