const PRIME64_1: u64 = 0x9E37_79B1_85EB_CA87;
const PRIME64_2: u64 = 0xC2B2_AE3D_27D4_EB4F;
const PRIME64_3: u64 = 0x1656_67B1_9E37_79F9;
const PRIME64_4: u64 = 0x85EB_CA77_C2B2_AE63;
const PRIME64_5: u64 = 0x27D4_EB2F_1656_67C5;
#[derive(Clone)]
pub(crate) struct Xxh64 {
acc: [u64; 4],
total_len: u64,
buf: [u8; 32],
buf_len: usize,
}
impl Xxh64 {
pub(crate) fn new() -> Self {
Self {
acc: [
PRIME64_1.wrapping_add(PRIME64_2),
PRIME64_2,
0,
0u64.wrapping_sub(PRIME64_1),
],
total_len: 0,
buf: [0u8; 32],
buf_len: 0,
}
}
#[inline]
fn round(acc: u64, lane: u64) -> u64 {
acc.wrapping_add(lane.wrapping_mul(PRIME64_2))
.rotate_left(31)
.wrapping_mul(PRIME64_1)
}
#[inline]
fn merge_round(acc: u64, lane: u64) -> u64 {
let acc = acc ^ Self::round(0, lane);
acc.wrapping_mul(PRIME64_1).wrapping_add(PRIME64_4)
}
#[inline]
fn read_u64(b: &[u8]) -> u64 {
u64::from_le_bytes(b[..8].try_into().unwrap())
}
#[inline]
fn process_stripe(acc: &mut [u64; 4], stripe: &[u8]) {
acc[0] = Self::round(acc[0], Self::read_u64(&stripe[0..8]));
acc[1] = Self::round(acc[1], Self::read_u64(&stripe[8..16]));
acc[2] = Self::round(acc[2], Self::read_u64(&stripe[16..24]));
acc[3] = Self::round(acc[3], Self::read_u64(&stripe[24..32]));
}
pub(crate) fn update(&mut self, mut data: &[u8]) {
self.total_len = self.total_len.wrapping_add(data.len() as u64);
if self.buf_len > 0 {
let need = 32 - self.buf_len;
if data.len() < need {
self.buf[self.buf_len..self.buf_len + data.len()].copy_from_slice(data);
self.buf_len += data.len();
return;
}
let (head, rest) = data.split_at(need);
self.buf[self.buf_len..].copy_from_slice(head);
let buf = self.buf;
Self::process_stripe(&mut self.acc, &buf);
self.buf_len = 0;
data = rest;
}
let mut chunks = data.chunks_exact(32);
for stripe in &mut chunks {
Self::process_stripe(&mut self.acc, stripe);
}
let rem = chunks.remainder();
if !rem.is_empty() {
self.buf[..rem.len()].copy_from_slice(rem);
self.buf_len = rem.len();
}
}
pub(crate) fn digest(&self) -> u64 {
let mut h = if self.total_len >= 32 {
let mut h = self.acc[0]
.rotate_left(1)
.wrapping_add(self.acc[1].rotate_left(7))
.wrapping_add(self.acc[2].rotate_left(12))
.wrapping_add(self.acc[3].rotate_left(18));
h = Self::merge_round(h, self.acc[0]);
h = Self::merge_round(h, self.acc[1]);
h = Self::merge_round(h, self.acc[2]);
h = Self::merge_round(h, self.acc[3]);
h
} else {
PRIME64_5
};
h = h.wrapping_add(self.total_len);
let mut p = &self.buf[..self.buf_len];
while p.len() >= 8 {
let k1 = Self::round(0, Self::read_u64(p));
h = (h ^ k1)
.rotate_left(27)
.wrapping_mul(PRIME64_1)
.wrapping_add(PRIME64_4);
p = &p[8..];
}
if p.len() >= 4 {
let k = u32::from_le_bytes(p[..4].try_into().unwrap()) as u64;
h = (h ^ k.wrapping_mul(PRIME64_1))
.rotate_left(23)
.wrapping_mul(PRIME64_2)
.wrapping_add(PRIME64_3);
p = &p[4..];
}
for &b in p {
h = (h ^ (b as u64).wrapping_mul(PRIME64_5))
.rotate_left(11)
.wrapping_mul(PRIME64_1);
}
h ^= h >> 33;
h = h.wrapping_mul(PRIME64_2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME64_3);
h ^= h >> 32;
h
}
}
#[cfg(test)]
mod tests {
use super::*;
fn xxh64(data: &[u8]) -> u64 {
let mut h = Xxh64::new();
h.update(data);
h.digest()
}
#[test]
fn reference_vectors() {
assert_eq!(xxh64(b""), 0xEF46_DB37_51D8_E999);
assert_eq!(xxh64(b"a"), 0xD24E_C4F1_A98C_6E5B);
assert_eq!(xxh64(b"abc"), 0x44BC_2CF5_AD77_0999);
let long: alloc::vec::Vec<u8> = (0..64u8).collect();
assert_eq!(xxh64(&long), 0xF7C6_7301_DB67_13F0);
}
#[test]
fn streaming_matches_one_shot() {
let data: alloc::vec::Vec<u8> = (0..250u32).map(|i| (i.wrapping_mul(37)) as u8).collect();
let one = xxh64(&data);
for chunk in [1usize, 3, 7, 8, 16, 31, 32, 33] {
let mut h = Xxh64::new();
for part in data.chunks(chunk) {
h.update(part);
}
assert_eq!(h.digest(), one, "chunk size {chunk}");
}
}
}