Skip to main content

rapidhash/inner/state/
global_state.rs

1use core::hash::BuildHasher;
2use core::fmt::Formatter;
3use crate::inner::RapidHasher;
4use crate::inner::seeding::secrets::GlobalSecrets;
5
6/// A [`BuildHasher`] that uses a global seed and secrets, randomized only once on startup.
7///
8/// The global seed and secrets are randomized on the first instantiation, and then every subsequent
9/// instance of GlobalState will re-use the same seed and secrets, ensuring consistent hash outputs
10/// for the duration of the program.
11///
12/// The one-time randomization is derived from OS/platform entropy when the `getrandom_04` feature is
13/// enabled, or the standard library's secure RNG when the `std` feature is enabled, falling back
14/// to ASLR and weaker entropy sources otherwise. Because every
15/// instance shares a single seed and secret set, `GlobalState` only offers minimal HashDoS
16/// resistance: an attacker cannot predict the secrets, but every map in the process shares the same
17/// collision structure. Prefer [`RandomState`](crate::fast::RandomState) when each map should be
18/// seeded independently.
19///
20/// See [`RandomState`](crate::fast::RandomState)'s portability docs for enabling true
21/// randomisation on wasm32 and embedded targets via the `getrandom` feature.
22///
23/// # Performance
24///
25/// `GlobalState` is faster and smaller than `RandomState` while still providing some minimal DoS
26/// resistance, and so may be preferable to `RandomState` when instantiating many hashmaps in a
27/// tight loop.
28///
29/// # Example
30/// ```rust
31/// use std::collections::HashMap;
32/// use std::hash::Hasher;
33///
34/// use rapidhash::fast::GlobalState;
35///
36/// let mut map = HashMap::with_hasher(GlobalState::default());
37/// map.insert(42, "the answer");
38/// ```
39#[derive(Copy, Clone, Eq, PartialEq)]
40pub struct GlobalState<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> {
41    /// The global secrets is a zero-sized type to keep HashMap<K, V, RandomState> small.
42    secrets: GlobalSecrets,
43}
44
45impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> GlobalState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
46    /// Create a new global state with a global seed and secrets.
47    ///
48    /// The seed and secrets are randomized once, on the first instantiation of any `GlobalState`,
49    /// and then cached for the lifetime of the program; all subsequent instances share them. With
50    /// the `std` feature the one-time randomization is derived from the standard library's secure
51    /// RNG; without `std` it falls back to ASLR and other weaker sources of entropy.
52    ///
53    /// Because every instance shares the same seed and secrets, all maps in the process share the
54    /// same collision structure. Use [`RandomState`](crate::fast::RandomState) if you need each map
55    /// to be seeded independently.
56    ///
57    /// On platforms which do not support atomic pointers, the secrets will be the default rapidhash
58    /// secrets, which are not randomized. Therefore, **`GlobalState` on targets without atomic
59    /// pointer support has no HashDoS resistance guarantees**: there is nowhere to store a
60    /// randomized value, so even the `getrandom_04` feature cannot help. Prefer
61    /// [`RandomState`](crate::fast::RandomState) with the `getrandom_04` feature on these targets,
62    /// which draws a fresh random seed per instance instead.
63    #[inline(always)]
64    pub fn new() -> Self {
65        Self {
66            secrets: GlobalSecrets::new(),
67        }
68    }
69}
70
71/// Warning that `GlobalState` only randomizes the seed on platforms that support atomic pointers.
72impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> Default for GlobalState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
73    #[inline(always)]
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool>  BuildHasher for GlobalState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
80    type Hasher = RapidHasher<'static, AVALANCHE, SPONGE, COMPACT, PROTECTED>;
81
82    #[inline(always)]
83    fn build_hasher(&self) -> Self::Hasher {
84        RapidHasher::new_precomputed_seed(
85            self.secrets.get_global_seed(),
86            self.secrets.get()
87        )
88    }
89}
90
91impl<const AVALANCHE: bool, const SPONGE: bool, const COMPACT: bool, const PROTECTED: bool> core::fmt::Debug for GlobalState<AVALANCHE, SPONGE, COMPACT, PROTECTED> {
92    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
93        f.debug_struct("GlobalState").finish_non_exhaustive()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use core::hash::BuildHasher;
100
101    type GlobalState = super::GlobalState<false, true, false, false>;
102
103    #[test]
104    fn test_global_state() {
105        let state1 = GlobalState::new();
106        let state2 = GlobalState::new();
107
108        let finish1a = state1.hash_one(b"hello");
109        let finish1b = state1.hash_one(b"hello");
110        let finish2a = state2.hash_one(b"hello");
111
112        assert_eq!(finish1a, finish1b);
113        assert_eq!(finish1a, finish2a);
114    }
115
116    #[test]
117    fn test_debug() {
118        extern crate alloc;
119        let state = GlobalState::new();
120        let debug_str = alloc::format!("{:?}", state);
121        assert_eq!(debug_str, "GlobalState { .. }");
122    }
123}