use core::hash::Hasher;
use super::DEFAULT_RAPID_SECRETS;
use super::mix_np::rapid_mix_np;
use super::rapid_const::rapidhash_core;
use super::seed::rapidhash_seed;
use crate::util::hints::likely;
macro_rules! write_num {
($write_num:ident, $int:ident, $unsigned:ident) => {
#[inline(always)]
fn $write_num(&mut self, i: $int) {
const N: u8 = core::mem::size_of::<$int>() as u8 * 8;
if SPONGE {
let bytes = (i as $unsigned) as u128;
if likely(self.sponge_len + N <= 128) {
self.sponge |= bytes << self.sponge_len;
self.sponge_len += N;
} else {
let a = self.sponge as u64;
let b = (self.sponge >> 64) as u64;
self.seed = rapid_mix_np::<PROTECTED>(a ^ self.seed, b ^ self.secrets[0]);
self.sponge = bytes;
self.sponge_len = N;
}
} else {
let bytes = (i as $unsigned) as u64;
self.seed = rapid_mix_np::<PROTECTED>(bytes ^ self.secrets[0], bytes ^ self.seed);
}
}
};
}
#[derive(Copy, Clone)]
pub struct RapidHasher<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool = false, const PROTECTED: bool = false> {
seed: u64,
secrets: &'s [u64; 7],
sponge: u128,
sponge_len: u8,
}
impl<'s, const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> RapidHasher<'s, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
pub const DEFAULT_SEED: u64 = super::seed::DEFAULT_SEED;
#[inline(always)]
#[must_use]
pub const fn new(mut seed: u64) -> Self {
seed = rapidhash_seed(seed);
Self::new_precomputed_seed(seed, &DEFAULT_RAPID_SECRETS.secrets)
}
#[inline(always)]
#[must_use]
pub(super) const fn new_precomputed_seed(seed: u64, secrets: &'s [u64; 7]) -> Self {
Self {
seed,
secrets,
sponge: 0,
sponge_len: 0,
}
}
#[inline(always)]
#[must_use]
pub const fn default_const() -> Self {
Self::new(Self::DEFAULT_SEED)
}
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for RapidHasher<'_, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
#[inline(always)]
fn default() -> Self {
Self::new(super::seed::DEFAULT_SEED)
}
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Hasher for RapidHasher<'_, AVALANCHE, SPONGE, COMPACT, PROTECTED> {
#[inline(always)]
fn finish(&self) -> u64 {
#[allow(clippy::collapsible_else_if)]
if SPONGE {
if !AVALANCHE {
if self.sponge_len > 0 {
let a = self.sponge as u64;
let b = (self.sponge >> 64) as u64;
rapid_mix_np::<PROTECTED>(a ^ self.seed, b ^ self.secrets[0])
} else {
self.seed
}
} else {
let mut seed = self.seed;
if self.sponge_len > 0 {
let a = self.sponge as u64;
let b = (self.sponge >> 64) as u64;
seed = rapid_mix_np::<PROTECTED>(a ^ self.seed, b ^ self.secrets[0]);
}
rapid_mix_np::<PROTECTED>(seed, DEFAULT_RAPID_SECRETS.secrets[1])
}
} else {
if !AVALANCHE {
self.seed
} else {
rapid_mix_np::<PROTECTED>(self.seed, DEFAULT_RAPID_SECRETS.secrets[1])
}
}
}
#[inline(always)]
fn write(&mut self, bytes: &[u8]) {
self.seed = rapidhash_core::<AVALANCHE, COMPACT, PROTECTED>(self.seed, self.secrets, bytes);
}
write_num!(write_u8, u8, u8);
write_num!(write_u16, u16, u16);
write_num!(write_u32, u32, u32);
write_num!(write_u64, u64, u64);
write_num!(write_usize, usize, usize);
write_num!(write_i8, i8, u8);
write_num!(write_i16, i16, u16);
write_num!(write_i32, i32, u32);
write_num!(write_i64, i64, u64);
write_num!(write_isize, isize, usize);
#[inline(always)]
fn write_u128(&mut self, i: u128) {
let a = i as u64;
let b = (i >> 64) as u64;
self.seed = rapid_mix_np::<PROTECTED>(a ^ self.seed, b ^ self.secrets[0]);
}
#[inline(always)]
fn write_i128(&mut self, i: i128) {
let a = (i as u128) as u64;
let b = (i as u128 >> 64) as u64;
self.seed = rapid_mix_np::<PROTECTED>(a ^ self.seed, b ^ self.secrets[0]);
}
#[cfg(feature = "nightly")]
#[inline(always)]
fn write_length_prefix(&mut self, len: usize) {
self.write_usize(len);
}
#[cfg(feature = "nightly")]
#[inline(always)]
fn write_str(&mut self, s: &str) {
self.write(s.as_bytes());
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::hash::BuildHasher;
use crate::fast::SeedableState;
use super::*;
#[test]
fn test_hasher_size() {
assert_eq!(core::mem::size_of::<RapidHasher::<true, true, false, false>>(), 48);
}
#[ignore]
#[test]
fn test_hasher_write_u64() {
assert_eq!((8 & 24) >> (8 >> 3), 4);
let ints = [1234u64, 0, 1, u64::MAX, u64::MAX - 2385962040453523];
for int in ints {
let mut hasher = RapidHasher::<true, false>::default();
hasher.write(int.to_le_bytes().as_slice());
let a = hasher.finish();
assert_eq!(int.to_le_bytes().as_slice().len(), 8);
let mut hasher = RapidHasher::<true, false>::default();
hasher.write_u64(int);
let b = hasher.finish();
assert_eq!(a, b, "Mismatching hash for u64 with input {int}");
}
}
#[test]
#[ignore]
#[cfg(feature = "std")]
fn test_num_collisions() {
let builder = SeedableState::default();
let mut collisions = 0;
let mut set = std::collections::HashSet::new();
for i in 0..=u16::MAX {
let hash_u16 = builder.hash_one(i) & 0xFFFFFF;
if set.contains(&hash_u16) {
collisions += 1;
} else {
set.insert(hash_u16);
}
}
assert_eq!(collisions, 0, "Collisions found when hashing numbers with seed {builder:?}");
}
}