Nothing to see here, move along
at main 434 lines 12 kB view raw
1use crate::block_io::BlockIo; 2use crate::cache::BlockCache; 3use crate::ditto::{DittoRegion, HealResult}; 4use crate::error::IntegrityError; 5use lancer_core::fs::{BTREE_NODE_SIZE, BTreeNode, BlockRef}; 6 7pub struct BlockHashes { 8 pub content_hash: u128, 9 pub integrity_crc: u32, 10} 11 12pub fn xxhash128(data: &[u8]) -> u128 { 13 xxhash_rust::xxh3::xxh3_128(data) 14} 15 16pub fn xxhash64(data: &[u8]) -> u64 { 17 xxhash_rust::xxh3::xxh3_64(data) 18} 19 20pub fn compute_block_hashes(data: &[u8]) -> BlockHashes { 21 BlockHashes { 22 content_hash: xxhash128(data), 23 integrity_crc: crc32c(data), 24 } 25} 26 27pub fn verify_block_crc(data: &[u8], expected_crc: u32) -> bool { 28 crc32c(data) == expected_crc 29} 30 31pub fn verify_block_hash(data: &[u8], expected_hash: u128) -> bool { 32 xxhash128(data) == expected_hash 33} 34 35const CRC32C_TABLE: [u32; 256] = generate_crc32c_table(); 36 37const fn generate_crc32c_table() -> [u32; 256] { 38 let mut table = [0u32; 256]; 39 let mut i = 0u32; 40 while i < 256 { 41 let mut crc = i; 42 let mut j = 0; 43 while j < 8 { 44 crc = match crc & 1 { 45 1 => (crc >> 1) ^ 0x82F6_3B78, 46 _ => crc >> 1, 47 }; 48 j += 1; 49 } 50 table[i as usize] = crc; 51 i += 1; 52 } 53 table 54} 55 56pub fn crc32c_sw(data: &[u8]) -> u32 { 57 !data.iter().fold(!0u32, |crc, &byte| { 58 (crc >> 8) ^ CRC32C_TABLE[((crc ^ byte as u32) & 0xFF) as usize] 59 }) 60} 61 62#[cfg(target_arch = "x86_64")] 63#[target_feature(enable = "sse4.2")] 64unsafe fn crc32c_sse42(data: &[u8]) -> u32 { 65 use core::arch::x86_64::{_mm_crc32_u8, _mm_crc32_u64}; 66 67 let chunk_count = data.len() / 8; 68 let tail_start = chunk_count * 8; 69 70 let crc_after_chunks = (0..chunk_count).fold(!0u64, |crc, i| { 71 debug_assert!(i * 8 + 8 <= data.len()); 72 let chunk = unsafe { core::ptr::read_unaligned(data.as_ptr().add(i * 8) as *const u64) }; 73 _mm_crc32_u64(crc, chunk) 74 }); 75 76 let crc_final = data[tail_start..] 77 .iter() 78 .fold(crc_after_chunks as u32, |crc, &byte| { 79 _mm_crc32_u8(crc, byte) 80 }); 81 82 !crc_final 83} 84 85#[cfg(target_arch = "x86_64")] 86#[allow(unused_unsafe)] 87fn sse42_available() -> bool { 88 use core::sync::atomic::{AtomicU8, Ordering}; 89 static STATUS: AtomicU8 = AtomicU8::new(0); 90 match STATUS.load(Ordering::Acquire) { 91 0 => { 92 let cpuid = unsafe { core::arch::x86_64::__cpuid(1) }; 93 let available = (cpuid.ecx & (1 << 20)) != 0; 94 STATUS.store( 95 match available { 96 true => 2, 97 false => 1, 98 }, 99 Ordering::Release, 100 ); 101 available 102 } 103 2 => true, 104 _ => false, 105 } 106} 107 108#[cfg(target_arch = "x86_64")] 109pub fn crc32c(data: &[u8]) -> u32 { 110 match sse42_available() { 111 true => unsafe { crc32c_sse42(data) }, 112 false => crc32c_sw(data), 113 } 114} 115 116#[cfg(not(target_arch = "x86_64"))] 117pub fn crc32c(data: &[u8]) -> u32 { 118 crc32c_sw(data) 119} 120 121const VERIFY_MAX_DEPTH: usize = 5; 122 123enum VerifyWalk { 124 Verified, 125 Error(IntegrityError), 126} 127 128pub fn verify_path( 129 cache: &mut BlockCache, 130 bio: &mut BlockIo, 131 tree_root: &BlockRef, 132 target_key: u64, 133) -> Result<(), IntegrityError> { 134 if tree_root.is_null() { 135 return Err(IntegrityError::TreeCorrupt); 136 } 137 138 let result = (0..VERIFY_MAX_DEPTH + 1).try_fold(*tree_root, |current_ref, _| { 139 let block_num = crate::blockref_block_num(&current_ref); 140 let data = cache 141 .cache_read(bio, block_num) 142 .map_err(|e| VerifyWalk::Error(IntegrityError::Io(e)))?; 143 144 let actual_crc = crc32c(data); 145 match actual_crc == current_ref.integrity_crc { 146 true => {} 147 false => { 148 return Err(VerifyWalk::Error(IntegrityError::CrcMismatch { block_num })); 149 } 150 } 151 152 let actual_hash = xxhash128(data); 153 match actual_hash == current_ref.content_hash_u128() { 154 true => {} 155 false => { 156 return Err(VerifyWalk::Error(IntegrityError::HashMismatch { 157 block_num, 158 })); 159 } 160 } 161 162 let node = read_btree_node(data); 163 match node.is_valid() { 164 true => {} 165 false => return Err(VerifyWalk::Error(IntegrityError::TreeCorrupt)), 166 } 167 168 match node.is_leaf() { 169 true => Err(VerifyWalk::Verified), 170 false => { 171 let child_idx = node.find_child_index(target_key); 172 match child_idx < node.entry_count() { 173 true => Ok(node.entries[child_idx]), 174 false => Err(VerifyWalk::Error(IntegrityError::KeyNotInTree)), 175 } 176 } 177 } 178 }); 179 180 match result { 181 Err(VerifyWalk::Verified) => Ok(()), 182 Err(VerifyWalk::Error(e)) => Err(e), 183 Ok(_) => Err(IntegrityError::TreeCorrupt), 184 } 185} 186 187fn read_btree_node(data: &[u8; BTREE_NODE_SIZE]) -> BTreeNode { 188 unsafe { core::ptr::read_unaligned(data.as_ptr() as *const BTreeNode) } 189} 190 191pub fn metadata_read_healing( 192 cache: &mut BlockCache, 193 bio: &mut BlockIo, 194 ditto: &DittoRegion, 195 block_num: u64, 196 expected_crc: u32, 197) -> Result<ReadHealOutcome, IntegrityError> { 198 let data = cache 199 .cache_read(bio, block_num) 200 .map_err(IntegrityError::Io)?; 201 202 let actual_crc = crc32c(data); 203 match actual_crc == expected_crc { 204 true => Ok(ReadHealOutcome::Clean), 205 false => match ditto.try_heal(bio, block_num, expected_crc) { 206 Ok(HealResult::Repaired(repaired_data)) => { 207 cache 208 .cache_write(bio, block_num, &repaired_data) 209 .map_err(IntegrityError::Io)?; 210 Ok(ReadHealOutcome::Repaired) 211 } 212 Ok(HealResult::BothCorrupt) => Err(IntegrityError::CrcMismatch { block_num }), 213 Err(e) => Err(IntegrityError::Io(e)), 214 }, 215 } 216} 217 218#[derive(Debug)] 219pub enum ReadHealOutcome { 220 Clean, 221 Repaired, 222} 223 224pub fn verify_and_heal_block( 225 bio: &mut BlockIo, 226 ditto: &DittoRegion, 227 block_num: u64, 228 data: &[u8], 229 expected_crc: u32, 230 is_metadata: bool, 231) -> Result<VerifyHealResult, IntegrityError> { 232 let actual_crc = crc32c(data); 233 match actual_crc == expected_crc { 234 true => Ok(VerifyHealResult::Ok), 235 false => match is_metadata { 236 true => match ditto.try_heal(bio, block_num, expected_crc) { 237 Ok(HealResult::Repaired(_)) => Ok(VerifyHealResult::HealedFromDitto), 238 Ok(HealResult::BothCorrupt) => Err(IntegrityError::CrcMismatch { block_num }), 239 Err(e) => Err(IntegrityError::Io(e)), 240 }, 241 false => Err(IntegrityError::CrcMismatch { block_num }), 242 }, 243 } 244} 245 246pub enum VerifyHealResult { 247 Ok, 248 HealedFromDitto, 249} 250 251#[cfg(test)] 252mod tests { 253 use super::*; 254 255 #[test] 256 fn crc32c_empty_input() { 257 assert_eq!(crc32c_sw(&[]), 0x0000_0000); 258 assert_eq!(crc32c(&[]), 0x0000_0000); 259 } 260 261 #[test] 262 fn crc32c_known_vector_iscsi() { 263 let data = b"123456789"; 264 assert_eq!(crc32c_sw(data), 0xE306_9283); 265 } 266 267 #[test] 268 fn crc32c_sw_matches_dispatch() { 269 let patterns: &[&[u8]] = &[ 270 b"", 271 b"\x00", 272 b"hello world", 273 b"123456789", 274 &[0xFF; 4096], 275 &(0..=255).collect::<Vec<u8>>(), 276 ]; 277 patterns.iter().for_each(|data| { 278 assert_eq!( 279 crc32c(data), 280 crc32c_sw(data), 281 "mismatch for data len {}", 282 data.len() 283 ); 284 }); 285 } 286 287 #[test] 288 fn crc32c_single_byte_variations() { 289 let results: Vec<u32> = (0u8..=255).map(|b| crc32c_sw(&[b])).collect(); 290 let unique_count = { 291 let mut sorted = results.clone(); 292 sorted.sort(); 293 sorted.dedup(); 294 sorted.len() 295 }; 296 assert_eq!(unique_count, 256); 297 } 298 299 #[test] 300 fn crc32c_different_data_different_crcs() { 301 let a = crc32c(b"block A content"); 302 let b = crc32c(b"block B content"); 303 assert_ne!(a, b); 304 } 305 306 #[test] 307 fn crc32c_full_block_sw_matches_dispatch() { 308 let block = [0xAB_u8; 4096]; 309 assert_eq!(crc32c(&block), crc32c_sw(&block)); 310 } 311 312 #[test] 313 fn crc32c_alignment_sizes() { 314 [1, 7, 8, 9, 15, 16, 17, 63, 64, 65, 256, 4096] 315 .iter() 316 .for_each(|&size| { 317 let data: Vec<u8> = (0..size).map(|i| (i & 0xFF) as u8).collect(); 318 assert_eq!(crc32c(&data), crc32c_sw(&data), "mismatch at size {}", size); 319 }); 320 } 321 322 #[test] 323 fn xxhash128_deterministic() { 324 let data = b"some block data"; 325 assert_eq!(xxhash128(data), xxhash128(data)); 326 } 327 328 #[test] 329 fn xxhash128_different_data_different_hashes() { 330 assert_ne!(xxhash128(b"aaa"), xxhash128(b"bbb")); 331 } 332 333 #[test] 334 fn xxhash64_deterministic() { 335 let data = b"snapshot name"; 336 assert_eq!(xxhash64(data), xxhash64(data)); 337 } 338 339 #[test] 340 fn xxhash64_different_data_different_hashes() { 341 assert_ne!(xxhash64(b"snap1"), xxhash64(b"snap2")); 342 } 343 344 #[test] 345 fn compute_block_hashes_consistent() { 346 let data = [0x42u8; 4096]; 347 let h = compute_block_hashes(&data); 348 assert_eq!(h.integrity_crc, crc32c(&data)); 349 assert_eq!(h.content_hash, xxhash128(&data)); 350 } 351 352 #[test] 353 fn verify_block_crc_pass() { 354 let data = [0xDE_u8; 4096]; 355 let crc = crc32c(&data); 356 assert!(verify_block_crc(&data, crc)); 357 } 358 359 #[test] 360 fn verify_block_crc_fail() { 361 let data = [0xDE_u8; 4096]; 362 assert!(!verify_block_crc(&data, 0xDEAD_BEEF)); 363 } 364 365 #[test] 366 fn verify_block_hash_pass() { 367 let data = [0x11_u8; 4096]; 368 let hash = xxhash128(&data); 369 assert!(verify_block_hash(&data, hash)); 370 } 371 372 #[test] 373 fn verify_block_hash_fail() { 374 let data = [0x11_u8; 4096]; 375 assert!(!verify_block_hash(&data, 0xBAD)); 376 } 377 378 #[test] 379 fn metadata_read_healing_clean_block() { 380 let mut bio = crate::test_helpers::make_bio(256); 381 let mut cache = crate::test_helpers::make_cache(); 382 let ditto = DittoRegion::init(10, 200); 383 384 let block_data = [0xAA_u8; 4096]; 385 let crc = crc32c(&block_data); 386 bio.write_blocks(50, 1, &block_data).unwrap(); 387 388 match metadata_read_healing(&mut cache, &mut bio, &ditto, 50, crc) { 389 Ok(ReadHealOutcome::Clean) => {} 390 other => panic!("expected Clean, got {:?}", other), 391 } 392 } 393 394 #[test] 395 fn metadata_read_healing_repairs_from_ditto() { 396 let mut bio = crate::test_helpers::make_bio(256); 397 let mut cache = crate::test_helpers::make_cache(); 398 let ditto = DittoRegion::init(10, 200); 399 400 let good_data = [0xBB_u8; 4096]; 401 let good_crc = crc32c(&good_data); 402 403 let corrupt_data = [0xCC_u8; 4096]; 404 bio.write_blocks(50, 1, &corrupt_data).unwrap(); 405 406 let ditto_block = ditto.ditto_addr(50).unwrap(); 407 bio.write_blocks(ditto_block, 1, &good_data).unwrap(); 408 409 match metadata_read_healing(&mut cache, &mut bio, &ditto, 50, good_crc) { 410 Ok(ReadHealOutcome::Repaired) => {} 411 other => panic!("expected Repaired, got {:?}", other), 412 } 413 } 414 415 #[test] 416 fn metadata_read_healing_both_corrupt() { 417 let mut bio = crate::test_helpers::make_bio(256); 418 let mut cache = crate::test_helpers::make_cache(); 419 let ditto = DittoRegion::init(10, 200); 420 421 let corrupt = [0xDD_u8; 4096]; 422 bio.write_blocks(50, 1, &corrupt).unwrap(); 423 424 let ditto_block = ditto.ditto_addr(50).unwrap(); 425 let also_corrupt = [0xEE_u8; 4096]; 426 bio.write_blocks(ditto_block, 1, &also_corrupt).unwrap(); 427 428 let expected_crc = crc32c(&[0xFF_u8; 4096]); 429 match metadata_read_healing(&mut cache, &mut bio, &ditto, 50, expected_crc) { 430 Err(IntegrityError::CrcMismatch { block_num: 50 }) => {} 431 other => panic!("expected CrcMismatch, got {:?}", other), 432 } 433 } 434}