use crate::innodb::constants::*;
use crate::innodb::vendor::VendorInfo;
use byteorder::{BigEndian, ByteOrder};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ChecksumAlgorithm {
Crc32c,
InnoDB,
MariaDbFullCrc32,
None,
}
pub fn validate_checksum(
page_data: &[u8],
page_size: u32,
vendor_info: Option<&VendorInfo>,
) -> ChecksumResult {
let ps = page_size as usize;
if page_data.len() < ps {
return ChecksumResult {
algorithm: ChecksumAlgorithm::None,
valid: false,
stored_checksum: 0,
calculated_checksum: 0,
};
}
let first_u32 = BigEndian::read_u32(&page_data[FIL_PAGE_SPACE_OR_CHKSUM..]);
if first_u32 == 0 {
let all_zero = page_data[..ps].iter().all(|&b| b == 0);
if all_zero {
return ChecksumResult {
algorithm: ChecksumAlgorithm::None,
valid: true,
stored_checksum: 0,
calculated_checksum: 0,
};
}
}
if vendor_info.is_some_and(|v| v.is_full_crc32()) {
let stored = BigEndian::read_u32(&page_data[ps - 4..ps]);
let calculated = calculate_mariadb_full_crc32(page_data, ps);
if stored == calculated {
return ChecksumResult {
algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
valid: true,
stored_checksum: stored,
calculated_checksum: calculated,
};
}
return ChecksumResult {
algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
valid: false,
stored_checksum: stored,
calculated_checksum: calculated,
};
}
let stored_checksum = first_u32;
if stored_checksum == 0xDEADBEEF {
return ChecksumResult {
algorithm: ChecksumAlgorithm::None,
valid: true,
stored_checksum,
calculated_checksum: 0xDEADBEEF,
};
}
let crc_checksum = calculate_crc32c(page_data, ps);
if stored_checksum == crc_checksum {
return ChecksumResult {
algorithm: ChecksumAlgorithm::Crc32c,
valid: true,
stored_checksum,
calculated_checksum: crc_checksum,
};
}
let innodb_checksum = calculate_innodb_checksum(page_data, ps);
if stored_checksum == innodb_checksum {
return ChecksumResult {
algorithm: ChecksumAlgorithm::InnoDB,
valid: true,
stored_checksum,
calculated_checksum: innodb_checksum,
};
}
ChecksumResult {
algorithm: ChecksumAlgorithm::Crc32c,
valid: false,
stored_checksum,
calculated_checksum: crc_checksum,
}
}
#[derive(Debug, Clone)]
pub struct ChecksumResult {
pub algorithm: ChecksumAlgorithm,
pub valid: bool,
pub stored_checksum: u32,
pub calculated_checksum: u32,
}
pub fn calculate_mariadb_full_crc32(page_data: &[u8], page_size: usize) -> u32 {
crc32c::crc32c(&page_data[0..page_size - 4])
}
pub fn calculate_crc32c(page_data: &[u8], page_size: usize) -> u32 {
let end = page_size - SIZE_FIL_TRAILER;
let crc1 = crc32c::crc32c(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
let crc2 = crc32c::crc32c(&page_data[FIL_PAGE_DATA..end]);
crc1 ^ crc2
}
#[inline]
fn ut_fold_ulint_pair(n1: u32, n2: u32) -> u32 {
let step = n1 ^ n2 ^ UT_HASH_RANDOM_MASK2;
let step = (step << 8).wrapping_add(n1);
let step = step ^ UT_HASH_RANDOM_MASK;
step.wrapping_add(n2)
}
fn ut_fold_binary(data: &[u8]) -> u32 {
let mut fold: u32 = 0;
for &byte in data {
fold = ut_fold_ulint_pair(fold, byte as u32);
}
fold
}
pub fn calculate_innodb_checksum(page_data: &[u8], page_size: usize) -> u32 {
let end = page_size - SIZE_FIL_TRAILER;
let fold1 = ut_fold_binary(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
let fold2 = ut_fold_binary(&page_data[FIL_PAGE_DATA..end]);
fold1.wrapping_add(fold2)
}
pub fn validate_lsn(page_data: &[u8], page_size: u32) -> bool {
let ps = page_size as usize;
if page_data.len() < ps {
return false;
}
let header_lsn = BigEndian::read_u64(&page_data[FIL_PAGE_LSN..]);
let header_lsn_low32 = (header_lsn & 0xFFFFFFFF) as u32;
let trailer_offset = ps - SIZE_FIL_TRAILER;
let trailer_lsn_low32 = BigEndian::read_u32(&page_data[trailer_offset + 4..]);
header_lsn_low32 == trailer_lsn_low32
}
pub fn recalculate_checksum(page_data: &mut [u8], page_size: u32, algorithm: ChecksumAlgorithm) {
let ps = page_size as usize;
if page_data.len() < ps {
return;
}
match algorithm {
ChecksumAlgorithm::Crc32c => {
let checksum = calculate_crc32c(page_data, ps);
BigEndian::write_u32(&mut page_data[FIL_PAGE_SPACE_OR_CHKSUM..], checksum);
}
ChecksumAlgorithm::InnoDB => {
let checksum = calculate_innodb_checksum(page_data, ps);
BigEndian::write_u32(&mut page_data[FIL_PAGE_SPACE_OR_CHKSUM..], checksum);
}
ChecksumAlgorithm::MariaDbFullCrc32 => {
let checksum = calculate_mariadb_full_crc32(page_data, ps);
BigEndian::write_u32(&mut page_data[ps - 4..], checksum);
}
ChecksumAlgorithm::None => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::innodb::vendor::MariaDbFormat;
#[test]
fn test_all_zero_page_is_valid() {
let page = vec![0u8; 16384];
let result = validate_checksum(&page, 16384, None);
assert!(result.valid);
}
#[test]
fn test_no_checksum_magic() {
let mut page = vec![0u8; 16384];
BigEndian::write_u32(&mut page[0..], 0xDEADBEEF);
let result = validate_checksum(&page, 16384, None);
assert!(result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::None);
}
#[test]
fn test_mariadb_full_crc32() {
let ps = 16384usize;
let mut page = vec![0xABu8; ps];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
let crc = crc32c::crc32c(&page[0..ps - 4]);
BigEndian::write_u32(&mut page[ps - 4..], crc);
let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
let result = validate_checksum(&page, ps as u32, Some(&vendor));
assert!(result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
}
#[test]
fn test_mariadb_full_crc32_invalid() {
let ps = 16384usize;
let mut page = vec![0xABu8; ps];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u32(&mut page[ps - 4..], 0xDEADDEAD);
let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
let result = validate_checksum(&page, ps as u32, Some(&vendor));
assert!(!result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
}
#[test]
fn test_lsn_validation_matching() {
let mut page = vec![0u8; 16384];
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
BigEndian::write_u32(&mut page[16380..], 0x12345678);
assert!(validate_lsn(&page, 16384));
}
#[test]
fn test_lsn_validation_mismatch() {
let mut page = vec![0u8; 16384];
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
BigEndian::write_u32(&mut page[16380..], 0xAAAAAAAA);
assert!(!validate_lsn(&page, 16384));
}
#[test]
fn test_recalculate_checksum_crc32c() {
let ps = 16384usize;
let mut page = vec![0u8; ps];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 5000);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], 1);
let trailer = ps - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page[trailer + 4..], 5000);
BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_OR_CHKSUM..], 0xDEAD);
let result = validate_checksum(&page, ps as u32, None);
assert!(!result.valid);
recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::Crc32c);
let result = validate_checksum(&page, ps as u32, None);
assert!(result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::Crc32c);
}
#[test]
fn test_recalculate_checksum_innodb() {
let ps = 16384usize;
let mut page = vec![0u8; ps];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 5000);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], 1);
let trailer = ps - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page[trailer + 4..], 5000);
recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::InnoDB);
let result = validate_checksum(&page, ps as u32, None);
assert!(result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::InnoDB);
}
#[test]
fn test_recalculate_checksum_mariadb() {
let ps = 16384usize;
let mut page = vec![0xABu8; ps];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::MariaDbFullCrc32);
let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
let result = validate_checksum(&page, ps as u32, Some(&vendor));
assert!(result.valid);
assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
}
}