1use crate::innodb::constants::*;
19use crate::innodb::vendor::VendorInfo;
20use byteorder::{BigEndian, ByteOrder};
21use serde::Serialize;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
40pub enum ChecksumAlgorithm {
41 Crc32c,
43 InnoDB,
45 MariaDbFullCrc32,
47 None,
49}
50
51pub fn validate_checksum(
84 page_data: &[u8],
85 page_size: u32,
86 vendor_info: Option<&VendorInfo>,
87) -> ChecksumResult {
88 let ps = page_size as usize;
89 if page_data.len() < ps {
90 return ChecksumResult {
91 algorithm: ChecksumAlgorithm::None,
92 valid: false,
93 stored_checksum: 0,
94 calculated_checksum: 0,
95 };
96 }
97
98 let first_u32 = BigEndian::read_u32(&page_data[FIL_PAGE_SPACE_OR_CHKSUM..]);
100 if first_u32 == 0 {
101 let all_zero = page_data[..ps].iter().all(|&b| b == 0);
102 if all_zero {
103 return ChecksumResult {
104 algorithm: ChecksumAlgorithm::None,
105 valid: true,
106 stored_checksum: 0,
107 calculated_checksum: 0,
108 };
109 }
110 }
111
112 if vendor_info.is_some_and(|v| v.is_full_crc32()) {
114 let stored = BigEndian::read_u32(&page_data[ps - 4..ps]);
115 let calculated = calculate_mariadb_full_crc32(page_data, ps);
116 if stored == calculated {
117 return ChecksumResult {
118 algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
119 valid: true,
120 stored_checksum: stored,
121 calculated_checksum: calculated,
122 };
123 }
124 return ChecksumResult {
126 algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
127 valid: false,
128 stored_checksum: stored,
129 calculated_checksum: calculated,
130 };
131 }
132
133 let stored_checksum = first_u32;
134
135 if stored_checksum == 0xDEADBEEF {
137 return ChecksumResult {
138 algorithm: ChecksumAlgorithm::None,
139 valid: true,
140 stored_checksum,
141 calculated_checksum: 0xDEADBEEF,
142 };
143 }
144
145 let crc_checksum = calculate_crc32c(page_data, ps);
147 if stored_checksum == crc_checksum {
148 return ChecksumResult {
149 algorithm: ChecksumAlgorithm::Crc32c,
150 valid: true,
151 stored_checksum,
152 calculated_checksum: crc_checksum,
153 };
154 }
155
156 let innodb_checksum = calculate_innodb_checksum(page_data, ps);
158 if stored_checksum == innodb_checksum {
159 return ChecksumResult {
160 algorithm: ChecksumAlgorithm::InnoDB,
161 valid: true,
162 stored_checksum,
163 calculated_checksum: innodb_checksum,
164 };
165 }
166
167 ChecksumResult {
169 algorithm: ChecksumAlgorithm::Crc32c,
170 valid: false,
171 stored_checksum,
172 calculated_checksum: crc_checksum,
173 }
174}
175
176#[derive(Debug, Clone)]
193pub struct ChecksumResult {
194 pub algorithm: ChecksumAlgorithm,
196 pub valid: bool,
198 pub stored_checksum: u32,
200 pub calculated_checksum: u32,
202}
203
204pub fn calculate_mariadb_full_crc32(page_data: &[u8], page_size: usize) -> u32 {
210 crc32c::crc32c(&page_data[0..page_size - 4])
211}
212
213pub fn calculate_crc32c(page_data: &[u8], page_size: usize) -> u32 {
224 let end = page_size - SIZE_FIL_TRAILER;
225
226 let crc1 = crc32c::crc32c(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
228
229 let crc2 = crc32c::crc32c(&page_data[FIL_PAGE_DATA..end]);
231
232 crc1 ^ crc2
234}
235
236#[inline]
242fn ut_fold_ulint_pair(n1: u32, n2: u32) -> u32 {
243 let step = n1 ^ n2 ^ UT_HASH_RANDOM_MASK2;
244 let step = (step << 8).wrapping_add(n1);
245 let step = step ^ UT_HASH_RANDOM_MASK;
246 step.wrapping_add(n2)
247}
248
249fn ut_fold_binary(data: &[u8]) -> u32 {
255 let mut fold: u32 = 0;
256 for &byte in data {
257 fold = ut_fold_ulint_pair(fold, byte as u32);
258 }
259 fold
260}
261
262pub fn calculate_innodb_checksum(page_data: &[u8], page_size: usize) -> u32 {
269 let end = page_size - SIZE_FIL_TRAILER;
270
271 let fold1 = ut_fold_binary(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
272 let fold2 = ut_fold_binary(&page_data[FIL_PAGE_DATA..end]);
273
274 fold1.wrapping_add(fold2)
275}
276
277pub fn validate_lsn(page_data: &[u8], page_size: u32) -> bool {
305 let ps = page_size as usize;
306 if page_data.len() < ps {
307 return false;
308 }
309 let header_lsn = BigEndian::read_u64(&page_data[FIL_PAGE_LSN..]);
310 let header_lsn_low32 = (header_lsn & 0xFFFFFFFF) as u32;
311
312 let trailer_offset = ps - SIZE_FIL_TRAILER;
313 let trailer_lsn_low32 = BigEndian::read_u32(&page_data[trailer_offset + 4..]);
314
315 header_lsn_low32 == trailer_lsn_low32
316}
317
318pub fn recalculate_checksum(page_data: &mut [u8], page_size: u32, algorithm: ChecksumAlgorithm) {
326 let ps = page_size as usize;
327 if page_data.len() < ps {
328 return;
329 }
330
331 match algorithm {
332 ChecksumAlgorithm::Crc32c => {
333 let checksum = calculate_crc32c(page_data, ps);
334 BigEndian::write_u32(&mut page_data[FIL_PAGE_SPACE_OR_CHKSUM..], checksum);
335 }
336 ChecksumAlgorithm::InnoDB => {
337 let checksum = calculate_innodb_checksum(page_data, ps);
338 BigEndian::write_u32(&mut page_data[FIL_PAGE_SPACE_OR_CHKSUM..], checksum);
339 }
340 ChecksumAlgorithm::MariaDbFullCrc32 => {
341 let checksum = calculate_mariadb_full_crc32(page_data, ps);
342 BigEndian::write_u32(&mut page_data[ps - 4..], checksum);
343 }
344 ChecksumAlgorithm::None => {}
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::innodb::vendor::MariaDbFormat;
352
353 #[test]
354 fn test_all_zero_page_is_valid() {
355 let page = vec![0u8; 16384];
356 let result = validate_checksum(&page, 16384, None);
357 assert!(result.valid);
358 }
359
360 #[test]
361 fn test_no_checksum_magic() {
362 let mut page = vec![0u8; 16384];
363 BigEndian::write_u32(&mut page[0..], 0xDEADBEEF);
364 let result = validate_checksum(&page, 16384, None);
365 assert!(result.valid);
366 assert_eq!(result.algorithm, ChecksumAlgorithm::None);
367 }
368
369 #[test]
370 fn test_mariadb_full_crc32() {
371 let ps = 16384usize;
372 let mut page = vec![0xABu8; ps];
373 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
375 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
376
377 let crc = crc32c::crc32c(&page[0..ps - 4]);
379 BigEndian::write_u32(&mut page[ps - 4..], crc);
380
381 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
382 let result = validate_checksum(&page, ps as u32, Some(&vendor));
383 assert!(result.valid);
384 assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
385 }
386
387 #[test]
388 fn test_mariadb_full_crc32_invalid() {
389 let ps = 16384usize;
390 let mut page = vec![0xABu8; ps];
391 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
392 BigEndian::write_u32(&mut page[ps - 4..], 0xDEADDEAD);
394
395 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
396 let result = validate_checksum(&page, ps as u32, Some(&vendor));
397 assert!(!result.valid);
398 assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
399 }
400
401 #[test]
402 fn test_lsn_validation_matching() {
403 let mut page = vec![0u8; 16384];
404 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
405 BigEndian::write_u32(&mut page[16380..], 0x12345678);
406 assert!(validate_lsn(&page, 16384));
407 }
408
409 #[test]
410 fn test_lsn_validation_mismatch() {
411 let mut page = vec![0u8; 16384];
412 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
413 BigEndian::write_u32(&mut page[16380..], 0xAAAAAAAA);
414 assert!(!validate_lsn(&page, 16384));
415 }
416
417 #[test]
418 fn test_recalculate_checksum_crc32c() {
419 let ps = 16384usize;
420 let mut page = vec![0u8; ps];
421 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
422 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 5000);
423 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
424 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], 1);
425 let trailer = ps - SIZE_FIL_TRAILER;
426 BigEndian::write_u32(&mut page[trailer + 4..], 5000);
427
428 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_OR_CHKSUM..], 0xDEAD);
430 let result = validate_checksum(&page, ps as u32, None);
431 assert!(!result.valid);
432
433 recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::Crc32c);
435 let result = validate_checksum(&page, ps as u32, None);
436 assert!(result.valid);
437 assert_eq!(result.algorithm, ChecksumAlgorithm::Crc32c);
438 }
439
440 #[test]
441 fn test_recalculate_checksum_innodb() {
442 let ps = 16384usize;
443 let mut page = vec![0u8; ps];
444 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
445 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 5000);
446 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
447 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], 1);
448 let trailer = ps - SIZE_FIL_TRAILER;
449 BigEndian::write_u32(&mut page[trailer + 4..], 5000);
450
451 recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::InnoDB);
452 let result = validate_checksum(&page, ps as u32, None);
453 assert!(result.valid);
454 assert_eq!(result.algorithm, ChecksumAlgorithm::InnoDB);
455 }
456
457 #[test]
458 fn test_recalculate_checksum_mariadb() {
459 let ps = 16384usize;
460 let mut page = vec![0xABu8; ps];
461 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
462 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
463
464 recalculate_checksum(&mut page, ps as u32, ChecksumAlgorithm::MariaDbFullCrc32);
465 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
466 let result = validate_checksum(&page, ps as u32, Some(&vendor));
467 assert!(result.valid);
468 assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
469 }
470}