const PRIME_1: u64 = 0x9E37_79B1_85EB_CA87;
const PRIME_2: u64 = 0xC2B2_AE3D_27D4_EB4F;
const PRIME_3: u64 = 0x1656_67B1_9E37_79F9;
const PRIME_4: u64 = 0x85EB_CA77_C2B2_AE63;
const PRIME_5: u64 = 0x27D4_EB2F_1656_67C5;
const STRIPE: usize = 32;
pub(super) struct Xxh64 {
seed: u64,
accumulators: [u64; 4],
buffer: [u8; STRIPE],
buffered: usize,
total: u64,
}
impl Xxh64 {
pub(super) fn new(seed: u64) -> Self {
Self {
seed,
accumulators: [
seed.wrapping_add(PRIME_1).wrapping_add(PRIME_2),
seed.wrapping_add(PRIME_2),
seed,
seed.wrapping_sub(PRIME_1),
],
buffer: [0; STRIPE],
buffered: 0,
total: 0,
}
}
pub(super) fn update(&mut self, mut data: &[u8]) {
self.total = self.total.wrapping_add(data.len() as u64);
if self.buffered > 0 {
let take = (STRIPE - self.buffered).min(data.len());
self.buffer[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
self.buffered += take;
data = &data[take..];
if self.buffered < STRIPE {
return;
}
let stripe = self.buffer;
self.consume(&stripe);
self.buffered = 0;
}
let (stripes, rest) = data.as_chunks::<STRIPE>();
for stripe in stripes {
self.consume(stripe);
}
self.buffer[..rest.len()].copy_from_slice(rest);
self.buffered = rest.len();
}
pub(super) fn finish(&self) -> u64 {
let mut hash = if self.total >= STRIPE as u64 {
let [a, b, c, d] = self.accumulators;
let mut hash = a
.rotate_left(1)
.wrapping_add(b.rotate_left(7))
.wrapping_add(c.rotate_left(12))
.wrapping_add(d.rotate_left(18));
for accumulator in self.accumulators {
hash = (hash ^ round(0, accumulator))
.wrapping_mul(PRIME_1)
.wrapping_add(PRIME_4);
}
hash
} else {
self.seed.wrapping_add(PRIME_5)
};
hash = hash.wrapping_add(self.total);
let mut rest = &self.buffer[..self.buffered];
while rest.len() >= 8 {
hash ^= round(0, read_u64(rest));
hash = hash
.rotate_left(27)
.wrapping_mul(PRIME_1)
.wrapping_add(PRIME_4);
rest = &rest[8..];
}
if rest.len() >= 4 {
hash ^= u64::from(read_u32(rest)).wrapping_mul(PRIME_1);
hash = hash
.rotate_left(23)
.wrapping_mul(PRIME_2)
.wrapping_add(PRIME_3);
rest = &rest[4..];
}
for &byte in rest {
hash ^= u64::from(byte).wrapping_mul(PRIME_5);
hash = hash.rotate_left(11).wrapping_mul(PRIME_1);
}
avalanche(hash)
}
fn consume(&mut self, stripe: &[u8; STRIPE]) {
let (lanes, _) = stripe.as_chunks::<8>();
for (accumulator, lane) in self.accumulators.iter_mut().zip(lanes) {
*accumulator = round(*accumulator, u64::from_le_bytes(*lane));
}
}
}
pub(super) fn xxh64(data: &[u8], seed: u64) -> u64 {
let mut hasher = Xxh64::new(seed);
hasher.update(data);
hasher.finish()
}
fn round(accumulator: u64, lane: u64) -> u64 {
accumulator
.wrapping_add(lane.wrapping_mul(PRIME_2))
.rotate_left(31)
.wrapping_mul(PRIME_1)
}
fn avalanche(mut hash: u64) -> u64 {
hash ^= hash >> 33;
hash = hash.wrapping_mul(PRIME_2);
hash ^= hash >> 29;
hash = hash.wrapping_mul(PRIME_3);
hash ^ (hash >> 32)
}
fn read_u64(bytes: &[u8]) -> u64 {
let mut lane = [0; 8];
lane.copy_from_slice(&bytes[..8]);
u64::from_le_bytes(lane)
}
fn read_u32(bytes: &[u8]) -> u32 {
let mut lane = [0; 4];
lane.copy_from_slice(&bytes[..4]);
u32::from_le_bytes(lane)
}
#[cfg(test)]
mod tests {
use super::*;
fn data() -> Vec<u8> {
(0..300u32).map(|i| (i * 7 + 3) as u8).collect()
}
#[test]
fn matches_the_reference_vectors() {
let data = data();
let cases: [(&[u8], u64, u64); 9] = [
(b"", 0, 0xEF46_DB37_51D8_E999),
(b"a", 0, 0xD24E_C4F1_A98C_6E5B),
(b"abc", 0, 0x44BC_2CF5_AD77_0999),
(b"abc", 1, 0xBEA9_CA81_9932_8908),
(&data[..31], 0, 0xA2AA_5F33_CC4A_6119),
(&data[..32], 0, 0x23C3_C17E_F790_FD97),
(&data[..100], 7, 0x6281_D896_ACB8_0D7C),
(&data, 0, 0x2400_04DB_EE0B_A6DC),
(&data, 0x9E37_79B9_7F4A_7C15, 0x4724_CCB3_6F2A_EE62),
];
for (input, seed, expected) in cases {
assert_eq!(
xxh64(input, seed),
expected,
"{} bytes, seed {seed}",
input.len()
);
}
}
#[test]
fn pieces_hash_like_the_whole() {
let data = data();
let whole = xxh64(&data, 3);
for split in [0, 1, 5, 31, 32, 33, 64, 299, 300] {
for second in [split, (split + 40).min(300)] {
let mut hasher = Xxh64::new(3);
hasher.update(&data[..split]);
hasher.update(&data[split..second]);
hasher.update(&data[second..]);
assert_eq!(hasher.finish(), whole, "split at {split} and {second}");
}
}
}
}