use std::hash::{BuildHasher, Hasher};
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
#[derive(Clone)]
pub struct DetHasher(u64);
impl DetHasher {
#[must_use]
pub const fn new() -> Self {
Self(FNV_OFFSET)
}
}
impl Default for DetHasher {
fn default() -> Self {
Self::new()
}
}
impl Hasher for DetHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
let mut h = self.0;
for &b in bytes {
h ^= u64::from(b);
h = h.wrapping_mul(FNV_PRIME);
}
self.0 = h;
}
}
#[derive(Default, Clone)]
pub struct DetState;
impl BuildHasher for DetState {
type Hasher = DetHasher;
fn build_hasher(&self) -> Self::Hasher {
DetHasher::new()
}
}
pub type DetHashMap<K, V> = std::collections::HashMap<K, V, DetState>;
pub type DetHashSet<K> = std::collections::HashSet<K, DetState>;
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
fn hash_writes(writes: &[&[u8]]) -> u64 {
let mut h = DetHasher::new();
for w in writes {
h.write(w);
}
h.finish()
}
#[test]
fn empty_hash_is_offset_basis() {
assert_eq!(DetHasher::new().finish(), FNV_OFFSET);
}
#[test]
fn distinct_sequences_differ() {
assert_ne!(hash_writes(&[b"abc"]), hash_writes(&[b"abd"]));
}
#[test]
fn write_is_order_sensitive() {
assert_ne!(hash_writes(&[b"ab", b"cd"]), hash_writes(&[b"cd", b"ab"]),);
}
#[test]
fn zero_state_does_not_restart() {
let target = (0u16..=u16::from(u8::MAX)).map(|b| b as u8).find(|&b| {
let mut h = DetHasher::new();
h.write(&[b]);
h.finish() == 0
});
if let Some(zeroing) = target {
let mut a = DetHasher::new();
a.write(&[zeroing]);
a.write(&[0x01]);
let mut b = DetHasher::new();
b.write(&[0x01]);
assert_ne!(a.finish(), b.finish());
}
assert_ne!(hash_writes(&[]), hash_writes(&[b"x"]));
}
}