postcard_rpc/
lib.rs

1//! The goal of `postcard-rpc` is to make it easier for a
2//! host PC to talk to a constrained device, like a microcontroller.
3//!
4//! See [the repo] for examples
5//!
6//! [the repo]: https://github.com/jamesmunns/postcard-rpc
7//! [the overview]: https://github.com/jamesmunns/postcard-rpc/blob/main/docs/overview.md
8//!
9//! ## Architecture overview
10//!
11//! ```text
12//!                 ┌──────────┐      ┌─────────┐         ┌───────────┐
13//!                 │ Endpoint │      │ Publish │         │ Subscribe │
14//!                 └──────────┘      └─────────┘         └───────────┘
15//!                   │     ▲       message│                │        ▲
16//!    ┌────────┐ rqst│     │resp          │       subscribe│        │messages
17//!  ┌─┤ CLIENT ├─────┼─────┼──────────────┼────────────────┼────────┼──┐
18//!  │ └────────┘     ▼     │              ▼                ▼        │  │
19//!  │       ┌─────────────────────────────────────────────────────┐ │  │
20//!  │       │                     HostClient                      │ │  │
21//!  │       └─────────────────────────────────────────────────────┘ │  │
22//!  │         │                  │              ▲           │       |  │
23//!  │         │                  │              │           │       │  │
24//!  │         │                  │              │           ▼       │  │
25//!  │         │                  │      ┌──────────────┬──────────────┐│
26//!  │         │                  └─────▶│ Pending Resp │ Subscription ││
27//!  │         │                         └──────────────┴──────────────┘│
28//!  │         │                                 ▲              ▲       │
29//!  │         │                                 └───────┬──────┘       │
30//!  │         ▼                                         │              │
31//!  │      ┌────────────────────┐            ┌────────────────────┐    │
32//!  │      ││ Task: out_worker  │            │  Task: in_worker  ▲│    │
33//!  │      ├┼───────────────────┤            ├───────────────────┼┤    │
34//!  │      │▼  Trait: WireTx    │            │   Trait: WireRx   ││    │
35//!  └──────┴────────────────────┴────────────┴────────────────────┴────┘
36//!                    │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ▲
37//!                    │   The Server + Client WireRx    │
38//!                    │ │ and WireTx traits can be    │ │
39//!                    │   impl'd for any wire           │
40//!                    │ │ transport like USB, TCP,    │ │
41//!                    │   I2C, UART, etc.               │
42//!                    ▼ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
43//!   ┌─────┬────────────────────┬────────────┬────────────────────┬─────┐
44//!   │     ││  Trait: WireRx    │            │   Trait: WireTx   ▲│     │
45//!   │     ├┼───────────────────┤            ├───────────────────┼┤     │
46//!   │     ││      Server       │       ┌───▶│       Sender      ││     │
47//!   │     ├┼───────────────────┤       │    └────────────────────┘     │
48//!   │     │▼ Macro: Dispatch   │       │               ▲               │
49//!   │     └────────────────────┘       │               │               │
50//!   │    ┌─────────┐ │ ┌──────────┐    │ ┌───────────┐ │ ┌───────────┐ │
51//!   │    │  Topic  │ │ │ Endpoint │    │ │ Publisher │ │ │ Publisher │ │
52//!   │    │   fn    │◀┼▶│ async fn │────┤ │   Task    │─┼─│   Task    │ │
53//!   │    │ Handler │ │ │ Handler  │    │ └───────────┘ │ └───────────┘ │
54//!   │    └─────────┘ │ └──────────┘    │               │               │
55//!   │    ┌─────────┐ │ ┌──────────┐    │ ┌───────────┐ │ ┌───────────┐ │
56//!   │    │  Topic  │ │ │ Endpoint │    │ │ Publisher │ │ │ Publisher │ │
57//!   │    │async fn │◀┴▶│   task   │────┘ │   Task    │─┴─│   Task    │ │
58//!   │    │ Handler │   │ Handler  │      └───────────┘   └───────────┘ │
59//!   │    └─────────┘   └──────────┘                                    │
60//!   │ ┌────────┐                                                       │
61//!   └─┤ SERVER ├───────────────────────────────────────────────────────┘
62//!     └────────┘
63//! ```
64//!
65//! ## Defining a schema
66//!
67//! Typically, you will define your "wire types" in a shared schema crate. This
68//! crate essentially defines the protocol used between two or more devices.
69//!
70//! A schema consists of a couple of necessary items:
71//!
72//! ### Wire types
73//!
74//! We will need to define all of the types that we will use within our protocol.
75//! We specify normal Rust types, which will need to implement or derive three
76//! important traits:
77//!
78//! * [`serde`]'s [`Serialize`] trait - which defines how we can
79//!   convert a type into bytes on the wire
80//! * [`serde`]'s [`Deserialize`] trait - which defines how we
81//!   can convert bytes on the wire into a type
82//! * [`postcard_schema`]'s [`Schema`] trait - which generates a reflection-style
83//!   schema value for a given type.
84//!
85//! ### Endpoints
86//!
87//! Now that we have some basic types that will be used on the wire, we need
88//! to start building our protocol. The first thing we can build are [Endpoint]s,
89//! which represent a bidirectional "Request"/"Response" relationship. One of our
90//! devices will act as a Client (who makes a request, and receives a response),
91//! and the other device will act as a Server (who receives a request, and sends
92//! a response). Every request should be followed (eventually) by exactly one response.
93//!
94//! An endpoint consists of:
95//!
96//! * The type of the Request
97//! * The type of the Response
98//! * A string "path", like an HTTP URI that uniquely identifies the endpoint.
99//!
100//! ### Topics
101//!
102//! Sometimes, you would just like to send data in a single direction, with no
103//! response. This could be for reasons like asynchronous logging, blindly sending
104//! sensor data periodically, or any other reason you can think of.
105//!
106//! Topics have no "client" or "server" role, either device may decide to send a
107//! message on a given topic.
108//!
109//! A topic consists of:
110//!
111//! * The type of the Message
112//! * A string "path", like an HTTP URI that uniquely identifies the topic.
113
114#![cfg_attr(not(any(test, feature = "use-std")), no_std)]
115#![deny(missing_docs)]
116#![deny(unused_imports)]
117#![deny(rustdoc::broken_intra_doc_links)]
118
119/// Re-export used by macros
120#[doc(hidden)]
121pub use postcard;
122/// Re-export used by macros
123#[doc(hidden)]
124pub use postcard_schema;
125
126use header::{VarKey, VarKeyKind};
127use postcard_schema::{schema::NamedType, Schema};
128use serde::{Deserialize, Serialize};
129
130pub mod header;
131mod macros;
132pub mod server;
133pub mod standard_icd;
134pub mod uniques;
135
136#[cfg(feature = "cobs")]
137pub mod accumulator;
138
139#[cfg(feature = "use-std")]
140pub mod host_client;
141
142#[cfg(any(test, feature = "test-utils"))]
143pub mod test_utils;
144
145// Re-export Key components that now live in postcard-schema instead
146// of here in postcard-rpc
147pub use postcard_schema::key::hash;
148pub use postcard_schema::key::Key;
149
150/// A compacted 2-byte key
151///
152/// This is defined specifically as the following conversion:
153///
154/// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]`
155/// * Key4 bytes (`u8`): `a ^ b ^ c ^ d ^ e ^ f ^ g ^ h`
156#[derive(Debug, Copy, Clone, PartialEq)]
157pub struct Key1(u8);
158
159/// A compacted 2-byte key
160///
161/// This is defined specifically as the following conversion:
162///
163/// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]`
164/// * Key4 bytes (`[u8; 2]`): `[a ^ b ^ c ^ d, e ^ f ^ g ^ h]`
165#[derive(Debug, Copy, Clone, PartialEq)]
166pub struct Key2([u8; 2]);
167
168/// A compacted 4-byte key
169///
170/// This is defined specifically as the following conversion:
171///
172/// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]`
173/// * Key4 bytes (`[u8; 4]`): `[a ^ b, c ^ d, e ^ f, g ^ h]`
174#[derive(Debug, Copy, Clone, PartialEq)]
175pub struct Key4([u8; 4]);
176
177impl Key1 {
178    /// Convert from a 2-byte key
179    ///
180    /// This is a lossy conversion, and can never fail
181    #[inline]
182    pub const fn from_key2(value: Key2) -> Self {
183        let [a, b] = value.0;
184        Self(a ^ b)
185    }
186
187    /// Compare if the keys match in a const context
188    #[inline]
189    pub const fn const_cmp(&self, other: &Self) -> bool {
190        self.0 == other.0
191    }
192
193    /// Convert from a 4-byte key
194    ///
195    /// This is a lossy conversion, and can never fail
196    #[inline]
197    pub const fn from_key4(value: Key4) -> Self {
198        let [a, b, c, d] = value.0;
199        Self(a ^ b ^ c ^ d)
200    }
201
202    /// Convert from a full size 8-byte key
203    ///
204    /// This is a lossy conversion, and can never fail
205    #[inline]
206    pub const fn from_key8(value: Key) -> Self {
207        let [a, b, c, d, e, f, g, h] = value.to_bytes();
208        Self(a ^ b ^ c ^ d ^ e ^ f ^ g ^ h)
209    }
210
211    /// Convert to the inner byte representation
212    #[inline]
213    pub const fn to_bytes(&self) -> u8 {
214        self.0
215    }
216
217    /// Create a `Key1` from a [`VarKey`]
218    ///
219    /// This method can never fail, but has the same API as other key
220    /// types for consistency reasons.
221    #[inline]
222    pub fn try_from_varkey(value: &VarKey) -> Option<Self> {
223        Some(match value {
224            VarKey::Key1(key1) => *key1,
225            VarKey::Key2(key2) => Key1::from_key2(*key2),
226            VarKey::Key4(key4) => Key1::from_key4(*key4),
227            VarKey::Key8(key) => Key1::from_key8(*key),
228        })
229    }
230}
231
232impl Key2 {
233    /// Convert from a 4-byte key
234    ///
235    /// This is a lossy conversion, and can never fail
236    #[inline]
237    pub const fn from_key4(value: Key4) -> Self {
238        let [a, b, c, d] = value.0;
239        Self([a ^ b, c ^ d])
240    }
241
242    /// Compare if the keys match in a const context
243    #[inline]
244    pub const fn const_cmp(&self, other: &Self) -> bool {
245        let mut i = 0;
246        let mut all_match = true;
247        while i < self.0.len() {
248            all_match &= self.0[i] == other.0[i];
249            i += 1;
250        }
251        all_match
252    }
253
254    /// Convert from a full size 8-byte key
255    ///
256    /// This is a lossy conversion, and can never fail
257    #[inline]
258    pub const fn from_key8(value: Key) -> Self {
259        let [a, b, c, d, e, f, g, h] = value.to_bytes();
260        Self([a ^ b ^ c ^ d, e ^ f ^ g ^ h])
261    }
262
263    /// Convert to the inner byte representation
264    #[inline]
265    pub const fn to_bytes(&self) -> [u8; 2] {
266        self.0
267    }
268
269    /// Attempt to create a [`Key2`] from a [`VarKey`].
270    ///
271    /// Only succeeds if `value` is a `VarKey::Key2`, `VarKey::Key4`, or `VarKey::Key8`.
272    #[inline]
273    pub fn try_from_varkey(value: &VarKey) -> Option<Self> {
274        Some(match value {
275            VarKey::Key1(_) => return None,
276            VarKey::Key2(key2) => *key2,
277            VarKey::Key4(key4) => Key2::from_key4(*key4),
278            VarKey::Key8(key) => Key2::from_key8(*key),
279        })
280    }
281}
282
283impl Key4 {
284    /// Convert from a full size 8-byte key
285    ///
286    /// This is a lossy conversion, and can never fail
287    #[inline]
288    pub const fn from_key8(value: Key) -> Self {
289        let [a, b, c, d, e, f, g, h] = value.to_bytes();
290        Self([a ^ b, c ^ d, e ^ f, g ^ h])
291    }
292
293    /// Compare if the keys match in a const context
294    #[inline]
295    pub const fn const_cmp(&self, other: &Self) -> bool {
296        let mut i = 0;
297        let mut all_match = true;
298        while i < self.0.len() {
299            all_match &= self.0[i] == other.0[i];
300            i += 1;
301        }
302        all_match
303    }
304
305    /// Convert to the inner byte representation
306    #[inline]
307    pub const fn to_bytes(&self) -> [u8; 4] {
308        self.0
309    }
310
311    /// Attempt to create a [`Key4`] from a [`VarKey`].
312    ///
313    /// Only succeeds if `value` is a `VarKey::Key4` or `VarKey::Key8`.
314    #[inline]
315    pub fn try_from_varkey(value: &VarKey) -> Option<Self> {
316        Some(match value {
317            VarKey::Key1(_) => return None,
318            VarKey::Key2(_) => return None,
319            VarKey::Key4(key4) => *key4,
320            VarKey::Key8(key) => Key4::from_key8(*key),
321        })
322    }
323}
324
325/// The source type was too small to create from
326#[derive(Debug, PartialEq, Clone, Copy)]
327pub struct TooSmall;
328
329impl TryFrom<&VarKey> for Key1 {
330    type Error = TooSmall;
331
332    #[inline]
333    fn try_from(value: &VarKey) -> Result<Self, Self::Error> {
334        Self::try_from_varkey(value).ok_or(TooSmall)
335    }
336}
337
338impl TryFrom<&VarKey> for Key2 {
339    type Error = TooSmall;
340
341    #[inline]
342    fn try_from(value: &VarKey) -> Result<Self, Self::Error> {
343        Self::try_from_varkey(value).ok_or(TooSmall)
344    }
345}
346
347impl TryFrom<&VarKey> for Key4 {
348    type Error = TooSmall;
349
350    #[inline]
351    fn try_from(value: &VarKey) -> Result<Self, Self::Error> {
352        Self::try_from_varkey(value).ok_or(TooSmall)
353    }
354}
355
356impl TryFrom<&VarKey> for Key {
357    type Error = TooSmall;
358
359    #[inline]
360    fn try_from(value: &VarKey) -> Result<Self, Self::Error> {
361        if let VarKey::Key8(key) = value {
362            Ok(*key)
363        } else {
364            Err(TooSmall)
365        }
366    }
367}
368
369impl From<Key2> for Key1 {
370    fn from(value: Key2) -> Self {
371        Self::from_key2(value)
372    }
373}
374
375impl From<Key4> for Key1 {
376    fn from(value: Key4) -> Self {
377        Self::from_key4(value)
378    }
379}
380
381impl From<Key> for Key1 {
382    fn from(value: Key) -> Self {
383        Self::from_key8(value)
384    }
385}
386
387impl From<Key4> for Key2 {
388    fn from(value: Key4) -> Self {
389        Self::from_key4(value)
390    }
391}
392
393impl From<Key> for Key2 {
394    fn from(value: Key) -> Self {
395        Self::from_key8(value)
396    }
397}
398
399impl From<Key> for Key4 {
400    fn from(value: Key) -> Self {
401        Self::from_key8(value)
402    }
403}
404
405/// A marker trait denoting a single endpoint
406///
407/// Typically used with the [endpoint] macro.
408pub trait Endpoint {
409    /// The type of the Request (client to server)
410    type Request: Schema;
411    /// The type of the Response (server to client)
412    type Response: Schema;
413    /// The path associated with this Endpoint
414    const PATH: &'static str;
415    /// The unique [Key] identifying the Request
416    const REQ_KEY: Key;
417    /// The unique [Key4] identifying the Request
418    const REQ_KEY4: Key4 = Key4::from_key8(Self::REQ_KEY);
419    /// The unique [Key2] identifying the Request
420    const REQ_KEY2: Key2 = Key2::from_key8(Self::REQ_KEY);
421    /// The unique [Key1] identifying the Request
422    const REQ_KEY1: Key1 = Key1::from_key8(Self::REQ_KEY);
423    /// The unique [Key] identifying the Response
424    const RESP_KEY: Key;
425    /// The unique [Key4] identifying the Response
426    const RESP_KEY4: Key4 = Key4::from_key8(Self::RESP_KEY);
427    /// The unique [Key2] identifying the Response
428    const RESP_KEY2: Key2 = Key2::from_key8(Self::RESP_KEY);
429    /// The unique [Key1] identifying the Response
430    const RESP_KEY1: Key1 = Key1::from_key8(Self::RESP_KEY);
431}
432
433/// A marker trait denoting a single topic
434///
435/// Unlike [Endpoint]s, [Topic]s are unidirectional, and can be sent
436/// at any time asynchronously. Messages may be sent client to server,
437/// or server to client.
438///
439/// Typically used with the [topic] macro.
440pub trait Topic {
441    /// The type of the Message (unidirectional)
442    type Message: Schema + ?Sized;
443    /// The path associated with this Topic
444    const PATH: &'static str;
445    /// The unique [Key] identifying the Message
446    const TOPIC_KEY: Key;
447    /// The unique [Key4] identifying the Message
448    const TOPIC_KEY4: Key4 = Key4::from_key8(Self::TOPIC_KEY);
449    /// The unique [Key2] identifying the Message
450    const TOPIC_KEY2: Key2 = Key2::from_key8(Self::TOPIC_KEY);
451    /// The unique [Key2] identifying the Message
452    const TOPIC_KEY1: Key1 = Key1::from_key8(Self::TOPIC_KEY);
453}
454
455/// The direction of topic messages
456#[derive(Debug, PartialEq, Clone, Copy, Schema, Serialize, Deserialize)]
457#[cfg_attr(feature = "defmt", derive(defmt::Format))]
458pub enum TopicDirection {
459    /// Topic messages sent TO the SERVER, FROM the CLIENT
460    ToServer,
461    /// Topic messages sent TO the CLIENT, FROM the SERVER
462    ToClient,
463}
464
465/// An overview of all topics (in and out) and endpoints
466///
467/// Typically generated by the [`define_dispatch!()`] macro. Contains a list
468/// of all unique types across endpoints and topics, as well as the endpoints,
469/// topics in (client to server), topics out (server to client), as well as a
470/// calculated minimum key length required to avoid collisions in either the in
471/// or out direction.
472pub struct DeviceMap {
473    /// The set of unique types used by all endpoints and topics in this map
474    pub types: &'static [&'static NamedType],
475    /// The list of endpoints by path string, request key, and response key
476    pub endpoints: &'static [(&'static str, Key, Key)],
477    /// The list of topics (client to server) by path string and topic key
478    pub topics_in: &'static [(&'static str, Key)],
479    /// The list of topics (server to client) by path string and topic key
480    pub topics_out: &'static [(&'static str, Key)],
481    /// The minimum key size required to avoid hash collisions
482    pub min_key_len: VarKeyKind,
483}
484
485/// An overview of a list of endpoints
486///
487/// Typically generated by the [`endpoints!()`] macro. Contains a list of
488/// all unique types used by a list of endpoints, as well as the list of these
489/// endpoints by path, request key, and response key
490#[derive(Debug)]
491pub struct EndpointMap {
492    /// The set of unique types used by all endpoints in this map
493    pub types: &'static [&'static NamedType],
494    /// The list of endpoints by path string, request key, and response key
495    pub endpoints: &'static [(&'static str, Key, Key)],
496}
497
498/// An overview of a list of topics
499///
500/// Typically generated by the [`topics!()`] macro. Contains a list of all
501/// unique types used by a list of topics as well as the list of the topics
502/// by path and key
503#[derive(Debug)]
504pub struct TopicMap {
505    /// The direction of these topic messages
506    pub direction: TopicDirection,
507    /// The set of unique types used by all topics in this map
508    pub types: &'static [&'static NamedType],
509    /// The list of topics by path string and topic key
510    pub topics: &'static [(&'static str, Key)],
511}
512
513#[cfg(test)]
514mod test {
515    use postcard_schema::key::Key;
516
517    use crate::{Key1, Key2, Key4};
518
519    const K1: Key = unsafe { Key::from_bytes([1, 2, 3, 4, 5, 6, 7, 8]) };
520    const K2: Key = unsafe { Key::from_bytes([1, 2, 3, 4, 5, 6, 7, 9]) };
521    const K3: Key = unsafe { Key::from_bytes([1, 2, 3, 4, 5, 6, 7, 8]) };
522
523    #[test]
524    fn const_cmp8() {
525        assert!(K1.const_cmp(&K3));
526        assert!(!K1.const_cmp(&K2));
527    }
528
529    #[test]
530    fn const_cmp4() {
531        let k1 = Key4::from_key8(K1);
532        let k2 = Key4::from_key8(K2);
533        let k3 = Key4::from_key8(K3);
534
535        assert!(k1.const_cmp(&k3));
536        assert!(!k1.const_cmp(&k2));
537    }
538
539    #[test]
540    fn const_cmp2() {
541        let k1 = Key2::from_key8(K1);
542        let k2 = Key2::from_key8(K2);
543        let k3 = Key2::from_key8(K3);
544
545        assert!(k1.const_cmp(&k3));
546        assert!(!k1.const_cmp(&k2));
547    }
548
549    #[test]
550    fn const_cmp1() {
551        let k1 = Key1::from_key8(K1);
552        let k2 = Key1::from_key8(K2);
553        let k3 = Key1::from_key8(K3);
554
555        assert!(k1.const_cmp(&k3));
556        assert!(!k1.const_cmp(&k2));
557    }
558}