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}