mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-27 04:54:21 +01:00
192 lines
5.5 KiB
Rust
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"),
|
|
}
|
|
}
|
|
}
|