pub const FNV_OFFSET_64: u64 = 0xcbf2_9ce4_8422_2325;
pub const FNV_PRIME_64: u64 = 0x100_0000_01b3;
#[must_use]
pub fn fnv1a_64(bytes: &[u8]) -> u64 {
let mut h = FNV_OFFSET_64;
for &b in bytes {
h = fnv1a_64_step(h, b);
}
h
}
#[inline]
#[must_use]
pub const fn fnv1a_64_step(h: u64, b: u8) -> u64 {
(h ^ (b as u64)).wrapping_mul(FNV_PRIME_64)
}
pub fn fnv1a_64_extend(h: &mut u64, bytes: &[u8]) {
for &b in bytes {
*h = fnv1a_64_step(*h, b);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_hashes_to_offset_basis() {
assert_eq!(fnv1a_64(b""), FNV_OFFSET_64);
}
#[test]
fn single_byte_matches_canonical_table() {
assert_eq!(fnv1a_64(b"a"), 0xaf63_dc4c_8601_ec8c);
assert_eq!(fnv1a_64(b"foobar"), 0x8594_4171_f739_67e8);
}
#[test]
fn step_and_extend_agree_with_oneshot() {
let input = b"the quick brown fox jumps over the lazy dog";
let oneshot = fnv1a_64(input);
let mut streaming = FNV_OFFSET_64;
fnv1a_64_extend(&mut streaming, input);
assert_eq!(oneshot, streaming);
let mut by_step = FNV_OFFSET_64;
for &b in input {
by_step = fnv1a_64_step(by_step, b);
}
assert_eq!(oneshot, by_step);
}
#[test]
fn fnv1a_64_is_deterministic_across_calls() {
let input = b"wafrift-canonical-form";
assert_eq!(fnv1a_64(input), fnv1a_64(input));
}
#[test]
fn extend_from_offset_matches_oneshot_no_prefix_drift() {
let input = b"some-arbitrary-canonical-bytes";
let mut acc = FNV_OFFSET_64;
fnv1a_64_extend(&mut acc, input);
assert_eq!(acc, fnv1a_64(input));
}
}