const M: u32 = 1 << 16;
pub const STRONG_LEN: usize = 16;
pub type StrongHash = [u8; STRONG_LEN];
#[must_use]
pub fn strong_hash(data: &[u8]) -> StrongHash {
let digest = blake3::hash(data);
let mut out = [0u8; STRONG_LEN];
out.copy_from_slice(&digest.as_bytes()[..STRONG_LEN]);
out
}
#[derive(Debug, Clone, Copy)]
pub struct RollingChecksum {
a: u32,
b: u32,
window_len: u32,
}
impl RollingChecksum {
#[must_use]
pub fn new(window: &[u8]) -> Self {
let len = window.len();
let window_len = u32::try_from(len).unwrap_or(u32::MAX);
let mut a: u32 = 0;
let mut b: u32 = 0;
let chunks = len / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let w = window_len.wrapping_sub(base as u32);
let b0 = u32::from(window[base]);
let b1 = u32::from(window[base + 1]);
let b2 = u32::from(window[base + 2]);
let b3 = u32::from(window[base + 3]);
let b4 = u32::from(window[base + 4]);
let b5 = u32::from(window[base + 5]);
let b6 = u32::from(window[base + 6]);
let b7 = u32::from(window[base + 7]);
a = a.wrapping_add(
b0.wrapping_add(b1)
.wrapping_add(b2)
.wrapping_add(b3)
.wrapping_add(b4)
.wrapping_add(b5)
.wrapping_add(b6)
.wrapping_add(b7),
);
let sum_bytes = b0
.wrapping_add(b1)
.wrapping_add(b2)
.wrapping_add(b3)
.wrapping_add(b4)
.wrapping_add(b5)
.wrapping_add(b6)
.wrapping_add(b7);
let weighted = b1
.wrapping_add(b2.wrapping_mul(2))
.wrapping_add(b3.wrapping_mul(3))
.wrapping_add(b4.wrapping_mul(4))
.wrapping_add(b5.wrapping_mul(5))
.wrapping_add(b6.wrapping_mul(6))
.wrapping_add(b7.wrapping_mul(7));
b = b.wrapping_add(w.wrapping_mul(sum_bytes).wrapping_sub(weighted));
}
let remainder_start = chunks * 8;
for i in remainder_start..len {
let k = i;
let weight = window_len.wrapping_sub(u32::try_from(k).unwrap_or(0));
let byte = u32::from(window[i]);
a = a.wrapping_add(byte);
b = b.wrapping_add(weight.wrapping_mul(byte));
}
Self { a, b, window_len }
}
pub fn roll(&mut self, out_byte: u8, in_byte: u8) {
let out = u32::from(out_byte);
let in_ = u32::from(in_byte);
self.a = self.a.wrapping_sub(out).wrapping_add(in_);
self.b = self
.b
.wrapping_sub(self.window_len.wrapping_mul(out))
.wrapping_add(self.a);
}
#[must_use]
pub fn value(&self) -> u32 {
(self.a & (M - 1)) | ((self.b & (M - 1)) << 16)
}
#[must_use]
pub fn window_len(&self) -> u32 {
self.window_len
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_matches_manual() {
let rc = RollingChecksum::new(&[1, 2, 3]);
assert_eq!(rc.value(), 6 + 10 * M);
}
#[test]
fn roll_equals_recompute() {
let data = b"the quick brown fox jumps over the lazy dog";
let w = 7usize;
let mut rc = RollingChecksum::new(&data[..w]);
for i in 1..=(data.len() - w) {
rc.roll(data[i - 1], data[i + w - 1]);
let fresh = RollingChecksum::new(&data[i..i + w]);
assert_eq!(rc.value(), fresh.value(), "mismatch at offset {i}");
}
}
#[test]
fn strong_hash_is_stable_and_16_bytes() {
let h1 = strong_hash(b"hello");
let h2 = strong_hash(b"hello");
let h3 = strong_hash(b"world");
assert_eq!(h1, h2);
assert_ne!(h1, h3);
assert_eq!(h1.len(), 16);
}
}