1use crate::innodb::constants::*;
19use crate::innodb::vendor::VendorInfo;
20use byteorder::{BigEndian, ByteOrder};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ChecksumAlgorithm {
25 Crc32c,
27 InnoDB,
29 MariaDbFullCrc32,
31 None,
33}
34
35pub fn validate_checksum(
41 page_data: &[u8],
42 page_size: u32,
43 vendor_info: Option<&VendorInfo>,
44) -> ChecksumResult {
45 let ps = page_size as usize;
46 if page_data.len() < ps {
47 return ChecksumResult {
48 algorithm: ChecksumAlgorithm::None,
49 valid: false,
50 stored_checksum: 0,
51 calculated_checksum: 0,
52 };
53 }
54
55 let first_u32 = BigEndian::read_u32(&page_data[FIL_PAGE_SPACE_OR_CHKSUM..]);
57 if first_u32 == 0 {
58 let all_zero = page_data[..ps].iter().all(|&b| b == 0);
59 if all_zero {
60 return ChecksumResult {
61 algorithm: ChecksumAlgorithm::None,
62 valid: true,
63 stored_checksum: 0,
64 calculated_checksum: 0,
65 };
66 }
67 }
68
69 if vendor_info.is_some_and(|v| v.is_full_crc32()) {
71 let stored = BigEndian::read_u32(&page_data[ps - 4..ps]);
72 let calculated = calculate_mariadb_full_crc32(page_data, ps);
73 if stored == calculated {
74 return ChecksumResult {
75 algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
76 valid: true,
77 stored_checksum: stored,
78 calculated_checksum: calculated,
79 };
80 }
81 return ChecksumResult {
83 algorithm: ChecksumAlgorithm::MariaDbFullCrc32,
84 valid: false,
85 stored_checksum: stored,
86 calculated_checksum: calculated,
87 };
88 }
89
90 let stored_checksum = first_u32;
91
92 if stored_checksum == 0xDEADBEEF {
94 return ChecksumResult {
95 algorithm: ChecksumAlgorithm::None,
96 valid: true,
97 stored_checksum,
98 calculated_checksum: 0xDEADBEEF,
99 };
100 }
101
102 let crc_checksum = calculate_crc32c(page_data, ps);
104 if stored_checksum == crc_checksum {
105 return ChecksumResult {
106 algorithm: ChecksumAlgorithm::Crc32c,
107 valid: true,
108 stored_checksum,
109 calculated_checksum: crc_checksum,
110 };
111 }
112
113 let innodb_checksum = calculate_innodb_checksum(page_data, ps);
115 if stored_checksum == innodb_checksum {
116 return ChecksumResult {
117 algorithm: ChecksumAlgorithm::InnoDB,
118 valid: true,
119 stored_checksum,
120 calculated_checksum: innodb_checksum,
121 };
122 }
123
124 ChecksumResult {
126 algorithm: ChecksumAlgorithm::Crc32c,
127 valid: false,
128 stored_checksum,
129 calculated_checksum: crc_checksum,
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct ChecksumResult {
136 pub algorithm: ChecksumAlgorithm,
138 pub valid: bool,
140 pub stored_checksum: u32,
142 pub calculated_checksum: u32,
144}
145
146fn calculate_mariadb_full_crc32(page_data: &[u8], page_size: usize) -> u32 {
152 crc32c::crc32c(&page_data[0..page_size - 4])
153}
154
155fn calculate_crc32c(page_data: &[u8], page_size: usize) -> u32 {
166 let end = page_size - SIZE_FIL_TRAILER;
167
168 let crc1 = crc32c::crc32c(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
170
171 let crc2 = crc32c::crc32c(&page_data[FIL_PAGE_DATA..end]);
173
174 crc1 ^ crc2
176}
177
178#[inline]
184fn ut_fold_ulint_pair(n1: u32, n2: u32) -> u32 {
185 let step = n1 ^ n2 ^ UT_HASH_RANDOM_MASK2;
186 let step = (step << 8).wrapping_add(n1);
187 let step = step ^ UT_HASH_RANDOM_MASK;
188 step.wrapping_add(n2)
189}
190
191fn ut_fold_binary(data: &[u8]) -> u32 {
197 let mut fold: u32 = 0;
198 for &byte in data {
199 fold = ut_fold_ulint_pair(fold, byte as u32);
200 }
201 fold
202}
203
204fn calculate_innodb_checksum(page_data: &[u8], page_size: usize) -> u32 {
211 let end = page_size - SIZE_FIL_TRAILER;
212
213 let fold1 = ut_fold_binary(&page_data[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
214 let fold2 = ut_fold_binary(&page_data[FIL_PAGE_DATA..end]);
215
216 fold1.wrapping_add(fold2)
217}
218
219pub fn validate_lsn(page_data: &[u8], page_size: u32) -> bool {
223 let ps = page_size as usize;
224 if page_data.len() < ps {
225 return false;
226 }
227 let header_lsn = BigEndian::read_u64(&page_data[FIL_PAGE_LSN..]);
228 let header_lsn_low32 = (header_lsn & 0xFFFFFFFF) as u32;
229
230 let trailer_offset = ps - SIZE_FIL_TRAILER;
231 let trailer_lsn_low32 = BigEndian::read_u32(&page_data[trailer_offset + 4..]);
232
233 header_lsn_low32 == trailer_lsn_low32
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::innodb::vendor::MariaDbFormat;
240
241 #[test]
242 fn test_all_zero_page_is_valid() {
243 let page = vec![0u8; 16384];
244 let result = validate_checksum(&page, 16384, None);
245 assert!(result.valid);
246 }
247
248 #[test]
249 fn test_no_checksum_magic() {
250 let mut page = vec![0u8; 16384];
251 BigEndian::write_u32(&mut page[0..], 0xDEADBEEF);
252 let result = validate_checksum(&page, 16384, None);
253 assert!(result.valid);
254 assert_eq!(result.algorithm, ChecksumAlgorithm::None);
255 }
256
257 #[test]
258 fn test_mariadb_full_crc32() {
259 let ps = 16384usize;
260 let mut page = vec![0xABu8; ps];
261 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
263 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
264
265 let crc = crc32c::crc32c(&page[0..ps - 4]);
267 BigEndian::write_u32(&mut page[ps - 4..], crc);
268
269 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
270 let result = validate_checksum(&page, ps as u32, Some(&vendor));
271 assert!(result.valid);
272 assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
273 }
274
275 #[test]
276 fn test_mariadb_full_crc32_invalid() {
277 let ps = 16384usize;
278 let mut page = vec![0xABu8; ps];
279 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 1);
280 BigEndian::write_u32(&mut page[ps - 4..], 0xDEADDEAD);
282
283 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
284 let result = validate_checksum(&page, ps as u32, Some(&vendor));
285 assert!(!result.valid);
286 assert_eq!(result.algorithm, ChecksumAlgorithm::MariaDbFullCrc32);
287 }
288
289 #[test]
290 fn test_lsn_validation_matching() {
291 let mut page = vec![0u8; 16384];
292 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
293 BigEndian::write_u32(&mut page[16380..], 0x12345678);
294 assert!(validate_lsn(&page, 16384));
295 }
296
297 #[test]
298 fn test_lsn_validation_mismatch() {
299 let mut page = vec![0u8; 16384];
300 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 0x12345678);
301 BigEndian::write_u32(&mut page[16380..], 0xAAAAAAAA);
302 assert!(!validate_lsn(&page, 16384));
303 }
304}