const MOD: u64 = 0xFFFF_FFFF;
fn accumulate(data: &[u8], mut sum1: u64, mut sum2: u64) -> (u64, u64) {
let n = data.len() / 4;
for i in 0..n {
let off = i * 4;
let w = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as u64;
sum1 = (sum1 + w) % MOD;
sum2 = (sum2 + sum1) % MOD;
}
(sum1, sum2)
}
pub fn fletcher64_payload(payload: &[u8]) -> u64 {
let (sum1, sum2) = accumulate(payload, 0, 0);
let c1 = MOD - ((sum1 + sum2) % MOD);
let c2 = MOD - ((sum1 + c1) % MOD);
(c2 << 32) | c1
}
pub fn fletcher64(block: &[u8]) -> u64 {
if block.len() < 8 {
return fletcher64_payload(&[]);
}
fletcher64_payload(&block[8..])
}
pub fn verify(block: &[u8]) -> bool {
if block.len() < 8 || block.len() % 4 != 0 {
return false;
}
let cksum = u64::from_le_bytes(block[0..8].try_into().unwrap());
if cksum == 0 || cksum == u64::MAX {
return false;
}
let (sum1, sum2) = accumulate(&block[8..], 0, 0);
let (sum1, sum2) = accumulate(&block[..8], sum1, sum2);
sum1 == 0 && sum2 == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_zero_payload() {
let mut block = [0u8; 64];
for (i, b) in block.iter_mut().enumerate().skip(8) {
*b = (i as u8).wrapping_mul(7);
}
let cksum = fletcher64(&block);
block[..8].copy_from_slice(&cksum.to_le_bytes());
assert!(verify(&block));
}
#[test]
fn flipping_a_byte_breaks_verification() {
let mut block = [0u8; 64];
for (i, b) in block.iter_mut().enumerate().skip(8) {
*b = i as u8;
}
let cksum = fletcher64(&block);
block[..8].copy_from_slice(&cksum.to_le_bytes());
assert!(verify(&block));
block[20] ^= 0x01;
assert!(!verify(&block));
}
#[test]
fn known_pattern_is_self_consistent() {
let mut block = [0u8; 16];
block[8..12].copy_from_slice(&0x1234_5678u32.to_le_bytes());
block[12..16].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
let cksum = fletcher64(&block);
block[..8].copy_from_slice(&cksum.to_le_bytes());
assert!(verify(&block));
}
#[test]
fn empty_and_short_inputs() {
assert_eq!(fletcher64_payload(&[]), u64::MAX);
assert!(!verify(&[]));
assert!(!verify(&[0u8; 7]));
let mut b = [0u8; 8];
b.copy_from_slice(&u64::MAX.to_le_bytes());
assert!(!verify(&b));
}
}