Skip to main content

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}