consortium_codec/lib.rs
1// Copyright 2026 Ethan Wu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16#![cfg_attr(not(feature = "std"), no_std)]
17
18//! Serialization families for Consortium IPC and TEE parameter conversion.
19//!
20//! A Consortium link carries typed messages, but the wire carries bytes. This
21//! crate is the seam: [`Codec`] names a serialization *family*, [`CodecFor<T>`]
22//! says that family can handle a particular message type, and everything above —
23//! `consortium_ipc::Channel`, `consortium_tee::TeeParam` — is generic over it.
24//!
25//! # Architecture
26//!
27//! The trait pair is split so that a family's error type is declared once:
28//!
29//! ```text
30//! Codec one impl per family; declares Error
31//! └── CodecFor<T> one impl per (family, message type); encode/decode
32//! ```
33//!
34//! The load-bearing detail is that [`CodecFor::Decoded`] is an associated *type
35//! constructor*, not `T`:
36//!
37//! | Codec | `Decoded<'buf>` | Cost of a `recv` |
38//! | --------------- | -------------------- | --------------------------------- |
39//! | `PostcardCodec` | `T` | deserializes into an owned value |
40//! | `ProstCodec` | `T` | deserializes into an owned value |
41//! | `RkyvCodec` | `&'buf T::Archived` | validates and borrows the buffer |
42//!
43//! Callers must therefore not assume `decode` produces an owned `T`.
44//! `consortium_ipc::ReceivedMessage` wraps `Decoded<'buf>` and `Deref`s to `T`
45//! where the codec allows it, so call sites written against it survive a codec
46//! swap unchanged.
47//!
48//! Encoding is always into a caller-supplied `&mut [u8]` and returns the byte
49//! count. There is no allocation in the trait signature, which is what lets the
50//! same codec serve a `no_std` firmware channel and a Linux one.
51//!
52//! # Features
53//!
54//! `no_std` by default, and **there is no default codec** — a consumer names the
55//! family it wants, so firmware does not pay for one it never calls.
56//!
57//! | Feature | Exposes |
58//! | ---------- | ------------------------------------------------ |
59//! | `postcard` | `PostcardCodec` (serde, compact, owned decode) |
60//! | `prost` | `ProstCodec` (protobuf, owned decode) |
61//! | `rkyv` | `RkyvCodec`, `TrustedArchive` (zero-copy) |
62//! | `alloc` | heap-backed paths, including validated rkyv |
63//! | `std` | `alloc` plus `tracing`-routed diagnostics |
64//! | `defmt` | routes the same diagnostics to `defmt` |
65//!
66//! # Example
67//!
68//! ```rust,ignore
69//! use consortium_codec::{CodecFor, PostcardCodec};
70//! use serde::{Deserialize, Serialize};
71//!
72//! #[derive(Serialize, Deserialize)]
73//! struct Telemetry { sequence: u32, ready: bool }
74//!
75//! let mut buf = [0u8; 32];
76//! let n = PostcardCodec::encode(&Telemetry { sequence: 7, ready: true }, &mut buf)?;
77//! let back: Telemetry = <PostcardCodec as CodecFor<Telemetry>>::decode(&buf[..n])?;
78//! ```
79//!
80//! # Implementing a codec
81//!
82//! Add a unit struct, `impl Codec` for it to declare `Error`, then
83//! `impl<T> CodecFor<T> for MyCodec where T: <your serialization bound>`. Set
84//! `Decoded<'buf>` to `T` for an owning codec or to a borrow of `'buf` for a
85//! zero-copy one. Encode must report the exact byte count written and must fail
86//! rather than truncate when `buf` is too small — a transport sends `buf[..n]`
87//! verbatim.
88
89#[cfg(feature = "postcard")]
90pub(crate) mod postcard;
91
92#[cfg(feature = "prost")]
93pub(crate) mod prost;
94
95#[cfg(feature = "rkyv")]
96pub(crate) mod rkyv;
97
98#[cfg(feature = "postcard")]
99pub use postcard::PostcardCodec;
100
101#[cfg(feature = "prost")]
102pub use prost::ProstCodec;
103
104#[cfg(feature = "rkyv")]
105pub use rkyv::RkyvCodec;
106
107/// A serialization family.
108///
109/// Implemented once per family (not per message type) so that the family's error
110/// type is declared in one place and [`CodecFor<T>`] can stay a blanket impl over
111/// whatever bound the underlying library needs. Codecs are zero-sized markers;
112/// they are named as type parameters and never instantiated.
113pub trait Codec: core::marker::Sized {
114 /// Error returned by both [`CodecFor::encode`] and [`CodecFor::decode`].
115 ///
116 /// Typically the backing library's own error, widened where the codec adds
117 /// failure modes of its own (a too-small destination buffer, say).
118 type Error: core::fmt::Debug;
119}
120
121/// A [`Codec`] that can round-trip the message type `T`.
122///
123/// Both directions work against a caller-supplied slice, so the trait is usable
124/// from `no_std` firmware with a `static` scratch buffer.
125pub trait CodecFor<T>: Codec {
126 /// What [`decode`](CodecFor::decode) yields for a buffer living as long as
127 /// `'buf`.
128 ///
129 /// This is a type *constructor*, not `T`, which is what lets one trait cover
130 /// both owning and zero-copy codecs: `PostcardCodec` sets it to `T`, while a
131 /// zero-copy codec sets it to a borrow such as `&'buf T::Archived`. Callers
132 /// must not assume decoding produces an owned value —
133 /// `consortium_ipc::ReceivedMessage` exists to abstract over exactly this.
134 type Decoded<'buf>: 'buf
135 where
136 T: 'buf;
137
138 /// Serialize `msg` into `buf`, returning the number of bytes written.
139 ///
140 /// Implementations must never truncate: a transport transmits `buf[..n]`
141 /// verbatim, so a destination that cannot hold the whole message is an error,
142 /// not a partial write.
143 fn encode(msg: &T, buf: &mut [u8]) -> core::result::Result<usize, Self::Error>;
144
145 /// Deserialize a `T` from exactly the bytes in `buf`.
146 ///
147 /// `buf` is the received frame trimmed to its true length; trailing slack is
148 /// not tolerated by every family. Zero-copy codecs borrow from `buf`, which is
149 /// why the returned value is tied to `'buf`.
150 fn decode<'buf>(buf: &'buf [u8]) -> core::result::Result<Self::Decoded<'buf>, Self::Error>
151 where
152 T: 'buf;
153}
154
155#[cfg(feature = "rkyv")]
156pub use consortium_codec_macros::TrustedArchive;
157
158#[cfg(feature = "rkyv")]
159pub use crate::rkyv::TrustedArchive;