Skip to main content

shuyi_assets/
fnv.rs

1//! The word-wise FNV-1a hash the pack's checksum and a bundle's
2//! fingerprint share: FNV-1a's constants over 64-bit words (the last few
3//! bytes one at a time), each step followed by a shift that folds the
4//! high bits into the low ones. Each step is a bijection of the hash so
5//! far, so any one word changed changes it. Every integer is a `u64`,
6//! whatever the platform. A frame's snapshot hashes a painted picture with
7//! it too (ADR-0078).
8
9/// The 64-bit FNV-1a offset basis: where a hash starts.
10pub(crate) const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
11
12/// The 64-bit FNV-1a prime.
13const PRIME: u64 = 0x0100_0000_01b3;
14
15/// A running hash.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Hasher(pub(crate) u64);
18
19impl Hasher {
20    /// A hash at FNV-1a's offset basis.
21    pub const fn new() -> Self {
22        Self(OFFSET)
23    }
24
25    /// Takes one word.
26    pub fn word(&mut self, word: u64) {
27        let hash = (self.0 ^ word).wrapping_mul(PRIME);
28        self.0 = hash ^ (hash >> 32);
29    }
30
31    /// Takes `bytes`: each whole 8 bytes as a little-endian word, then the
32    /// rest one byte a word. Not their length: a caller that needs it
33    /// takes it too.
34    pub fn bytes(&mut self, bytes: &[u8]) {
35        let (words, rest) = bytes.as_chunks::<8>();
36        for word in words {
37            self.word(u64::from_le_bytes(*word));
38        }
39        for &b in rest {
40            self.word(u64::from(b));
41        }
42    }
43
44    /// Takes `bytes`' length, then `bytes`: so two runs of bytes taken one
45    /// after the other never read as another two.
46    pub fn sized(&mut self, bytes: &[u8]) {
47        self.word(bytes.len() as u64);
48        self.bytes(bytes);
49    }
50
51    /// The hash of what it has taken.
52    pub const fn finish(self) -> u64 {
53        self.0
54    }
55}
56
57impl Default for Hasher {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn finish_gives_the_hash_so_far() {
69        assert_eq!(Hasher::new().finish(), OFFSET);
70        assert_eq!(Hasher::default().finish(), OFFSET);
71        let mut one = Hasher::new();
72        one.word(1);
73        let mut two = Hasher::new();
74        two.word(2);
75        assert_ne!(one.finish(), two.finish());
76        assert_eq!(one.finish(), one.0);
77    }
78}