Skip to main content

delta_struct/
fingerprint.rs

1//! Stable content hashing, so a receiver can check that a delta is being
2//! applied to the state it was computed against.
3//!
4//! [`std::hash::Hash`] cannot do this job for two reasons. It is not
5//! implemented for [`HashSet`] or [`HashMap`], which are important
6//! collections for the `unordered` field type, and the hash it feeds a
7//! [`DefaultHasher`](std::collections::hash_map::DefaultHasher) is explicitly
8//! allowed to change between Rust releases — fine for a hash table that lives
9//! and dies in one process, useless for a value two processes have to agree
10//! on.
11//!
12//! [`Fingerprint`] fixes both. Sets and maps are folded commutatively so
13//! iteration order cannot matter, and [`Hasher`] is FNV-1a with the constants
14//! written down here, so the same value fingerprints identically on any
15//! platform, any Rust version, forever.
16//!
17//! Derive it rather than writing it:
18//!
19//! ```
20//! use delta_struct::{fingerprint_of, Fingerprint};
21//! use std::collections::HashSet;
22//!
23//! #[derive(Fingerprint)]
24//! struct Device {
25//!     services: HashSet<String>,
26//!     online: bool,
27//! }
28//!
29//! let device = |online| Device {
30//!     services: vec!["ssh".to_string(), "http".to_string()].into_iter().collect(),
31//!     online,
32//! };
33//!
34//! // Set iteration order does not reach the fingerprint.
35//! assert_eq!(fingerprint_of(&device(true)), fingerprint_of(&device(true)));
36//! assert_ne!(fingerprint_of(&device(true)), fingerprint_of(&device(false)));
37//! ```
38
39use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
40
41/// The FNV-1a 64-bit offset basis.
42const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
43/// The FNV-1a 64-bit prime.
44const PRIME: u64 = 0x0000_0100_0000_01b3;
45
46/// The hasher [`Fingerprint`] writes into: FNV-1a, 64-bit.
47///
48/// Deliberately not [`std::hash::Hasher`]. That trait's implementations are
49/// free to change their output between Rust releases, which would turn a
50/// toolchain upgrade on one side of a connection into a stream of spurious
51/// mismatches. This one is pinned to the constants above and will not move.
52///
53/// It is not a cryptographic hash and is not meant to survive an adversary —
54/// it exists to catch accidental divergence.
55#[derive(Clone, Debug)]
56pub struct Hasher {
57    state: u64,
58}
59
60impl Hasher {
61    /// Starts a new hasher at the offset basis.
62    pub fn new() -> Self {
63        Hasher {
64            state: OFFSET_BASIS,
65        }
66    }
67
68    /// Folds `bytes` into the hash.
69    pub fn write(&mut self, bytes: &[u8]) {
70        for byte in bytes {
71            self.state ^= u64::from(*byte);
72            self.state = self.state.wrapping_mul(PRIME);
73        }
74    }
75
76    /// Folds a whole sub-hash in, for combining nested fingerprints.
77    pub fn write_u64(&mut self, value: u64) {
78        self.write(&value.to_le_bytes());
79    }
80
81    /// The hash so far.
82    pub fn finish(&self) -> u64 {
83        self.state
84    }
85}
86
87impl Default for Hasher {
88    fn default() -> Self {
89        Hasher::new()
90    }
91}
92
93/// A value whose contents can be reduced to a number that two processes will
94/// agree on.
95///
96/// Derive it with `#[derive(Fingerprint)]`, which walks a struct's fields in
97/// declaration order, or an enum's variant index followed by its fields. Every
98/// field's type has to implement it too.
99pub trait Fingerprint {
100    /// Folds `self` into `hasher`.
101    fn fingerprint(&self, hasher: &mut Hasher);
102}
103
104/// Fingerprints a value on its own, which is what you usually want.
105///
106/// ```
107/// use delta_struct::fingerprint_of;
108///
109/// assert_eq!(fingerprint_of(&"hello"), fingerprint_of(&"hello"));
110/// assert_ne!(fingerprint_of(&"hello"), fingerprint_of(&"hellp"));
111/// ```
112pub fn fingerprint_of<T: Fingerprint + ?Sized>(value: &T) -> u64 {
113    let mut hasher = Hasher::new();
114    value.fingerprint(&mut hasher);
115    hasher.finish()
116}
117
118macro_rules! fingerprint_le_bytes {
119    ($($ty:ty),* $(,)?) => {
120        $(
121            impl Fingerprint for $ty {
122                fn fingerprint(&self, hasher: &mut Hasher) {
123                    hasher.write(&self.to_le_bytes());
124                }
125            }
126        )*
127    };
128}
129
130fingerprint_le_bytes!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
131
132macro_rules! fingerprint_widened {
133    ($($ty:ty => $wide:ty),* $(,)?) => {
134        $(
135            impl Fingerprint for $ty {
136                fn fingerprint(&self, hasher: &mut Hasher) {
137                    // Widened so that a 32-bit sender and a 64-bit receiver
138                    // agree on the same value.
139                    <$wide as Fingerprint>::fingerprint(&(*self as $wide), hasher);
140                }
141            }
142        )*
143    };
144}
145
146fingerprint_widened!(usize => u64, isize => i64);
147
148impl Fingerprint for bool {
149    fn fingerprint(&self, hasher: &mut Hasher) {
150        hasher.write(&[u8::from(*self)]);
151    }
152}
153
154impl Fingerprint for char {
155    fn fingerprint(&self, hasher: &mut Hasher) {
156        u32::from(*self).fingerprint(hasher);
157    }
158}
159
160/// Floats fingerprint by their bit pattern, so `NaN` matches itself and `0.0`
161/// does not match `-0.0` — the opposite of what `==` says in both cases. The
162/// question a fingerprint answers is "are these the same state?", not "are
163/// these numerically equal?".
164impl Fingerprint for f32 {
165    fn fingerprint(&self, hasher: &mut Hasher) {
166        self.to_bits().fingerprint(hasher);
167    }
168}
169
170impl Fingerprint for f64 {
171    fn fingerprint(&self, hasher: &mut Hasher) {
172        self.to_bits().fingerprint(hasher);
173    }
174}
175
176impl Fingerprint for str {
177    fn fingerprint(&self, hasher: &mut Hasher) {
178        // Length first, so that ("ab", "c") cannot collide with ("a", "bc").
179        hasher.write_u64(self.len() as u64);
180        hasher.write(self.as_bytes());
181    }
182}
183
184impl Fingerprint for String {
185    fn fingerprint(&self, hasher: &mut Hasher) {
186        self.as_str().fingerprint(hasher);
187    }
188}
189
190impl<T: Fingerprint + ?Sized> Fingerprint for &T {
191    fn fingerprint(&self, hasher: &mut Hasher) {
192        (**self).fingerprint(hasher);
193    }
194}
195
196impl<T: Fingerprint + ?Sized> Fingerprint for Box<T> {
197    fn fingerprint(&self, hasher: &mut Hasher) {
198        (**self).fingerprint(hasher);
199    }
200}
201
202impl<T: Fingerprint> Fingerprint for Option<T> {
203    fn fingerprint(&self, hasher: &mut Hasher) {
204        match self {
205            None => hasher.write(&[0]),
206            Some(value) => {
207                hasher.write(&[1]);
208                value.fingerprint(hasher);
209            }
210        }
211    }
212}
213
214impl<T: Fingerprint, E: Fingerprint> Fingerprint for Result<T, E> {
215    fn fingerprint(&self, hasher: &mut Hasher) {
216        match self {
217            Ok(value) => {
218                hasher.write(&[0]);
219                value.fingerprint(hasher);
220            }
221            Err(error) => {
222                hasher.write(&[1]);
223                error.fingerprint(hasher);
224            }
225        }
226    }
227}
228
229impl<T: Fingerprint> Fingerprint for [T] {
230    fn fingerprint(&self, hasher: &mut Hasher) {
231        hasher.write_u64(self.len() as u64);
232        for item in self {
233            item.fingerprint(hasher);
234        }
235    }
236}
237
238impl<T: Fingerprint> Fingerprint for Vec<T> {
239    fn fingerprint(&self, hasher: &mut Hasher) {
240        self.as_slice().fingerprint(hasher);
241    }
242}
243
244impl Fingerprint for () {
245    fn fingerprint(&self, _hasher: &mut Hasher) {}
246}
247
248macro_rules! fingerprint_tuples {
249    ($(($($index:tt $param:ident),+))+) => {
250        $(
251            impl<$($param: Fingerprint),+> Fingerprint for ($($param,)+) {
252                fn fingerprint(&self, hasher: &mut Hasher) {
253                    $(self.$index.fingerprint(hasher);)+
254                }
255            }
256        )+
257    };
258}
259
260fingerprint_tuples! {
261    (0 A)
262    (0 A, 1 B)
263    (0 A, 1 B, 2 C)
264    (0 A, 1 B, 2 C, 3 D)
265    (0 A, 1 B, 2 C, 3 D, 4 E)
266    (0 A, 1 B, 2 C, 3 D, 4 E, 5 F)
267}
268
269/// Folds each element's own fingerprint together with `^`, so the order they
270/// come out of the collection in cannot reach the result.
271///
272/// The length goes in too, which is what stops a set from colliding with a
273/// differently-sized one whose element hashes happen to cancel.
274fn fingerprint_unordered<I, F>(hasher: &mut Hasher, len: usize, items: I, mut each: F)
275where
276    F: FnMut(&mut Hasher, I::Item),
277    I: Iterator,
278{
279    let mut combined = 0u64;
280    for item in items {
281        let mut element = Hasher::new();
282        each(&mut element, item);
283        combined ^= element.finish();
284    }
285    hasher.write_u64(len as u64);
286    hasher.write_u64(combined);
287}
288
289impl<T: Fingerprint, S> Fingerprint for HashSet<T, S> {
290    fn fingerprint(&self, hasher: &mut Hasher) {
291        fingerprint_unordered(hasher, self.len(), self.iter(), |h, item| {
292            item.fingerprint(h)
293        });
294    }
295}
296
297impl<T: Fingerprint> Fingerprint for BTreeSet<T> {
298    fn fingerprint(&self, hasher: &mut Hasher) {
299        fingerprint_unordered(hasher, self.len(), self.iter(), |h, item| {
300            item.fingerprint(h)
301        });
302    }
303}
304
305impl<K: Fingerprint, V: Fingerprint, S> Fingerprint for HashMap<K, V, S> {
306    fn fingerprint(&self, hasher: &mut Hasher) {
307        fingerprint_unordered(hasher, self.len(), self.iter(), |h, (key, value)| {
308            key.fingerprint(h);
309            value.fingerprint(h);
310        });
311    }
312}
313
314impl<K: Fingerprint, V: Fingerprint> Fingerprint for BTreeMap<K, V> {
315    fn fingerprint(&self, hasher: &mut Hasher) {
316        fingerprint_unordered(hasher, self.len(), self.iter(), |h, (key, value)| {
317            key.fingerprint(h);
318            value.fingerprint(h);
319        });
320    }
321}