use std::hash::Hasher;
const OFFSET: u64 = 0xCBF2_9CE4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01B3;
pub const fn fnv1a64(bytes: &[u8]) -> u64 {
let mut state = OFFSET;
let mut index = 0;
while index < bytes.len() {
state ^= bytes[index] as u64;
state = state.wrapping_mul(PRIME);
index += 1;
}
state
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Hash64 {
state: u64,
}
impl Hash64 {
pub const fn new() -> Self {
Self { state: OFFSET }
}
pub const fn digest(&self) -> u64 {
self.state
}
pub fn bytes(&mut self, bytes: &[u8]) {
for byte in bytes {
self.state ^= *byte as u64;
self.state = self.state.wrapping_mul(PRIME);
}
}
pub fn str(&mut self, value: &str) {
self.u64(value.len() as u64);
self.bytes(value.as_bytes());
}
pub fn u8(&mut self, value: u8) {
self.bytes(&[value]);
}
pub fn u16(&mut self, value: u16) {
self.bytes(&value.to_le_bytes());
}
pub fn u32(&mut self, value: u32) {
self.bytes(&value.to_le_bytes());
}
pub fn u64(&mut self, value: u64) {
self.bytes(&value.to_le_bytes());
}
pub fn u128(&mut self, value: u128) {
self.bytes(&value.to_le_bytes());
}
pub fn i8(&mut self, value: i8) {
self.u8(value as u8);
}
pub fn i16(&mut self, value: i16) {
self.u16(value as u16);
}
pub fn i32(&mut self, value: i32) {
self.u32(value as u32);
}
pub fn i64(&mut self, value: i64) {
self.u64(value as u64);
}
pub fn usize(&mut self, value: usize) {
self.u64(value as u64);
}
pub fn isize(&mut self, value: isize) {
self.i64(value as i64);
}
pub fn f32(&mut self, value: f32) {
self.u32(value.to_bits());
}
pub fn f64(&mut self, value: f64) {
self.u64(value.to_bits());
}
pub fn bool(&mut self, value: bool) {
self.u8(value as u8);
}
}
impl Default for Hash64 {
fn default() -> Self {
Self::new()
}
}
impl Hasher for Hash64 {
fn finish(&self) -> u64 {
self.digest()
}
fn write(&mut self, bytes: &[u8]) {
self.bytes(bytes);
}
fn write_u8(&mut self, value: u8) {
self.u8(value);
}
fn write_u16(&mut self, value: u16) {
self.u16(value);
}
fn write_u32(&mut self, value: u32) {
self.u32(value);
}
fn write_u64(&mut self, value: u64) {
self.u64(value);
}
fn write_u128(&mut self, value: u128) {
self.u128(value);
}
fn write_usize(&mut self, value: usize) {
self.usize(value);
}
fn write_i8(&mut self, value: i8) {
self.i8(value);
}
fn write_i16(&mut self, value: i16) {
self.i16(value);
}
fn write_i32(&mut self, value: i32) {
self.i32(value);
}
fn write_i64(&mut self, value: i64) {
self.i64(value);
}
fn write_i128(&mut self, value: i128) {
self.u128(value as u128);
}
fn write_isize(&mut self, value: isize) {
self.isize(value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_published_fnv1a_vectors_hold() {
assert_eq!(fnv1a64(b""), 0xCBF2_9CE4_8422_2325);
assert_eq!(fnv1a64(b"a"), 0xAF63_DC4C_8601_EC8C);
assert_eq!(fnv1a64(b"foobar"), 0x8594_4171_F739_67E8);
assert_eq!(fnv1a64(b"loot"), 0xCDEC_E8AD_70D5_84B7);
}
#[test]
fn streaming_agrees_with_the_one_shot_hash() {
for bytes in [b"".as_slice(), b"a", b"loot", b"repame-sim"] {
let mut hash = Hash64::new();
hash.bytes(bytes);
assert_eq!(hash.digest(), fnv1a64(bytes), "{bytes:?}");
}
}
#[test]
fn a_written_integer_equals_its_little_endian_bytes() {
let mut by_value = Hash64::new();
by_value.u32(0x0403_0201);
by_value.u8(0x05);
let mut by_bytes = Hash64::new();
by_bytes.bytes(&[0x01, 0x02, 0x03, 0x04, 0x05]);
assert_eq!(by_value.digest(), by_bytes.digest());
let mut wide = Hash64::new();
wide.u64(u64::MAX);
let mut wide_bytes = Hash64::new();
wide_bytes.bytes(&[0xFF; 8]);
assert_eq!(wide.digest(), wide_bytes.digest());
}
#[test]
fn lengths_prefix_and_signs_keep_their_bits() {
let mut joined = Hash64::new();
joined.str("ab");
let mut split = Hash64::new();
split.str("a");
split.str("b");
assert_ne!(joined.digest(), split.digest());
let mut signed = Hash64::new();
signed.i32(-1);
let mut unsigned = Hash64::new();
unsigned.u32(u32::MAX);
assert_eq!(signed.digest(), unsigned.digest());
let mut plus_zero = Hash64::new();
plus_zero.f32(0.0);
let mut minus_zero = Hash64::new();
minus_zero.f32(-0.0);
assert_ne!(plus_zero.digest(), minus_zero.digest(), "bits, not values");
}
#[test]
fn the_hasher_path_matches_the_write_methods() {
use std::hash::Hash;
let mut through_hash = Hash64::new();
7u32.hash(&mut through_hash);
true.hash(&mut through_hash);
(-3i64).hash(&mut through_hash);
let mut through_writes = Hash64::new();
through_writes.u32(7);
through_writes.bool(true);
through_writes.i64(-3);
assert_eq!(through_hash.digest(), through_writes.digest());
}
#[test]
fn a_digest_is_the_same_in_the_next_run() {
let mut first = Hash64::new();
first.str("repame-sim");
let mut second = Hash64::new();
second.str("repame-sim");
assert_eq!(first.digest(), second.digest());
assert_eq!(first, second);
assert_eq!(Hash64::default().digest(), fnv1a64(b""));
}
}