use std::hash::{BuildHasher, Hasher};
#[derive(Clone, Copy, Default)]
pub(super) struct GuidBuildHasher;
impl BuildHasher for GuidBuildHasher {
type Hasher = GuidHasher;
#[inline]
fn build_hasher(&self) -> GuidHasher {
GuidHasher(0)
}
}
pub(super) struct GuidHasher(u64);
impl Hasher for GuidHasher {
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut acc = self.0;
let mut chunks = bytes.chunks_exact(8);
for c in &mut chunks {
let v = u64::from_le_bytes(c.try_into().unwrap());
acc = (acc ^ v).wrapping_mul(0x9E37_79B9_7F4A_7C15);
}
let rem = chunks.remainder();
if !rem.is_empty() {
let mut buf = [0u8; 8];
buf[..rem.len()].copy_from_slice(rem);
acc = (acc ^ u64::from_le_bytes(buf)).wrapping_mul(0x9E37_79B9_7F4A_7C15);
}
self.0 = acc;
}
#[inline]
fn finish(&self) -> u64 {
let mut z = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::BlobGuid;
use std::collections::HashSet;
fn hash(guid: &BlobGuid) -> u64 {
GuidBuildHasher.hash_one(guid)
}
#[test]
fn deterministic() {
let g: BlobGuid = [7; 16];
assert_eq!(hash(&g), hash(&g));
}
#[test]
fn distinct_guids_avalanche() {
let mut seen = HashSet::new();
for i in 0u32..100_000 {
let mut g: BlobGuid = [0; 16];
g[..4].copy_from_slice(&i.to_le_bytes());
g[8] = (i & 0xFF) as u8;
assert!(seen.insert(hash(&g)), "hash collision at {i}");
}
let a: BlobGuid = [0; 16];
let mut b = a;
b[0] = 1;
let diff = (hash(&a) ^ hash(&b)).count_ones();
assert!(diff >= 16, "weak avalanche: only {diff} bits changed");
}
}