From 980159d060f932531583c27f407c21020124a76c Mon Sep 17 00:00:00 2001 From: Tom Kaitchuck Date: Tue, 6 Oct 2020 22:48:38 -0700 Subject: [PATCH] Use getrandom for obtaining random state if std is enabled. (This requires an API change.) --- Cargo.toml | 21 +++--- src/aes_hash.rs | 23 ++++--- src/convert.rs | 6 ++ src/fallback_hash.rs | 35 +++++----- src/hash_quality_test.rs | 83 ++++++++++++------------ src/lib.rs | 62 +++++------------- src/random_state.rs | 134 ++++++++++++++++++++------------------- tests/nopanic.rs | 4 +- 8 files changed, 178 insertions(+), 190 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27d17c6..bd82ec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ahash" -version = "0.4.5" +version = "0.5.0" authors = ["Tom Kaitchuck "] license = "MIT OR Apache-2.0" description = "A non-cryptographic hash function using AES-NI for high performance" @@ -19,18 +19,15 @@ bench = true doc = true [features] -default = ["compile-time-rng", "std"] +default = ["std"] # Enabling this will enable `AHashMap` and `AHashSet` -std = [] +std = ["getrandom", "lazy_static"] +compile-time-rng = ["const-random"] -# Enables specilization (reuqires nightly) +# Enables specilization (requires nightly) specialize = [] -# Disabling this feature will make the `ABuildHasher::new` use a preset seed -# and disable the `Default` impl for `AHasher` -compile-time-rng = ["const-random"] - [[bench]] name = "ahash" path = "tests/bench.rs" @@ -60,16 +57,18 @@ debug-assertions = false codegen-units = 1 [dependencies] +lazy_static = { version = "1.4.0", optional = true } +getrandom = { version = "0.2.0", optional = true } const-random = { version = "0.1.6", optional = true } [dev-dependencies] no-panic = "0.1.10" criterion = {version = "0.3.2"} -seahash = "3.0.5" +seahash = "4.0" fnv = "1.0.5" fxhash = "0.2.1" -hex = "0.3.2" -rand = "0.6.5" +hex = "0.4.2" +rand = "0.7.3" [package.metadata.docs.rs] rustc-args = ["-C", "target-feature=+aes"] diff --git a/src/aes_hash.rs b/src/aes_hash.rs index 2593dea..e3137e4 100644 --- a/src/aes_hash.rs +++ b/src/aes_hash.rs @@ -3,6 +3,7 @@ use crate::operations::*; #[cfg(feature = "specialize")] use crate::HasherExt; use core::hash::Hasher; +use crate::RandomState; /// A `Hasher` for hashing an arbitrary stream of bytes. /// @@ -56,14 +57,14 @@ impl AHasher { } } - #[cfg(test)] - pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher { - use crate::random_state::scramble_keys; - let (k1, k2, k3, k4) = scramble_keys(key1, key2); - AHasher { - enc: [k1, k2].convert(), - sum: [k3, k4].convert(), - key: add_by_64s([k1, k2], [k3, k4]).convert(), + #[inline] + pub(crate) fn from_random_state(rand_state: &RandomState) -> Self { + let key1 = [rand_state.k0, rand_state.k1].convert(); + let key2 = [rand_state.k2, rand_state.k3].convert(); + Self { + enc: key1, + sum: key2, + key: key1 ^ key2, } } @@ -108,7 +109,9 @@ impl HasherExt for AHasher { } } -/// Provides methods to hash all of the primitive types. +/// Provides [Hasher] methods to hash all of the primitive types. +/// +/// [Hasher]: core::hash::Hasher impl Hasher for AHasher { #[inline] fn write_u8(&mut self, i: u8) { @@ -228,7 +231,7 @@ mod tests { use std::hash::{BuildHasher, Hasher}; #[test] fn test_sanity() { - let mut hasher = RandomState::with_seeds(192837465, 1234567890).build_hasher(); + let mut hasher = RandomState::with_seeds(1, 2, 3,4).build_hasher(); hasher.write_u64(0); let h1 = hasher.finish(); hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]); diff --git a/src/convert.rs b/src/convert.rs index 435c03c..3d41e36 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -47,6 +47,12 @@ convert!(u128, [u64; 2]); convert!(u128, [u32; 4]); convert!(u128, [u16; 8]); convert!(u128, [u8; 16]); +convert!([u64; 8], [u32; 16]); +convert!([u64; 8], [u16; 32]); +convert!([u64; 8], [u8; 64]); +convert!([u64; 4], [u32; 8]); +convert!([u64; 4], [u16; 16]); +convert!([u64; 4], [u8; 32]); convert!([u64; 2], [u32; 4]); convert!([u64; 2], [u16; 8]); convert!([u64; 2], [u8; 16]); diff --git a/src/fallback_hash.rs b/src/fallback_hash.rs index 6ba796e..198dbfa 100644 --- a/src/fallback_hash.rs +++ b/src/fallback_hash.rs @@ -3,9 +3,11 @@ use crate::operations::folded_multiply; #[cfg(feature = "specialize")] use crate::HasherExt; use core::hash::Hasher; +use crate::RandomState; +use crate::random_state::PI; ///This constant come from Kunth's prng (Empirically it works better than those from splitmix32). -const MULTIPLE: u64 = crate::random_state::MULTIPLE; +const MULTIPLE: u64 = 6364136223846793005; const ROT: u32 = 23; //17 /// A `Hasher` for hashing an arbitrary stream of bytes. @@ -31,22 +33,23 @@ impl AHasher { #[inline] #[allow(dead_code)] // Is not called if non-fallback hash is used. pub fn new_with_keys(key1: u128, key2: u128) -> AHasher { + let pi: [u128; 2] = PI.convert(); + let key1: [u64; 2] = (key1 ^ pi[0]).convert(); + let key2: [u64; 2] = (key2 ^ pi[1]).convert(); AHasher { - buffer: key1 as u64, - pad: key2 as u64, - extra_keys: (key1 ^ key2).convert(), + buffer: key1[0], + pad: key1[1], + extra_keys: key2, } } - - #[cfg(test)] + + #[inline] #[allow(dead_code)] // Is not called if non-fallback hash is used. - pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher { - use crate::random_state::scramble_keys; - let (k1, k2, k3, k4) = scramble_keys(key1, key2); + pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher { AHasher { - buffer: k1, - pad: k2, - extra_keys: [k3, k4], + buffer: rand_state.k0, + pad: rand_state.k1, + extra_keys: [rand_state.k2, rand_state.k3], } } @@ -117,7 +120,9 @@ impl HasherExt for AHasher { } } -/// Provides methods to hash all of the primitive types. +/// Provides [Hasher] methods to hash all of the primitive types. +/// +/// [Hasher]: core::hash::Hasher impl Hasher for AHasher { #[inline] fn write_u8(&mut self, i: u8) { @@ -141,9 +146,7 @@ impl Hasher for AHasher { #[inline] fn write_u128(&mut self, i: u128) { - let data: [u64; 2] = i.convert(); - self.update(data[0]); - self.update(data[1]); + self.large_update(i); } #[inline] diff --git a/src/hash_quality_test.rs b/src/hash_quality_test.rs index 1a69562..5640bba 100644 --- a/src/hash_quality_test.rs +++ b/src/hash_quality_test.rs @@ -93,7 +93,7 @@ fn test_no_full_collisions(gen_hash: impl Fn() -> T) { assert_eq!(2396744, map.len()); } -fn test_keys_change_output(constructor: impl Fn(u64, u64) -> T) { +fn test_keys_change_output(constructor: impl Fn(u128, u128) -> T) { let mut a = constructor(1, 1); let mut b = constructor(1, 2); let mut c = constructor(2, 1); @@ -110,7 +110,7 @@ fn test_keys_change_output(constructor: impl Fn(u64, u64) -> T) { assert_sufficiently_different(c.finish(), d.finish(), 1); } -fn test_input_affect_every_byte(constructor: impl Fn(u64, u64) -> T) { +fn test_input_affect_every_byte(constructor: impl Fn(u128, u128) -> T) { let base = 0.get_hash(constructor(0, 0)); for shift in 0..16 { let mut alternitives = vec![]; @@ -124,13 +124,13 @@ fn test_input_affect_every_byte(constructor: impl Fn(u64, u64) -> } ///Ensures that for every bit in the output there is some value for each byte in the key that flips it. -fn test_keys_affect_every_byte(item: H, constructor: impl Fn(u64, u64) -> T) { +fn test_keys_affect_every_byte(item: H, constructor: impl Fn(u128, u128) -> T) { let base = item.get_hash(constructor(0, 0)); - for shift in 0..8 { + for shift in 0..16 { let mut alternitives1 = vec![]; let mut alternitives2 = vec![]; for v in 0..256 { - let input = (v as u64) << (shift * 8); + let input = (v as u128) << (shift * 8); let hasher1 = constructor(input, 0); let hasher2 = constructor(0, input); let h1 = item.get_hash(hasher1); @@ -151,7 +151,7 @@ fn assert_each_byte_differs(base: u64, alternitives: Vec) { assert_eq!(core::u64::MAX, changed_bits, "Bits changed: {:x}", changed_bits); } -fn test_finish_is_consistent(constructor: impl Fn(u64, u64) -> T) { +fn test_finish_is_consistent(constructor: impl Fn(u128, u128) -> T) { let mut hasher = constructor(1, 2); "Foo".hash(&mut hasher); let a = hasher.finish(); @@ -159,8 +159,8 @@ fn test_finish_is_consistent(constructor: impl Fn(u64, u64) -> T) { assert_eq!(a, b); } -fn test_single_key_bit_flip(constructor: impl Fn(u64, u64) -> T) { - for bit in 0..64 { +fn test_single_key_bit_flip(constructor: impl Fn(u128, u128) -> T) { + for bit in 0..128 { let mut a = constructor(0, 0); let mut b = constructor(0, 1 << bit); let mut c = constructor(1 << bit, 0); @@ -320,57 +320,58 @@ mod fallback_tests { #[test] fn fallback_single_bit_flip() { - test_single_bit_flip(|| AHasher::test_with_keys(0, 0)) + test_single_bit_flip(|| AHasher::new_with_keys(0, 0)) } #[test] fn fallback_single_key_bit_flip() { - test_single_key_bit_flip(AHasher::test_with_keys) + test_single_key_bit_flip(AHasher::new_with_keys) } #[test] fn fallback_all_bytes_matter() { - test_all_bytes_matter(|| AHasher::test_with_keys(0, 0)); + test_all_bytes_matter(|| AHasher::new_with_keys(0, 0)); } #[test] fn fallback_test_no_pair_collisions() { - test_no_pair_collisions(|| AHasher::test_with_keys(0, 0)); + test_no_pair_collisions(|| AHasher::new_with_keys(0, 0)); } #[test] fn fallback_test_no_full_collisions() { - test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890)); + test_no_full_collisions(|| AHasher::new_with_keys(12345, 67890)); } #[test] fn fallback_keys_change_output() { - test_keys_change_output(AHasher::test_with_keys); + test_keys_change_output(AHasher::new_with_keys); } #[test] fn fallback_input_affect_every_byte() { - test_input_affect_every_byte(AHasher::test_with_keys); + test_input_affect_every_byte(AHasher::new_with_keys); } #[test] fn fallback_keys_affect_every_byte() { - test_keys_affect_every_byte(0, AHasher::test_with_keys); - test_keys_affect_every_byte("", AHasher::test_with_keys); - test_keys_affect_every_byte((0, 0), AHasher::test_with_keys); + //For fallback second key is not used in every hash. + test_keys_affect_every_byte(0, |a, b| AHasher::new_with_keys(a ^ b, a)); + test_keys_affect_every_byte("", |a, b| AHasher::new_with_keys(a ^ b, a)); + test_keys_affect_every_byte((0, 0), |a, b| AHasher::new_with_keys(a ^ b, a)); } #[test] fn fallback_finish_is_consistant() { - test_finish_is_consistent(AHasher::test_with_keys) + test_finish_is_consistent(AHasher::new_with_keys) } #[test] fn fallback_padding_doesnot_collide() { - test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 0)); - test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 1)); - test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 0)); - test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 1)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 0)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 1)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(1, 0)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(1, 1)); } } @@ -382,8 +383,8 @@ mod aes_tests { use crate::hash_quality_test::*; use std::hash::{Hash, Hasher}; - const BAD_KEY: u64 = 0x5252_5252_5252_5252; //This encrypts to 0. - const BAD_KEY2: u64 = 0x6363_6363_6363_6363; //This decrypts to 0. + const BAD_KEY: u128 = 0x5252_5252_5252_5252_5252_5252_5252_5252; //This encrypts to 0. + const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363; //This decrypts to 0. #[test] fn test_single_bit_in_byte() { @@ -396,56 +397,56 @@ mod aes_tests { #[test] fn aes_single_bit_flip() { - test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); - test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + test_single_bit_flip(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY)); + test_single_bit_flip(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2)); } #[test] fn aes_single_key_bit_flip() { - test_single_key_bit_flip(|k1, k2| AHasher::test_with_keys(k1, k2)) + test_single_key_bit_flip(AHasher::new_with_keys) } #[test] fn aes_all_bytes_matter() { - test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); - test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + test_all_bytes_matter(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY)); + test_all_bytes_matter(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2)); } #[test] fn aes_test_no_pair_collisions() { - test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); - test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + test_no_pair_collisions(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY)); + test_no_pair_collisions(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2)); } #[test] fn ase_test_no_full_collisions() { - test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890)); + test_no_full_collisions(|| AHasher::new_with_keys(12345, 67890)); } #[test] fn aes_keys_change_output() { - test_keys_change_output(AHasher::test_with_keys); + test_keys_change_output(AHasher::new_with_keys); } #[test] fn aes_input_affect_every_byte() { - test_input_affect_every_byte(AHasher::test_with_keys); + test_input_affect_every_byte(AHasher::new_with_keys); } #[test] fn aes_keys_affect_every_byte() { - test_keys_affect_every_byte(0, AHasher::test_with_keys); - test_keys_affect_every_byte("", AHasher::test_with_keys); - test_keys_affect_every_byte((0, 0), AHasher::test_with_keys); + test_keys_affect_every_byte(0, AHasher::new_with_keys); + test_keys_affect_every_byte("", AHasher::new_with_keys); + test_keys_affect_every_byte((0, 0), AHasher::new_with_keys); } #[test] fn aes_finish_is_consistant() { - test_finish_is_consistent(AHasher::test_with_keys) + test_finish_is_consistent(AHasher::new_with_keys) } #[test] fn aes_padding_doesnot_collide() { - test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); - test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY)); + test_padding_doesnot_collide(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2)); } } diff --git a/src/lib.rs b/src/lib.rs index 542fa35..189699c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ -//! # aHash -//! -//! This hashing algorithm is intended to be a high performance, (hardware specific), keyed hash function. +//! AHash is a hashing algorithm is intended to be a high performance, (hardware specific), keyed hash function. //! This can be seen as a DOS resistant alternative to `FxHash`, or a fast equivalent to `SipHash`. //! It provides a high speed hash algorithm, but where the result is not predictable without knowing a Key. //! This allows it to be used in a `HashMap` without allowing for the possibility that an malicious user can @@ -49,13 +47,14 @@ pub use crate::hash_map::AHashMap; #[cfg(feature = "std")] pub use crate::hash_set::AHashSet; use core::hash::Hasher; +use core::hash::BuildHasher; -/// Provides a default [Hasher] compile time generated constants for keys. +/// Provides a default [Hasher] with fixed keys. /// This is typically used in conjunction with [BuildHasherDefault] to create /// [AHasher]s in order to hash the keys of the map. -/// +/// /// Generally it is preferable to use [RandomState] instead, so that different -/// hashmaps will have different keys. However if fixed keys are desireable this +/// hashmaps will have different keys. However if fixed keys are desireable this /// may be used instead. /// /// # Example @@ -73,13 +72,15 @@ use core::hash::Hasher; /// [HashMap]: std::collections::HashMap impl Default for AHasher { - /// Constructs a new [AHasher] with compile time generated constants for keys if the - /// `compile-time-rng`feature is enabled. Otherwise the keys will be fixed constants. - /// This means the keys will be the same from one instance to another, - /// but different from build to the next. So if it is possible for a potential - /// attacker to have access to the compiled binary it would be better - /// to specify keys generated at runtime. - /// + /// Constructs a new [AHasher] with fixed keys. + /// If `std` is enabled these will be generated upon first invocation. + /// Otherwise if the `compile-time-rng`feature is enabled these will be generated at compile time. + /// If neither of these features are available, hardcoded constants will be used. + /// + /// Because the values are fixed, different hashers will all hash elements the same way. + /// This could make hash values predictable, if DOS attacks are a concern. If this behaviour is + /// not required, it may be preferable to use [RandomState] instead. + /// /// # Examples /// /// ``` @@ -95,38 +96,8 @@ impl Default for AHasher { /// assert_eq!(hasher_1.finish(), hasher_2.finish()); /// ``` #[inline] - #[cfg(feature = "compile-time-rng")] fn default() -> AHasher { - AHasher::new_with_keys(const_random!(u128), const_random!(u128)) - } - - /// Constructs a new [AHasher] with compile time generated constants for keys if the - /// `compile-time-rng`feature is enabled. Otherwise the keys will be fixed constants. - /// This means the keys will be the same from one instance to another, - /// but different from build to the next. So if it is possible for a potential - /// attacker to have access to the compiled binary it would be better - /// to specify keys generated at runtime. - /// - /// # Examples - /// - /// ``` - /// use ahash::AHasher; - /// use std::hash::Hasher; - /// - /// let mut hasher_1 = AHasher::default(); - /// let mut hasher_2 = AHasher::default(); - /// - /// hasher_1.write_u32(1234); - /// hasher_2.write_u32(1234); - /// - /// assert_eq!(hasher_1.finish(), hasher_2.finish()); - /// ``` - #[inline] - #[cfg(not(feature = "compile-time-rng"))] - fn default() -> AHasher { - const K1: u128 = (random_state::INIT_SEED[0] as u128).wrapping_mul(random_state::MULTIPLE as u128); - const K2: u128 = (random_state::INIT_SEED[1] as u128).wrapping_mul(random_state::MULTIPLE as u128); - AHasher::new_with_keys(K1, K2) + RandomState::with_fixed_keys().build_hasher() } } @@ -169,13 +140,13 @@ impl HasherExt for T { // input.get_hash(a) // } +#[cfg(feature = "std")] #[cfg(test)] mod test { use crate::convert::Convert; use crate::*; use std::collections::HashMap; - #[cfg(feature = "std")] #[test] fn test_default_builder() { use core::hash::BuildHasherDefault; @@ -183,6 +154,7 @@ mod test { let mut map = HashMap::>::default(); map.insert(1, 3); } + #[test] fn test_builder() { let mut map = HashMap::::default(); diff --git a/src/random_state.rs b/src/random_state.rs index 0936556..ee5ac78 100644 --- a/src/random_state.rs +++ b/src/random_state.rs @@ -1,30 +1,23 @@ use crate::convert::Convert; -use crate::AHasher; +use crate::{AHasher}; use core::fmt; use core::hash::BuildHasher; -use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering; - -use crate::operations::folded_multiply; -#[cfg(all(feature = "compile-time-rng", not(test)))] -use const_random::const_random; - -///This constant come from Kunth's prng -pub(crate) const MULTIPLE: u64 = 6364136223846793005; -pub(crate) const INCREMENT: u64 = 1442695040888963407; - -// Const random provides randomized starting key with no runtime cost. -#[cfg(all(feature = "compile-time-rng", not(test)))] -pub(crate) const INIT_SEED: [u64; 2] = [const_random!(u64), const_random!(u64)]; - -#[cfg(any(not(feature = "compile-time-rng"), test))] -pub(crate) const INIT_SEED: [u64; 2] = [0x2360_ED05_1FC6_5DA4, 0x4385_DF64_9FCC_F645]; //From PCG-64 - -#[cfg(all(feature = "compile-time-rng", not(test)))] -static SEED: AtomicUsize = AtomicUsize::new(const_random!(u64) as usize); +use core::hash::Hasher; +#[cfg(feature = "std")] +use lazy_static::*; +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(feature = "std")] +lazy_static! { + static ref SEEDS: [u64; 8] = { + let mut result: [u8; 64] = [0; 64]; + getrandom::getrandom(&mut result).expect("getrandom::getrandom() failed."); + result.convert() + }; +} +static COUNTER: AtomicUsize = AtomicUsize::new(0); -#[cfg(any(not(feature = "compile-time-rng"), test))] -static SEED: AtomicUsize = AtomicUsize::new(INCREMENT as usize); +pub(crate) const PI: [u64;4] = [0x243f_6a88_85a3_08d3, 0x1319_8a2e_0370_7344, 0xA409_3822_299F_31D0, 0x082E_FA98_EC4E_6C89]; /// Provides a [Hasher] factory. This is typically used (e.g. by [HashMap]) to create /// [AHasher]s in order to hash the keys of the map. See `build_hasher` below. @@ -49,48 +42,58 @@ impl fmt::Debug for RandomState { impl RandomState { #[inline] + #[cfg(feature = "std")] pub fn new() -> RandomState { - //Using a self pointer. When running with ASLR this is a random value. - let previous = SEED.load(Ordering::Relaxed) as u64; - let stack_mem_loc = &previous as *const _ as u64; - //This is similar to the update function in the fallback. - //only one multiply is needed because memory locations are not under an attackers control. - let current_seed = previous - .wrapping_add(stack_mem_loc) - .wrapping_mul(MULTIPLE) - .rotate_right(31); - SEED.store(current_seed as usize, Ordering::Relaxed); - let (k0, k1, k2, k3) = scramble_keys(&SEED as *const _ as u64, current_seed); - RandomState { k0, k1, k2, k3 } + let seeds = *SEEDS; + let mut hasher = AHasher::from_random_state(&RandomState{k0: seeds[0], k1: seeds[1], k2: seeds[2], k3: seeds[3]}); + let stack_mem_loc = &hasher as *const _ as usize; + hasher.write_usize(COUNTER.fetch_add(stack_mem_loc, Ordering::Relaxed)); + let mix = |k: u64| { + let mut h = hasher.clone(); + h.write_u64(k); + h.finish() + }; + RandomState { k0: mix(seeds[4]), k1: mix(seeds[5]), k2: mix(seeds[6]), k3: mix(seeds[7]) } + } + + #[inline] + #[cfg(all(not(feature = "std"), feature = "compile-time-rng"))] + pub fn new() -> RandomState { + let mut hasher = AHasher::from_random_state(&RandomState::with_fixed_keys()); + let stack_mem_loc = &hasher as *const _ as usize; + hasher.write_usize(COUNTER.fetch_add(stack_mem_loc, Ordering::Relaxed)); + let mix = |k: u64| { + let mut h = hasher.clone(); + h.write_u64(k); + h.finish() + }; + RandomState { k0: mix(const_random!(u64)), k1: mix(const_random!(u64)), k2: mix(const_random!(u64)), k3: mix(const_random!(u64)) } + } + + #[inline] + pub(crate) fn with_fixed_keys() -> RandomState { + #[cfg(feature = "std")] + { + let seeds = *SEEDS; + RandomState { k0: seeds[4], k1: seeds[5], k2: seeds[6], k3: seeds[7] } + } + #[cfg(all(not(feature = "std"), feature = "compile-time-rng"))] + { + RandomState { k0: const_random!(u64), k1: const_random!(u64), k2: const_random!(u64), k3: const_random!(u64) } + } + #[cfg(all(not(feature = "std"), not(feature = "compile-time-rng")))] + { + RandomState { k0: PI[3], k1: PI[2], k2: PI[1], k3: PI[0] } + } } /// Allows for explicitly setting the seeds to used. - pub const fn with_seeds(k0: u64, k1: u64) -> RandomState { - let (k0, k1, k2, k3) = scramble_keys(k0, k1); + pub const fn with_seeds(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState { RandomState { k0, k1, k2, k3 } } } -/// This is based on the fallback hasher -#[inline] -pub(crate) const fn scramble_keys(a: u64, b: u64) -> (u64, u64, u64, u64) { - let k1 = folded_multiply(INIT_SEED[0] ^ a, MULTIPLE).wrapping_add(b); - let k2 = folded_multiply(INIT_SEED[0] ^ b, MULTIPLE).wrapping_add(a); - let k3 = folded_multiply(INIT_SEED[1] ^ a, MULTIPLE).wrapping_add(b); - let k4 = folded_multiply(INIT_SEED[1] ^ b, MULTIPLE).wrapping_add(a); - let combined = folded_multiply(a ^ b, MULTIPLE).wrapping_add(INCREMENT); - let rot1 = (combined & 63) as u32; - let rot2 = ((combined >> 16) & 63) as u32; - let rot3 = ((combined >> 32) & 63) as u32; - let rot4 = ((combined >> 48) & 63) as u32; - ( - k1.rotate_left(rot1), - k2.rotate_left(rot2), - k3.rotate_left(rot3), - k4.rotate_left(rot4), - ) -} - +#[cfg(any(feature = "std", feature = "compile-time-rng"))] impl Default for RandomState { #[inline] fn default() -> Self { @@ -101,13 +104,11 @@ impl Default for RandomState { impl BuildHasher for RandomState { type Hasher = AHasher; - /// Constructs a new [AHasher] with keys based on compile time generated constants** and the location - /// this object was constructed at in memory. This means that two different [BuildHasher]s will will generate + /// Constructs a new [AHasher] with keys based on this [RandomState] object. + /// This means that two different [RandomState]s will will generate /// [AHasher]s that will return different hashcodes, but [Hasher]s created from the same [BuildHasher] /// will generate the same hashes for the same input data. /// - /// ** - only if the `compile-time-rng` feature is enabled. - /// /// # Examples /// /// ``` @@ -133,7 +134,7 @@ impl BuildHasher for RandomState { /// [HashMap]: std::collections::HashMap #[inline] fn build_hasher(&self) -> AHasher { - AHasher::new_with_keys([self.k0, self.k1].convert(), [self.k2, self.k3].convert()) + AHasher::from_random_state(self) } } @@ -141,13 +142,16 @@ impl BuildHasher for RandomState { mod test { use super::*; + #[cfg(feature = "std")] #[test] - fn test_const_rand_disabled() { - assert_eq!(INIT_SEED, [0x2360_ED05_1FC6_5DA4, 0x4385_DF64_9FCC_F645]); + fn test_unique() { + let a = RandomState::new(); + let b = RandomState::new(); + assert_ne!(a.build_hasher().finish(), b.build_hasher().finish()); } #[test] fn test_with_seeds_const() { - const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19); + const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19, 21, 23); } } diff --git a/tests/nopanic.rs b/tests/nopanic.rs index f3d9361..884f118 100644 --- a/tests/nopanic.rs +++ b/tests/nopanic.rs @@ -36,8 +36,8 @@ fn hash_test_random_wrapper(num: i32, string: &str) { #[inline(never)] #[no_panic] fn hash_test_random(num: i32, string: &str) -> (u64, u64) { - let hasher1 = RandomState::with_seeds(1, 2).build_hasher(); - let hasher2 = RandomState::with_seeds(1, 2).build_hasher(); + let hasher1 = RandomState::with_seeds(1, 2, 3, 4).build_hasher(); + let hasher2 = RandomState::with_seeds(1, 2, 3, 4).build_hasher(); (num.get_hash(hasher1), string.as_bytes().get_hash(hasher2)) }