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}