Skip to main content

graph_r/
hash.rs

1// Author: spicyzhug@gmail.com
2// Copyright: lituus-io, all rights reserved
3//
4//! The crate's single hash family (xxh3), exposed so dependants key and
5//! fingerprint with exactly the function behind [`crate::UrlKey`], section
6//! checksums and file trailers — one family across graph-r, link-r and their
7//! users, no second hashing crate. Thin wrappers: the underlying crate is an
8//! implementation detail and never appears in this API.
9
10/// xxh3-64 of `bytes` (what [`crate::UrlKey::of`] applies to a URL).
11#[must_use]
12#[inline]
13pub fn xxh3_64(bytes: &[u8]) -> u64 {
14    xxhash_rust::xxh3::xxh3_64(bytes)
15}
16
17/// xxh3-64 of `bytes` with a seed (independent hash functions for sketches).
18#[must_use]
19#[inline]
20pub fn xxh3_64_seeded(bytes: &[u8], seed: u64) -> u64 {
21    xxhash_rust::xxh3::xxh3_64_with_seed(bytes, seed)
22}
23
24/// xxh3-128 of `bytes` (fingerprints where 64-bit collisions matter).
25#[must_use]
26#[inline]
27pub fn xxh3_128(bytes: &[u8]) -> u128 {
28    xxhash_rust::xxh3::xxh3_128(bytes)
29}
30
31/// `MurmurHash3`'s 64-bit finaliser: full avalanche of an already-mixed
32/// word, for combining digests.
33#[must_use]
34#[inline]
35pub fn fmix64(mut k: u64) -> u64 {
36    k ^= k >> 33;
37    k = k.wrapping_mul(0xFF51_AFD7_ED55_8CCD);
38    k ^= k >> 33;
39    k = k.wrapping_mul(0xC4CE_B9FE_1A85_EC53);
40    k ^ (k >> 33)
41}
42
43/// The 24-bit key of the byte trigram `w[..3]` (exact, collision-free).
44/// Panics if `w` is shorter than three bytes.
45#[must_use]
46#[inline]
47pub fn trigram(w: &[u8]) -> u32 {
48    u32::from(w[0]) << 16 | u32::from(w[1]) << 8 | u32::from(w[2])
49}
50
51/// Streaming xxh3: feed pieces with [`Hasher::update`]; the digests equal
52/// the one-shot functions over the concatenation.
53#[derive(Clone)]
54pub struct Hasher(xxhash_rust::xxh3::Xxh3);
55
56impl std::fmt::Debug for Hasher {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.write_str("Hasher")
59    }
60}
61
62impl Default for Hasher {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl Hasher {
69    /// An unseeded hasher.
70    #[must_use]
71    pub fn new() -> Self {
72        Self(xxhash_rust::xxh3::Xxh3::new())
73    }
74
75    /// A seeded hasher (matches [`xxh3_64_seeded`]).
76    #[must_use]
77    pub fn with_seed(seed: u64) -> Self {
78        Self(xxhash_rust::xxh3::Xxh3::with_seed(seed))
79    }
80
81    /// Feed bytes.
82    #[inline]
83    pub fn update(&mut self, bytes: &[u8]) {
84        self.0.update(bytes);
85    }
86
87    /// Feed one length-delimited field (its length, then its bytes), so
88    /// adjacent fields can never run into each other (`"ab","c"` ≠ `"a","bc"`).
89    #[inline]
90    pub fn field(&mut self, bytes: &[u8]) {
91        self.update(&(bytes.len() as u64).to_le_bytes());
92        self.update(bytes);
93    }
94
95    /// 64-bit digest of everything fed so far (the hasher stays usable).
96    #[must_use]
97    pub fn digest64(&self) -> u64 {
98        self.0.digest()
99    }
100
101    /// 128-bit digest of everything fed so far.
102    #[must_use]
103    pub fn digest128(&self) -> u128 {
104        self.0.digest128()
105    }
106
107    /// Forget everything fed, keeping the seed.
108    pub fn reset(&mut self) {
109        self.0.reset();
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn one_shot_matches_url_key() {
119        assert_eq!(xxh3_64(b"https://x.dev/a"), crate::UrlKey::of("https://x.dev/a").raw());
120    }
121
122    #[test]
123    fn streaming_equals_one_shot() {
124        let mut h = Hasher::new();
125        h.update(b"hello ");
126        h.update(b"world");
127        assert_eq!(h.digest64(), xxh3_64(b"hello world"));
128        assert_eq!(h.digest128(), xxh3_128(b"hello world"));
129        h.reset();
130        assert_eq!(h.digest64(), xxh3_64(b""));
131    }
132
133    #[test]
134    fn seeds_give_independent_functions() {
135        assert_ne!(xxh3_64_seeded(b"v", 1), xxh3_64_seeded(b"v", 2));
136        let mut h = Hasher::with_seed(7);
137        h.update(b"v");
138        assert_eq!(h.digest64(), xxh3_64_seeded(b"v", 7));
139    }
140}