postcard_schema/key/
mod.rs

1//! A hash-based "Key" tag for postcard-schema compatible types
2//!
3//! This originally was developed for use in postcard-rpc, but has more
4//! general use as a general purpose type + usage tag.
5//!
6//! This key should NOT be relied on for memory safety purposes, validation
7//! of the data should still be performed, like that done by serde. It should
8//! be treated as misuse **resistant**, not misuse **proof**. It is possible
9//! for there to be hash collisions, and as a non-cryptograpic hash, it is
10//! likely trivial to intentionally cause a collision.
11
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    schema::{DataModelType, NamedType},
16    Schema,
17};
18
19pub mod hash;
20
21// TODO: https://github.com/knurling-rs/defmt/issues/928
22#[cfg(feature = "defmt-v0_3")]
23use defmt_v0_3 as defmt;
24
25/// The `Key` uniquely identifies what "kind" of message this is.
26///
27/// In order to generate it, `postcard-schema` takes two pieces of data:
28///
29/// * a `&str` "path" URI, similar to how you would use URIs as part of an HTTP path
30/// * The schema of the message type itself, using the [`Schema`] trait
31///
32/// [`Schema`]: crate::Schema
33///
34/// Specifically, we use [`Fnv1a`](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function),
35/// and produce a 64-bit digest, by first hashing the path, then hashing the
36/// schema. Fnv1a is a non-cryptographic hash function, designed to be reasonably
37/// efficient to compute even on small platforms like microcontrollers.
38///
39/// Changing **anything** about *either* of the path or the schema will produce
40/// a drastically different `Key` value.
41#[cfg_attr(feature = "defmt-v0_3", derive(defmt_v0_3::Format))]
42#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize, Hash)]
43pub struct Key([u8; 8]);
44
45impl Schema for Key {
46    const SCHEMA: &'static crate::schema::NamedType = &NamedType {
47        name: "Key",
48        ty: &DataModelType::NewtypeStruct(<[u8; 8] as Schema>::SCHEMA),
49    };
50}
51
52impl core::fmt::Debug for Key {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        f.write_str("Key(")?;
55        for b in self.0.iter() {
56            f.write_fmt(format_args!("{b} "))?;
57        }
58        f.write_str(")")
59    }
60}
61
62impl Key {
63    /// Create a Key for the given type and path
64    pub const fn for_path<T>(path: &str) -> Self
65    where
66        T: Schema + ?Sized,
67    {
68        Key(hash::fnv1a64::hash_ty_path::<T>(path))
69    }
70
71    /// Unsafely create a key from a given 8-byte value
72    ///
73    /// ## Safety
74    ///
75    /// This MUST only be used with pre-calculated values. Incorrectly
76    /// created keys could lead to the improper deserialization of
77    /// messages.
78    pub const unsafe fn from_bytes(bytes: [u8; 8]) -> Self {
79        Self(bytes)
80    }
81
82    /// Extract the bytes making up this key
83    pub const fn to_bytes(&self) -> [u8; 8] {
84        self.0
85    }
86
87    /// Compare 2 keys in const context.
88    pub const fn const_cmp(&self, other: &Self) -> bool {
89        let mut i = 0;
90        while i < self.0.len() {
91            if self.0[i] != other.0[i] {
92                return false;
93            }
94
95            i += 1;
96        }
97
98        true
99    }
100}
101
102#[cfg(feature = "use-std")]
103mod key_owned {
104    use super::*;
105    use crate::schema::owned::OwnedNamedType;
106    impl Key {
107        /// Calculate the Key for the given path and [`OwnedNamedType`]
108        pub fn for_owned_schema_path(path: &str, nt: &OwnedNamedType) -> Key {
109            Key(hash::fnv1a64_owned::hash_ty_path_owned(path, nt))
110        }
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use crate::{
117        key::Key,
118        schema::{DataModelType, NamedType},
119        Schema,
120    };
121
122    #[test]
123    fn matches_old_postcard_rpc_defn() {
124        let old = &NamedType {
125            name: "Key",
126            ty: &DataModelType::NewtypeStruct(&NamedType {
127                name: "[T; N]",
128                ty: &DataModelType::Tuple(&[
129                    &NamedType {
130                        name: "u8",
131                        ty: &DataModelType::U8,
132                    },
133                    &NamedType {
134                        name: "u8",
135                        ty: &DataModelType::U8,
136                    },
137                    &NamedType {
138                        name: "u8",
139                        ty: &DataModelType::U8,
140                    },
141                    &NamedType {
142                        name: "u8",
143                        ty: &DataModelType::U8,
144                    },
145                    &NamedType {
146                        name: "u8",
147                        ty: &DataModelType::U8,
148                    },
149                    &NamedType {
150                        name: "u8",
151                        ty: &DataModelType::U8,
152                    },
153                    &NamedType {
154                        name: "u8",
155                        ty: &DataModelType::U8,
156                    },
157                    &NamedType {
158                        name: "u8",
159                        ty: &DataModelType::U8,
160                    },
161                ]),
162            }),
163        };
164
165        let new = <Key as Schema>::SCHEMA;
166
167        assert_eq!(old, new);
168    }
169}