const MOD: u32 = 65_521;
const NMAX: usize = 5552;
pub const ADLER32_INIT: u32 = 1;
#[must_use]
pub fn adler32(data: &[u8]) -> u32 {
adler32_update(ADLER32_INIT, data)
}
#[must_use]
pub fn adler32_update(adler: u32, data: &[u8]) -> u32 {
let mut a = adler & 0xffff;
let mut b = adler >> 16;
for block in data.chunks(NMAX) {
let (quads, tail) = block.as_chunks::<4>();
for q in quads {
let [q0, q1, q2, q3] = q.map(u32::from);
b = b
.wrapping_add(a.wrapping_mul(4))
.wrapping_add(q0.wrapping_mul(4))
.wrapping_add(q1.wrapping_mul(3))
.wrapping_add(q2.wrapping_mul(2))
.wrapping_add(q3);
a = a
.wrapping_add(q0)
.wrapping_add(q1)
.wrapping_add(q2)
.wrapping_add(q3);
}
for &byte in tail {
a = a.wrapping_add(u32::from(byte));
b = b.wrapping_add(a);
}
a %= MOD;
b %= MOD;
}
(b << 16) | a
}
#[must_use]
pub fn adler32_combine(adler1: u32, adler2: u32, len2: u64) -> u32 {
const M: u64 = MOD as u64;
let rem = len2 % M;
let a1 = u64::from(adler1 & 0xffff) % M;
let b1 = u64::from(adler1 >> 16) % M;
let a2 = u64::from(adler2 & 0xffff) % M;
let b2 = u64::from(adler2 >> 16) % M;
let a = a1.wrapping_add(a2).wrapping_add(M.wrapping_sub(1)) % M;
let b = b1
.wrapping_add(b2)
.wrapping_add(rem.wrapping_mul(a1) % M)
.wrapping_add(M)
.wrapping_sub(rem)
% M;
let a = u32::try_from(a).unwrap_or(0);
let b = u32::try_from(b).unwrap_or(0);
(b << 16) | a
}
#[cfg(test)]
#[allow(clippy::arithmetic_side_effects)]
mod tests {
use super::*;
fn reference(data: &[u8]) -> u32 {
let (mut a, mut b) = (1u32, 0u32);
for &byte in data {
a = (a + u32::from(byte)) % MOD;
b = (b + a) % MOD;
}
(b << 16) | a
}
fn noise(len: usize, mut seed: u64) -> Vec<u8> {
(0..len)
.map(|_| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed as u8
})
.collect()
}
#[test]
fn known_values() {
assert_eq!(adler32(b""), 1);
assert_eq!(adler32(b"a"), 0x0062_0062);
assert_eq!(adler32(b"abc"), 0x024d_0127);
assert_eq!(adler32(b"Wikipedia"), 0x11e6_0398);
}
#[test]
fn matches_reference_across_block_boundaries() {
for len in [0, 1, 3, 4, 5, 5551, 5552, 5553, 11_104, 70_001] {
let data = noise(len, 0x9e37_79b9_7f4a_7c15 ^ len as u64);
assert_eq!(adler32(&data), reference(&data), "len {len}");
let ones = vec![0xffu8; len];
assert_eq!(adler32(&ones), reference(&ones), "0xff len {len}");
}
}
#[test]
fn update_is_incremental() {
let data = noise(20_000, 7);
let (x, y) = data.split_at(7_777);
assert_eq!(adler32_update(adler32(x), y), adler32(&data));
}
#[test]
fn combine_matches_concatenation() {
let data = noise(200_000, 99);
for split in [0, 1, 2, 65_520, 65_521, 65_522, 131_042, 199_999, 200_000] {
let (x, y) = data.split_at(split);
assert_eq!(
adler32_combine(adler32(x), adler32(y), y.len() as u64),
adler32(&data),
"split {split}"
);
}
let ones = vec![0xffu8; 300_000];
for split in [0, 65_521, 100_000, 300_000] {
let (x, y) = ones.split_at(split);
assert_eq!(
adler32_combine(adler32(x), adler32(y), y.len() as u64),
adler32(&ones),
"0xff split {split}"
);
}
}
}