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}