Skip to main content

rapidhash/v1/
rapid_const.rs

1use crate::util::mix::{rapid_mix, rapid_mum};
2use crate::util::read::{read_u32_combined, read_u64};
3use super::{DEFAULT_RAPID_SECRETS, RapidSecrets};
4
5/// Rapidhash a single byte stream, matching the C++ implementation.
6#[inline]
7pub const fn rapidhash_v1(data: &[u8]) -> u64 {
8    rapidhash_v1_inline::<true, false, false, false>(data, &DEFAULT_RAPID_SECRETS)
9}
10
11/// Rapidhash a single byte stream, matching the C++ implementation, with a custom seed.
12#[inline]
13pub const fn rapidhash_v1_seeded(data: &[u8], secrets: &RapidSecrets) -> u64 {
14    rapidhash_v1_inline::<true, false, false, false>(data, secrets)
15}
16
17/// Rapidhash a single byte stream, matching the C++ implementation.
18///
19/// Is marked with `#[inline(always)]` to force the compiler to inline and optimize the method.
20/// Can provide large performance uplifts for inputs where the length is known at compile time.
21///
22/// Compile time arguments:
23/// - `AVALANCHE`: Perform an extra mix step to avalanche the bits for higher hash quality. Enabled
24///   by default to match the C++ implementation.
25/// - `COMPACT`: Generates fewer instructions at compile time with less manual loop unrolling, but
26///   may be slower on some platforms. Disabled by default.
27/// - `PROTECTED`: Slightly stronger hash quality and DoS resistance by performing two extra XOR
28///   instructions on every mix step. Disabled by default.
29/// - `V1_BUG`: True to re-introduce the bug that was present on 48 byte length inputs in the
30///   1.x crate versions for backwards compatibility with the old rust implementation.
31#[inline(always)]
32pub const fn rapidhash_v1_inline<const AVALANCHE: bool, const COMPACT: bool, const PROTECTED: bool, const V1_BUG: bool>(data: &[u8], secrets: &RapidSecrets) -> u64 {
33    rapidhash_core::<AVALANCHE, COMPACT, PROTECTED, V1_BUG>(secrets.seed, &secrets.secrets, data)
34}
35
36#[inline(always)]
37pub(super) const fn rapidhash_core<const AVALANCHE: bool, const COMPACT: bool, const PROTECTED: bool, const V1_BUG: bool>(mut seed: u64, secrets: &[u64; 3], data: &[u8]) -> u64 {
38    let mut a = 0;
39    let mut b = 0;
40
41    seed ^= data.len() as u64;
42
43    if data.len() <= 16 {
44        // deviation from the C++ impl computes delta as follows
45        // let delta = (data.len() & 24) >> (data.len() >> 3);
46        // this is equivalent to "match {..8=>0, 8..=>4}"
47        // and so using the extra if-else statement is equivalent and allows the compiler to skip
48        // some unnecessary bounds checks while still being safe rust.
49        if data.len() >= 8 {
50            // len is 4..=16
51            let plast = data.len() - 4;
52            let delta = 4;
53            a ^= read_u32_combined(data, 0, plast);
54            b ^= read_u32_combined(data, delta, plast - delta);
55        } else if data.len() >= 4 {
56            let plast = data.len() - 4;
57            let delta = 0;
58            a ^= read_u32_combined(data, 0, plast);
59            b ^= read_u32_combined(data, delta, plast - delta);
60        } else if !data.is_empty() {
61            // len is 1..=3
62            let len = data.len();
63            a ^= ((data[0] as u64) << 56) | ((data[len >> 1] as u64) << 32) | data[len - 1] as u64;
64            // b = 0;
65        }
66    } else {
67        let mut slice = data;
68
69        // the v1.x.x versions of rapidhash had a bug where this if statement was omitted, which
70        // caused the hash to be incorrect for 48 byte inputs. The v2.x.x versions of this crate
71        // incorrectly handled the V1_BUG... The v3.x.x versions of this crate should now match
72        // the buggy v1.x.x crate version when V1_BUG=true. Kicking myself for this one.
73        if slice.len() > 48 || V1_BUG {
74            // most CPUs appear to benefit from this unrolled loop
75            let mut see1 = seed;
76            let mut see2 = seed;
77
78            if !COMPACT {
79                while slice.len() >= 96 {
80                    seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
81                    see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
82                    see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
83                    seed = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ secrets[0], read_u64(slice, 56) ^ seed);
84                    see1 = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ secrets[1], read_u64(slice, 72) ^ see1);
85                    see2 = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ secrets[2], read_u64(slice, 88) ^ see2);
86                    let (_, split) = slice.split_at(96);
87                    slice = split;
88                }
89                if slice.len() >= 48 {
90                    seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
91                    see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
92                    see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
93                    let (_, split) = slice.split_at(48);
94                    slice = split;
95                }
96            } else {
97                while slice.len() >= 48 {
98                    seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ seed);
99                    see1 = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ see1);
100                    see2 = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ see2);
101                    let (_, split) = slice.split_at(48);
102                    slice = split;
103                }
104            }
105            seed ^= see1 ^ see2;
106        }
107
108        if slice.len() > 16 {
109            seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[2], read_u64(slice, 8) ^ seed ^ secrets[1]);
110            if slice.len() > 32 {
111                seed = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[2], read_u64(slice, 24) ^ seed);
112            }
113        }
114
115        a ^= read_u64(data, data.len() - 16);
116        b ^= read_u64(data, data.len() - 8);
117    }
118
119    a ^= secrets[1];
120    b ^= seed;
121
122    let (a2, b2) = rapid_mum::<PROTECTED>(a, b);
123    a = a2;
124    b = b2;
125
126    if AVALANCHE {
127        rapidhash_finish::<PROTECTED>(a, b, data.len() as u64, secrets)
128    } else {
129        a ^ b
130    }
131}
132
133#[inline(always)]
134pub(super) const fn rapidhash_finish<const PROTECTED: bool>(a: u64, b: u64, len: u64, secrets: &[u64; 3]) -> u64 {
135    rapid_mix::<PROTECTED>(a ^ secrets[0] ^ len, b ^ secrets[1])
136}