Skip to main content

postcard_schema/key/
hash.rs

1//! URI and Schema Hashing
2//!
3//! We use `FNV1a` hashes with a digest size of 64 bits to represent dispatch keys.
4//!
5//! Unfortunately. using [core::hash::Hash] seems to not produce consistent results,
6//! which [was noted] in the docs. To overcome this, we implement a custom method for
7//! hashing the postcard [Schema].
8//!
9//! [was noted]: https://doc.rust-lang.org/stable/std/hash/trait.Hash.html#portability
10
11use crate::{
12    schema::{DataModelType, NamedType, NamedValue, NamedVariant},
13    Schema,
14};
15
16/// A const compatible Fnv1a64 hasher
17pub struct Fnv1a64Hasher {
18    state: u64,
19}
20
21impl Fnv1a64Hasher {
22    // source: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
23    const BASIS: u64 = 0xcbf2_9ce4_8422_2325;
24    const PRIME: u64 = 0x0000_0100_0000_01b3;
25
26    /// Create a new hasher with the default basis as state contents
27    pub fn new() -> Self {
28        Self { state: Self::BASIS }
29    }
30
31    /// Calculate the hash for each of the given data bytes
32    pub fn update(&mut self, data: &[u8]) {
33        for b in data {
34            let ext = u64::from(*b);
35            self.state ^= ext;
36            self.state = self.state.wrapping_mul(Self::PRIME);
37        }
38    }
39
40    /// Extract the current state for finalizing the hash
41    pub fn digest(self) -> u64 {
42        self.state
43    }
44
45    /// Same as digest but as bytes
46    pub fn digest_bytes(self) -> [u8; 8] {
47        self.digest().to_le_bytes()
48    }
49}
50
51impl Default for Fnv1a64Hasher {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57pub mod fnv1a64 {
58    //! Const and no-std helper methods and types for perfoming hash calculation
59    use crate::schema::DataModelVariant;
60
61    use super::*;
62
63    /// Calculate the Key hash for the given path and type T
64    pub const fn hash_ty_path<T: Schema + ?Sized>(path: &str) -> [u8; 8] {
65        let schema = T::SCHEMA;
66        let state = hash_update_str(Fnv1a64Hasher::BASIS, path);
67        hash_named_type(state, schema).to_le_bytes()
68    }
69
70    pub(crate) const fn hash_update(mut state: u64, bytes: &[u8]) -> u64 {
71        let mut idx = 0;
72        while idx < bytes.len() {
73            let ext = bytes[idx] as u64;
74            state ^= ext;
75            state = state.wrapping_mul(Fnv1a64Hasher::PRIME);
76            idx += 1;
77        }
78        state
79    }
80
81    pub(crate) const fn hash_update_str(state: u64, s: &str) -> u64 {
82        hash_update(state, s.as_bytes())
83    }
84
85    const fn hash_sdm_type(state: u64, sdmty: &'static DataModelType) -> u64 {
86        // The actual values we use here don't matter that much (as far as I know),
87        // as long as the values for each variant are unique. I am unsure of the
88        // implications of doing a TON of single byte calls to `update`, it may be
89        // worth doing some buffering, and only calling update every 4/8/16 bytes
90        // instead, if performance is a concern.
91        //
92        // As of initial implementation, I'm mostly concerned with "does it work",
93        // as hashing is typically only done on startup.
94        //
95        // Using all primes that fit into a single byte:
96        //
97        // all_primes = [
98        //     0x02, 0x03, 0x05, 0x07, 0x0B, 0x0D, 0x11, 0x13,
99        //     0x17, 0x1D, 0x1F, 0x25, 0x29, 0x2B, 0x2F, 0x35,
100        //     0x3B, 0x3D, 0x43, 0x47, 0x49, 0x4F, 0x53, 0x59,
101        //     0x61, 0x65, 0x67, 0x6B, 0x6D, 0x71, 0x7F, 0x83,
102        //     0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD,
103        //     0xB3, 0xB5, 0xBF, 0xC1, 0xC5, 0xC7, 0xD3, 0xDF,
104        //     0xE3, 0xE5, 0xE9, 0xEF, 0xF1, 0xFB,
105        // ];
106        // shuffled_primes = [
107        //     0x11, 0xC5, 0x3D, 0x95, 0x1D, 0x0D, 0x0B, 0x02,
108        //     0x83, 0xD3, 0x13, 0x8B, 0x6B, 0xAD, 0xEF, 0x71,
109        //     0xC1, 0x25, 0x65, 0x6D, 0x47, 0xBF, 0xB5, 0x9D,
110        //     0xDF, 0x03, 0xA7, 0x05, 0xC7, 0x4F, 0x7F, 0x67,
111        //     0xE9, 0xB3, 0xE5, 0x2B, 0x97, 0xFB, 0x61, 0x3B,
112        //     0x1F, 0xA3, 0x35, 0x43, 0x89, 0x49, 0xE3, 0x07,
113        //     0x53, 0xF1, 0x17, 0x2F, 0x29, 0x59,
114        // ];
115        match sdmty {
116            DataModelType::Bool => hash_update(state, &[0x11]),
117            DataModelType::I8 => hash_update(state, &[0xC5]),
118            DataModelType::U8 => hash_update(state, &[0x3D]),
119            DataModelType::I16 => hash_update(state, &[0x1D]),
120            DataModelType::I32 => hash_update(state, &[0x0D]),
121            DataModelType::I64 => hash_update(state, &[0x0B]),
122            DataModelType::I128 => hash_update(state, &[0x02]),
123            DataModelType::U16 => hash_update(state, &[0x83]),
124            DataModelType::U32 => hash_update(state, &[0xD3]),
125            DataModelType::U64 => hash_update(state, &[0x13]),
126            DataModelType::U128 => hash_update(state, &[0x8B]),
127            DataModelType::Usize => hash_update(state, &[0x6B]),
128            DataModelType::Isize => hash_update(state, &[0xAD]),
129            DataModelType::F32 => hash_update(state, &[0xEF]),
130            DataModelType::F64 => hash_update(state, &[0x71]),
131            DataModelType::Char => hash_update(state, &[0xC1]),
132            DataModelType::String => hash_update(state, &[0x25]),
133            DataModelType::ByteArray => hash_update(state, &[0x65]),
134            DataModelType::Option(nt) => {
135                let state = hash_update(state, &[0x6D]);
136                hash_named_type(state, nt)
137            }
138            DataModelType::Unit => hash_update(state, &[0x47]),
139            DataModelType::UnitStruct => hash_update(state, &[0xBF]),
140            DataModelType::NewtypeStruct(nt) => {
141                let state = hash_update(state, &[0x9D]);
142                hash_named_type(state, nt)
143            }
144            DataModelType::Seq(nt) => {
145                let state = hash_update(state, &[0x03]);
146                hash_named_type(state, nt)
147            }
148            DataModelType::Tuple(nts) => {
149                let mut state = hash_update(state, &[0xA7]);
150                let mut idx = 0;
151                while idx < nts.len() {
152                    state = hash_named_type(state, nts[idx]);
153                    idx += 1;
154                }
155                state
156            }
157            DataModelType::TupleStruct(nts) => {
158                let mut state = hash_update(state, &[0x05]);
159                let mut idx = 0;
160                while idx < nts.len() {
161                    state = hash_named_type(state, nts[idx]);
162                    idx += 1;
163                }
164                state
165            }
166            DataModelType::Map { key, val } => {
167                let state = hash_update(state, &[0x4F]);
168                let state = hash_named_type(state, key);
169                hash_named_type(state, val)
170            }
171            DataModelType::Struct(nvs) => {
172                let mut state = hash_update(state, &[0x7F]);
173                let mut idx = 0;
174                while idx < nvs.len() {
175                    state = hash_named_value(state, nvs[idx]);
176                    idx += 1;
177                }
178                state
179            }
180            DataModelType::Enum(nvs) => {
181                let mut state = hash_update(state, &[0xE9]);
182                let mut idx = 0;
183                while idx < nvs.len() {
184                    state = hash_named_variant(state, nvs[idx]);
185                    idx += 1;
186                }
187                state
188            }
189            DataModelType::Schema => hash_update(state, &[0xB3]),
190        }
191    }
192
193    const fn hash_named_type(state: u64, nt: &NamedType) -> u64 {
194        // NOTE: We do *not* hash the name of the type in hashv2. This
195        // is to allow "safe" type punning, e.g. treating `Vec<u8>` and
196        // `&[u8]` as compatible types, when talking between std and no-std
197        // targets
198        //
199        // let state = hash_update(state, nt.name.as_bytes());
200        hash_sdm_type(state, nt.ty)
201    }
202
203    const fn hash_named_variant(state: u64, nt: &NamedVariant) -> u64 {
204        let state = hash_update(state, nt.name.as_bytes());
205        match nt.ty {
206            DataModelVariant::UnitVariant => hash_update(state, &[0xB5]),
207            DataModelVariant::NewtypeVariant(nt) => {
208                let state = hash_update(state, &[0xDF]);
209                hash_named_type(state, nt)
210            }
211            DataModelVariant::TupleVariant(nts) => {
212                let mut state = hash_update(state, &[0xC7]);
213                let mut idx = 0;
214                while idx < nts.len() {
215                    state = hash_named_type(state, nts[idx]);
216                    idx += 1;
217                }
218                state
219            }
220            DataModelVariant::StructVariant(nvs) => {
221                let mut state = hash_update(state, &[0x67]);
222                let mut idx = 0;
223                while idx < nvs.len() {
224                    state = hash_named_value(state, nvs[idx]);
225                    idx += 1;
226                }
227                state
228            }
229        }
230    }
231
232    const fn hash_named_value(state: u64, nt: &NamedValue) -> u64 {
233        let state = hash_update(state, nt.name.as_bytes());
234        hash_named_type(state, nt.ty)
235    }
236}
237
238#[cfg(feature = "use-std")]
239pub mod fnv1a64_owned {
240    //! Heapful helpers and versions of hashing for use on `std` targets
241
242    use crate::schema::owned::{
243        OwnedDataModelType, OwnedDataModelVariant, OwnedNamedType, OwnedNamedValue,
244        OwnedNamedVariant,
245    };
246
247    use super::fnv1a64::*;
248    use super::*;
249
250    /// Calculate the Key hash for the given path and OwnedNamedType
251    pub fn hash_ty_path_owned(path: &str, nt: &OwnedNamedType) -> [u8; 8] {
252        let state = hash_update_str(Fnv1a64Hasher::BASIS, path);
253        hash_named_type_owned(state, nt).to_le_bytes()
254    }
255
256    fn hash_sdm_type_owned(state: u64, sdmty: &OwnedDataModelType) -> u64 {
257        // The actual values we use here don't matter that much (as far as I know),
258        // as long as the values for each variant are unique. I am unsure of the
259        // implications of doing a TON of single byte calls to `update`, it may be
260        // worth doing some buffering, and only calling update every 4/8/16 bytes
261        // instead, if performance is a concern.
262        //
263        // As of initial implementation, I'm mostly concerned with "does it work",
264        // as hashing is typically only done on startup.
265        //
266        // Using all primes that fit into a single byte:
267        //
268        // all_primes = [
269        //     0x02, 0x03, 0x05, 0x07, 0x0B, 0x0D, 0x11, 0x13,
270        //     0x17, 0x1D, 0x1F, 0x25, 0x29, 0x2B, 0x2F, 0x35,
271        //     0x3B, 0x3D, 0x43, 0x47, 0x49, 0x4F, 0x53, 0x59,
272        //     0x61, 0x65, 0x67, 0x6B, 0x6D, 0x71, 0x7F, 0x83,
273        //     0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD,
274        //     0xB3, 0xB5, 0xBF, 0xC1, 0xC5, 0xC7, 0xD3, 0xDF,
275        //     0xE3, 0xE5, 0xE9, 0xEF, 0xF1, 0xFB,
276        // ];
277        // shuffled_primes = [
278        //     0x11, 0xC5, 0x3D, 0x95, 0x1D, 0x0D, 0x0B, 0x02,
279        //     0x83, 0xD3, 0x13, 0x8B, 0x6B, 0xAD, 0xEF, 0x71,
280        //     0xC1, 0x25, 0x65, 0x6D, 0x47, 0xBF, 0xB5, 0x9D,
281        //     0xDF, 0x03, 0xA7, 0x05, 0xC7, 0x4F, 0x7F, 0x67,
282        //     0xE9, 0xB3, 0xE5, 0x2B, 0x97, 0xFB, 0x61, 0x3B,
283        //     0x1F, 0xA3, 0x35, 0x43, 0x89, 0x49, 0xE3, 0x07,
284        //     0x53, 0xF1, 0x17, 0x2F, 0x29, 0x59,
285        // ];
286        match sdmty {
287            OwnedDataModelType::Bool => hash_update(state, &[0x11]),
288            OwnedDataModelType::I8 => hash_update(state, &[0xC5]),
289            OwnedDataModelType::U8 => hash_update(state, &[0x3D]),
290            OwnedDataModelType::I16 => hash_update(state, &[0x1D]),
291            OwnedDataModelType::I32 => hash_update(state, &[0x0D]),
292            OwnedDataModelType::I64 => hash_update(state, &[0x0B]),
293            OwnedDataModelType::I128 => hash_update(state, &[0x02]),
294            OwnedDataModelType::U16 => hash_update(state, &[0x83]),
295            OwnedDataModelType::U32 => hash_update(state, &[0xD3]),
296            OwnedDataModelType::U64 => hash_update(state, &[0x13]),
297            OwnedDataModelType::U128 => hash_update(state, &[0x8B]),
298            OwnedDataModelType::Usize => hash_update(state, &[0x6B]),
299            OwnedDataModelType::Isize => hash_update(state, &[0xAD]),
300            OwnedDataModelType::F32 => hash_update(state, &[0xEF]),
301            OwnedDataModelType::F64 => hash_update(state, &[0x71]),
302            OwnedDataModelType::Char => hash_update(state, &[0xC1]),
303            OwnedDataModelType::String => hash_update(state, &[0x25]),
304            OwnedDataModelType::ByteArray => hash_update(state, &[0x65]),
305            OwnedDataModelType::Option(nt) => {
306                let state = hash_update(state, &[0x6D]);
307                hash_named_type_owned(state, nt)
308            }
309            OwnedDataModelType::Unit => hash_update(state, &[0x47]),
310            OwnedDataModelType::UnitStruct => hash_update(state, &[0xBF]),
311            OwnedDataModelType::NewtypeStruct(nt) => {
312                let state = hash_update(state, &[0x9D]);
313                hash_named_type_owned(state, nt)
314            }
315            OwnedDataModelType::Seq(nt) => {
316                let state = hash_update(state, &[0x03]);
317                hash_named_type_owned(state, nt)
318            }
319            OwnedDataModelType::Tuple(nts) => {
320                let mut state = hash_update(state, &[0xA7]);
321                let mut idx = 0;
322                while idx < nts.len() {
323                    state = hash_named_type_owned(state, &nts[idx]);
324                    idx += 1;
325                }
326                state
327            }
328            OwnedDataModelType::TupleStruct(nts) => {
329                let mut state = hash_update(state, &[0x05]);
330                let mut idx = 0;
331                while idx < nts.len() {
332                    state = hash_named_type_owned(state, &nts[idx]);
333                    idx += 1;
334                }
335                state
336            }
337            OwnedDataModelType::Map { key, val } => {
338                let state = hash_update(state, &[0x4F]);
339                let state = hash_named_type_owned(state, key);
340                hash_named_type_owned(state, val)
341            }
342            OwnedDataModelType::Struct(nvs) => {
343                let mut state = hash_update(state, &[0x7F]);
344                let mut idx = 0;
345                while idx < nvs.len() {
346                    state = hash_named_value_owned(state, &nvs[idx]);
347                    idx += 1;
348                }
349                state
350            }
351            OwnedDataModelType::Enum(nvs) => {
352                let mut state = hash_update(state, &[0xE9]);
353                let mut idx = 0;
354                while idx < nvs.len() {
355                    state = hash_named_variant_owned(state, &nvs[idx]);
356                    idx += 1;
357                }
358                state
359            }
360            OwnedDataModelType::Schema => hash_update(state, &[0xB3]),
361        }
362    }
363
364    fn hash_named_type_owned(state: u64, nt: &OwnedNamedType) -> u64 {
365        // NOTE: We do *not* hash the name of the type in hashv2. This
366        // is to allow "safe" type punning, e.g. treating `Vec<u8>` and
367        // `&[u8]` as compatible types, when talking between std and no-std
368        // targets
369        //
370        // let state = hash_update(state, nt.name.as_bytes());
371        hash_sdm_type_owned(state, &nt.ty)
372    }
373
374    fn hash_named_variant_owned(state: u64, nt: &OwnedNamedVariant) -> u64 {
375        let state = hash_update(state, nt.name.as_bytes());
376        match &nt.ty {
377            OwnedDataModelVariant::UnitVariant => hash_update(state, &[0xB5]),
378            OwnedDataModelVariant::NewtypeVariant(nt) => {
379                let state = hash_update(state, &[0xDF]);
380                hash_named_type_owned(state, nt)
381            }
382            OwnedDataModelVariant::TupleVariant(nts) => {
383                let mut state = hash_update(state, &[0xC7]);
384                let mut idx = 0;
385                while idx < nts.len() {
386                    state = hash_named_type_owned(state, &nts[idx]);
387                    idx += 1;
388                }
389                state
390            }
391            OwnedDataModelVariant::StructVariant(nvs) => {
392                let mut state = hash_update(state, &[0x67]);
393                let mut idx = 0;
394                while idx < nvs.len() {
395                    state = hash_named_value_owned(state, &nvs[idx]);
396                    idx += 1;
397                }
398                state
399            }
400        }
401    }
402
403    fn hash_named_value_owned(state: u64, nt: &OwnedNamedValue) -> u64 {
404        let state = hash_update(state, nt.name.as_bytes());
405        hash_named_type_owned(state, &nt.ty)
406    }
407}
408
409#[cfg(test)]
410mod test {
411    use super::fnv1a64::hash_ty_path;
412
413    #[test]
414    fn type_punning_good() {
415        let hash_1 = hash_ty_path::<Vec<u8>>("test_path");
416        let hash_2 = hash_ty_path::<&[u8]>("test_path");
417        let hash_3 = hash_ty_path::<Vec<u16>>("test_path");
418        let hash_4 = hash_ty_path::<&[u16]>("test_path");
419        let hash_5 = hash_ty_path::<Vec<u8>>("test_patt");
420        let hash_6 = hash_ty_path::<&[u8]>("test_patt");
421        assert_eq!(hash_1, hash_2);
422        assert_eq!(hash_3, hash_4);
423        assert_ne!(hash_1, hash_3);
424        assert_ne!(hash_2, hash_4);
425        assert_ne!(hash_1, hash_5);
426        assert_ne!(hash_2, hash_6);
427    }
428}