rapidhash/inner/state/random_state.rs
1use core::hash::{BuildHasher};
2use core::fmt::Formatter;
3use crate::inner::RapidHasher;
4use crate::inner::seeding::secrets::GlobalSecrets;
5
6/// A [`std::hash::RandomState`] compatible hasher that initializes a [`RapidHasher`] with a random
7/// seed and random global secrets.
8///
9/// This is designed to provide some HashDoS resistance by using a random seed per hashmap, and
10/// a global random set of secrets.
11///
12/// # Portability
13///
14/// On most target platforms, the secrets are randomly initialized once and cached globally for the
15/// lifetime of the program using a mix of ASLR and other entropy sources. The seed is randomly
16/// initialized for each new instance of `RandomState` using only ASLR and a mixing step.
17///
18/// On targets without atomic pointer support, the global secrets will not be randomized, and
19/// instead will fall back to the default secrets. This means these platforms will not have minimal
20/// HashDoS resistance guarantees. If this is important for your application, please raise a GitHub
21/// issue to improve support for these platforms.
22///
23/// # Example
24/// ```rust
25/// use std::collections::HashMap;
26/// use std::hash::Hasher;
27///
28/// use rapidhash::quality::RandomState;
29///
30/// let mut map = HashMap::with_hasher(RandomState::default());
31/// map.insert(42, "the answer");
32/// ```
33#[derive(Copy, Clone, Eq, PartialEq)]
34pub struct RandomState<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> {
35 seed: u64,
36
37 /// The global secrets is a zero-sized type to keep HashMap<K, V, RandomState> small.
38 secrets: GlobalSecrets,
39}
40
41impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
42 /// Create a new random state with a random seed.
43 ///
44 /// The seed is always randomized by using ASLR on every new instance of RandomState.
45 ///
46 /// With the `rand` feature enabled, the secrets will be randomized using [rand::random].
47 /// Otherwise, a mix of ASLR and some other poorer sources of entropy will be mixed together to
48 /// generate the secrets. The secrets are statically cached for the lifetime of the program
49 /// after their initial generation.
50 ///
51 /// On platforms that do not support atomic pointers, the secrets will be the default rapidhash
52 /// secrets, which are not randomized. Therefore, **targets without atomic pointer support will
53 /// not have minimal HashDoS resistance guarantees**.
54 #[inline]
55 pub fn new() -> Self {
56 Self {
57 seed: crate::inner::seeding::seed::get_seed(),
58 secrets: GlobalSecrets::new(),
59 }
60 }
61}
62
63/// Warning that `RandomState` only randomizes the seed on platforms that support atomic pointers.
64impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
65 #[inline]
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> BuildHasher for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
72 type Hasher = RapidHasher<'static, AVALANCHE, SPONGE, COMPACT, PROTECTED>;
73
74 #[inline(always)]
75 fn build_hasher(&self) -> Self::Hasher {
76 RapidHasher::new_precomputed_seed(self.seed, self.secrets.get())
77 }
78}
79
80impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> core::fmt::Debug for RandomState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
81 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
82 f.debug_struct("RandomState").finish_non_exhaustive()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use core::hash::BuildHasher;
89
90 type RandomState = super::RandomState<false, true, false, false>;
91
92 #[test]
93 fn test_random_state() {
94 assert_eq!(core::mem::size_of::<RandomState>(), 8);
95
96 let state1 = RandomState::new();
97 let state2 = RandomState::new();
98
99 let finish1a = state1.hash_one(b"hello");
100 let finish1b = state1.hash_one(b"hello");
101 let finish2a = state2.hash_one(b"hello");
102
103 assert_eq!(finish1a, finish1b);
104 assert_ne!(finish1a, finish2a);
105 }
106
107 #[test]
108 fn test_debug() {
109 extern crate alloc;
110 let state = RandomState::new();
111 let debug_str = alloc::format!("{:?}", state);
112 assert_eq!(debug_str, "RandomState { .. }");
113 }
114}