use core::hash::{BuildHasher, Hasher};
use super::DEFAULT_RAPID_SECRETS;
use super::mix_np::rapid_mix_np;
use super::rapid_const::rapidhash_core;
use super::seed::rapidhash_seed;
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 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<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool = false, const PROTECTED: bool = false> {
seed: u64,
secrets: &'static [u64; 7], sponge: u128,
sponge_len: u8,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct RapidBuildHasher<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool = false, const PROTECTED: bool = false> {
seed: u64,
secrets: &'static [u64; 7],
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> RapidBuildHasher<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
#[inline(always)]
pub const fn new(mut seed: u64) -> Self {
seed = rapidhash_seed(seed);
Self { seed, secrets: &DEFAULT_RAPID_SECRETS.secrets }
}
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> BuildHasher for RapidBuildHasher<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
type Hasher = RapidHasher<AVALANCHE, SPONGE, COMPACT, PROTECTED>;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
Self::Hasher::new_precomputed_seed(self.seed, self.secrets)
}
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for RapidBuildHasher<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
#[inline]
fn default() -> Self {
Self::new(RapidHasher::<AVALANCHE, SPONGE, COMPACT, PROTECTED>::DEFAULT_SEED)
}
}
impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> RapidHasher<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: &'static [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 {
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(test)]
mod tests {
extern crate std;
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 = RapidBuildHasher::<true, false>::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");
}
}