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}