rapidhash/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5#![cfg_attr(docsrs, feature(doc_cfg_hide))]
6#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))]
7
8#[deny(missing_docs)]
9#[deny(unused_must_use)]
10
11mod rapid_const;
12mod rapid_hasher;
13mod rapid_hasher_inline;
14#[cfg(any(feature = "std", docsrs))]
15mod rapid_file;
16#[cfg(any(feature = "std", feature = "rand", docsrs))]
17mod random_state;
18mod rng;
19
20#[doc(inline)]
21pub use crate::rapid_const::{rapidhash, rapidhash_inline, rapidhash_seeded, RAPID_SEED};
22#[doc(inline)]
23pub use crate::rapid_hasher::*;
24#[doc(inline)]
25pub use crate::rapid_hasher_inline::*;
26#[doc(inline)]
27#[cfg(any(feature = "std", docsrs))]
28pub use crate::rapid_file::*;
29#[doc(inline)]
30#[cfg(any(feature = "std", feature = "rand", docsrs))]
31pub use crate::random_state::*;
32#[doc(inline)]
33pub use crate::rng::*;
34
35
36#[cfg(test)]
37mod tests {
38    extern crate std;
39
40    use std::hash::{Hash, Hasher};
41    use std::collections::BTreeSet;
42    use rand::Rng;
43    use super::*;
44
45    #[derive(Hash)]
46    struct Object {
47        bytes: std::vec::Vec<u8>,
48    }
49
50    /// Check the [rapidhash] oneshot function is equivalent to [RapidHasher]
51    #[test]
52    fn hasher_equivalent_to_oneshot() {
53        let hash = rapidhash(b"hello world");
54        assert_ne!(hash, 0);
55        assert_eq!(hash, 17498481775468162579);
56
57        let mut hasher = RapidHasher::default();
58        hasher.write(b"hello world");
59        assert_eq!(hasher.finish(), 17498481775468162579);
60
61        let hash = rapidhash(b"hello world!");
62        assert_eq!(hash, 12238759925102402976);
63    }
64
65    /// `#[derive(Hash)]` writes a length prefix first, check understanding.
66    #[test]
67    fn derive_hash_works() {
68        let object = Object { bytes: b"hello world".to_vec() };
69        let mut hasher = RapidHasher::default();
70        object.hash(&mut hasher);
71        assert_eq!(hasher.finish(), 3415994554582211120);
72
73        let mut hasher = RapidHasher::default();
74        hasher.write_usize(b"hello world".len());
75        hasher.write(b"hello world");
76        assert_eq!(hasher.finish(), 3415994554582211120);
77    }
78
79    /// Check RapidHasher is equivalent to the raw rapidhash for a single byte stream.
80    ///
81    /// Also check that the hash is unique for different byte streams.
82    #[test]
83    fn all_sizes() {
84        let mut hashes = BTreeSet::new();
85
86        for size in 0..=1024 {
87            let mut data = std::vec![0; size];
88            rand::rng().fill(data.as_mut_slice());
89
90            let hash1 = rapidhash(&data);
91            let mut hasher = RapidHasher::default();
92            hasher.write(&data);
93            let hash2 = hasher.finish();
94
95            assert_eq!(hash1, hash2, "Failed on size {}", size);
96            assert!(!hashes.contains(&hash1), "Duplicate for size {}", size);
97
98            hashes.insert(hash1);
99        }
100    }
101
102    /// Hardcoded hash values that are known to be correct.
103    #[test]
104    fn hashes_to_expected_values() {
105        assert_eq!(                                    rapidhash(0u128.to_le_bytes().as_slice()),  8755926293314635566);
106        assert_eq!(                                    rapidhash(1u128.to_le_bytes().as_slice()), 17996969877019643443);
107        assert_eq!(                               rapidhash(0x1000u128.to_le_bytes().as_slice()),  3752997491443908878);
108        assert_eq!(                          rapidhash(0x1000_0000u128.to_le_bytes().as_slice()),  1347028408682550078);
109        assert_eq!(                  rapidhash(0x10000000_00000000u128.to_le_bytes().as_slice()),  3593052489046108800);
110        assert_eq!(                  rapidhash(0x10000000_00000001u128.to_le_bytes().as_slice()),  7365235785575411947);
111        assert_eq!(         rapidhash(0x10000000_00000000_00000000u128.to_le_bytes().as_slice()),  5399386355486589714);
112        assert_eq!(rapidhash(0x10000000_00000000_00000000_00000000u128.to_le_bytes().as_slice()), 13365378750111633005);
113        assert_eq!(rapidhash(0xffffffff_ffffffff_ffffffff_ffffffffu128.to_le_bytes().as_slice()), 10466158564987642889);
114    }
115
116    /// Ensure that changing a single bit flips at least 10 bits in the resulting hash, and on
117    /// average flips half of the bits.
118    ///
119    /// These tests are not deterministic, but should fail with a very low probability.
120    #[test]
121    fn flip_bit_trial() {
122        use rand::Rng;
123
124        let mut flips = std::vec![];
125
126        for len in 1..=256 {
127            let mut data = std::vec![0; len];
128            rand::rng().fill(&mut data[..]);
129
130            let hash = rapidhash(&data);
131            for byte in 0..len {
132                for bit in 0..8 {
133                    let mut data = data.clone();
134                    data[byte] ^= 1 << bit;
135                    let new_hash = rapidhash(&data);
136                    assert_ne!(hash, new_hash, "Flipping bit {} did not change hash", byte);
137                    let xor = hash ^ new_hash;
138                    let flipped = xor.count_ones() as u64;
139                    assert!(xor.count_ones() >= 10, "Flipping bit {byte}:{bit} changed only {flipped} bits");
140
141                    flips.push(flipped);
142                }
143            }
144        }
145
146        let average = flips.iter().sum::<u64>() as f64 / flips.len() as f64;
147        assert!(average > 31.95 && average < 32.05, "Did not flip an average of half the bits. average: {average}, expected: 32.0");
148    }
149
150    /// Helper method for [flip_bit_trial_streaming]. Hashes a byte stream in u8 chunks.
151    fn streaming_hash(data: &[u8]) -> u64 {
152        let mut hasher = RapidHasher::default();
153        for byte in data {
154            hasher.write_u8(*byte);
155        }
156        hasher.finish()
157    }
158
159    /// The same as [flip_bit_trial], but against our streaming implementation, to ensure that
160    /// reusing the `a`, `b`, and `seed` state is not causing glaringly obvious issues.
161    ///
162    /// This test is not a substitute for SMHasher or similar.
163    ///
164    /// These tests are not deterministic, but should fail with a very low probability.
165    #[test]
166    fn flip_bit_trial_streaming() {
167        use rand::Rng;
168
169        let mut flips = std::vec![];
170
171        for len in 1..=256 {
172            let mut data = std::vec![0; len];
173            rand::rng().fill(&mut data[..]);
174
175            let hash = streaming_hash(&data);
176            for byte in 0..len {
177                for bit in 0..8 {
178                    let mut data = data.clone();
179                    data[byte] ^= 1 << bit;
180
181                    // check that the hash changed
182                    let new_hash = streaming_hash(&data);
183                    assert_ne!(hash, new_hash, "Flipping bit {} did not change hash", byte);
184
185                    // track how many bits were flipped
186                    let xor = hash ^ new_hash;
187                    let flipped = xor.count_ones() as u64;
188                    assert!(xor.count_ones() >= 10, "Flipping bit {byte}:{bit} changed only {flipped} bits");
189                    flips.push(flipped);
190                }
191            }
192        }
193
194        // check that on average half of the bits were flipped
195        let average = flips.iter().sum::<u64>() as f64 / flips.len() as f64;
196        assert!(average > 31.95 && average < 32.05, "Did not flip an average of half the bits. average: {average}, expected: 32.0");
197    }
198}