commonware_codec/lib.rs
1//! Serialize structured data.
2//!
3//! # Overview
4//!
5//! Provides traits and implementations for efficient and safe binary serialization and
6//! deserialization of structured data. The library focuses on:
7//!
8//! - **Performance:** Uses the [bytes] crate and aims to minimize allocations.
9//! - **Safety:** Deserialization of untrusted data is made safer via the `Cfg` associated type in
10//! the [Read] trait, allowing users to impose limits (like maximum lengths) or other strict
11//! constraints on the data.
12//! - **Ease of Use:** Provides implementations for common Rust types and uses extension traits
13//! ([ReadExt], [DecodeExt], etc.) for ergonomic usage.
14//!
15//! # Core Concepts
16//!
17//! The library revolves around a few core traits:
18//!
19//! - [Write]: Implement this to define how your type is written to a byte buffer.
20//! - [Read]: Implement this to define how your type is read from a byte buffer.
21//! It has an associated `Cfg` type, primarily used to enforce constraints (e.g., size limits)
22//! when reading untrusted data. Use `()` if no config is needed.
23//! - [EncodeSize]: Implement this to calculate the exact encoded byte size of a value.
24//! Required for efficient buffer pre-allocation.
25//! - [FixedSize]: Marker trait for types whose encoded size is constant. Automatically
26//! implements [EncodeSize] and [MaxEncodeSize].
27//! - [MaxEncodeSize]: Provides an upper bound on encoded size that does not depend on
28//! caller-supplied limits.
29//!
30//! Helper traits combine these for convenience:
31//!
32//! - [Encode]: Combines [Write] + [EncodeSize]. Provides [Encode::encode()] method.
33//! - [Decode]: Requires [Read]. Provides [Decode::decode_cfg()] method that ensures
34//! that the entire buffer is consumed.
35//! - [Codec]: Combines [Encode] + [Decode].
36//!
37//! # Size Bounds
38//!
39//! Bounds reserve each scalar's maximum encoded type width, even when validation restricts its
40//! values. For example, a [`UInt<u64>`](varint::UInt) field contributes ten bytes even when its
41//! valid values fit in one. A bound therefore need not be attained by any valid value.
42//!
43//! Size functions accept caller-supplied limits for variable contents, such as item counts or
44//! byte lengths. Length prefixes still contribute their type's maximum width. For example,
45//! [`util::max_items_size`] gives a 133-byte bound for a `Vec<u8>` limited to 128 bytes: 128
46//! bytes of data and a five-byte [`UInt<u32>`](varint::UInt) length prefix.
47//!
48//! [FixedSize] types contribute their fixed size. Composite types sum their fields' bounds,
49//! and enums add their tag's bound to the largest variant bound. A size helper belongs with
50//! the encoding it describes, so enclosing types can reuse its result.
51//!
52//! # Decode Inputs
53//!
54//! Readers accept [Buf] inputs so decoded byte fields can share the input allocation. Pass owned
55//! buffers such as [::bytes::Bytes] directly, or borrow [::bytes::Bytes] through [Viewing] for an
56//! independent cursor without cloning the handle. Both preserve shared storage for retained byte
57//! fields. Decoders also accept owned [`Vec<u8>`] values through [Input] and take ownership of
58//! their allocation without copying the payload.
59//!
60//! Borrowed slices require [Copying]. Creating this adapter does not allocate, so scalar
61//! and byte-array reads can use reusable scratch storage. Fields that retain bytes copy
62//! their contents when decoded through the adapter.
63//!
64//! Use [Buf] for generic readers of serialized values and every helper on that read path,
65//! including fixed-size reads and length or padding validation. Use [Input] at entry
66//! points that convert owned inputs into readable buffers, then preserve [Buf] internally.
67//!
68//! Use [::bytes::Buf] for raw buffer implementations and byte-stream inputs to I/O,
69//! encoding, or hashing. When its cursor methods must be in scope alongside [Buf],
70//! import it as `use bytes::Buf as _;`.
71//!
72//! # Specialization
73//!
74//! Byte-oriented container paths use hidden trait hooks on [Write], [Read], and [EncodeSize] to
75//! select bulk-copy implementations while keeping generic fallbacks. Container implementations
76//! call hooks such as `T::write_slice`, `T::read_vec`, and `T::encode_size_slice`. The default
77//! methods preserve element-by-element behavior, while concrete element implementations can
78//! override only the paths they can make faster.
79//!
80//! Encoding specialization has two parts: aggregate sizing and aggregate writing. For example,
81//! `Vec<u8>::encode()` first asks for the output size, then writes the bytes. The [EncodeSize]
82//! slice hooks let fixed-size elements compute `SIZE * len` without scanning every element, while
83//! the [Write] slice hooks let byte containers write the payload with one bulk copy.
84//!
85//! These hooks keep the container code generic: a container like `Vec<T>` calls one element-level
86//! method for sizing, writing, or reading, and the element implementation decides whether the
87//! default element-by-element behavior or a bulk path applies.
88//!
89//! # Supported Types
90//!
91//! Natively supports encoding/decoding for:
92//! - Primitives: [bool],
93//! [u8], [u16], [u32], [u64], [u128],
94//! [i8], [i16], [i32], [i64], [i128],
95//! [f32], [f64], and [usize] (must fit within a [u32] for cross-platform compatibility).
96//! - Arrays: `[T; N]` supports [Write] and [Read] when `T` does, and supports [FixedSize],
97//! [Encode], [Codec], [EncodeFixed], and [CodecFixed] when `T: FixedSize`.
98//! - Collections: [`Vec`], [`Option`], `BTreeMap`, `BTreeSet`
99//! - Tuples: `(T1, T2, ...)` (up to 12 elements)
100//! - Common External Types: [::bytes::Bytes], `Arc<T>` (delegates to `T`)
101//!
102//! With the `std` feature (enabled by default):
103//! - Networking:
104//! [`std::net::Ipv4Addr`],
105//! [`std::net::Ipv6Addr`],
106//! [`std::net::SocketAddrV4`],
107//! [`std::net::SocketAddrV6`],
108//! [`std::net::IpAddr`],
109//! [`std::net::SocketAddr`]
110//! - Collections:
111//! [`std::collections::HashMap`],
112//! [`std::collections::HashSet`]
113//!
114//! # Implementing for Custom Types
115//!
116//! You typically need to implement [Write], [EncodeSize] (unless [FixedSize]), and [Read]
117//! for your custom structs and enums.
118//!
119//! ## Example 1. Fixed-Size Type
120//!
121//! ```
122//! use bytes::BufMut;
123//! use commonware_codec::{Buf, Error, FixedSize, Read, ReadExt, Write, Encode, DecodeExt};
124//!
125//! // Define a custom struct
126//! #[derive(Debug, Clone, PartialEq)]
127//! struct Point {
128//! x: u32, // FixedSize
129//! y: u32, // FixedSize
130//! }
131//!
132//! // 1. Implement Write: How to serialize the struct
133//! impl Write for Point {
134//! fn write(&self, buf: &mut impl BufMut) {
135//! // u32 implements Write
136//! self.x.write(buf);
137//! self.y.write(buf);
138//! }
139//! }
140//!
141//! // 2. Implement FixedSize (provides EncodeSize automatically)
142//! impl FixedSize for Point {
143//! // u32 implements FixedSize
144//! const SIZE: usize = u32::SIZE + u32::SIZE;
145//! }
146//!
147//! // 3. Implement Read: How to deserialize the struct (uses default Cfg = ())
148//! impl Read for Point {
149//! type Cfg = ();
150//! fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
151//! // Use ReadExt::read for ergonomic reading when Cfg is ()
152//! let x = u32::read(buf)?;
153//! let y = u32::read(buf)?;
154//! Ok(Self { x, y })
155//! }
156//! }
157//!
158//! // Point now automatically implements Encode, Decode, Codec
159//! let point = Point { x: 1, y: 2 };
160//!
161//! // Encode is available via FixedSize + Write
162//! let bytes = point.encode();
163//! assert_eq!(bytes.len(), Point::SIZE);
164//!
165//! // Decode is available via Read, use DecodeExt
166//! let decoded_point = Point::decode(bytes).unwrap();
167//! assert_eq!(point, decoded_point);
168//! ```
169//!
170//! ## Example 2. Variable-Size Type
171//!
172//! ```
173//! use bytes::BufMut;
174//! use commonware_codec::{Buf,
175//! Decode, Encode, EncodeSize, Error, FixedSize, Read, ReadExt,
176//! ReadRangeExt, Write, RangeCfg
177//! };
178//! use core::ops::RangeInclusive; // Example RangeCfg
179//!
180//! // Define a simple configuration for reading Item
181//! // Here, it just specifies the maximum allowed metadata length.
182//! #[derive(Clone)]
183//! pub struct ItemConfig {
184//! max_metadata_len: usize,
185//! }
186//!
187//! // Define a custom struct
188//! #[derive(Debug, Clone, PartialEq)]
189//! struct Item {
190//! id: u64, // FixedSize
191//! name: Option<u32>, // EncodeSize (depends on Option)
192//! metadata: Vec<u8>, // EncodeSize (variable)
193//! }
194//!
195//! // 1. Implement Write
196//! impl Write for Item {
197//! fn write(&self, buf: &mut impl BufMut) {
198//! self.id.write(buf); // u64 implements Write
199//! self.name.write(buf); // Option<u32> implements Write
200//! self.metadata.write(buf); // Vec<u8> implements Write
201//! }
202//! }
203//!
204//! // 2. Implement EncodeSize
205//! impl EncodeSize for Item {
206//! fn encode_size(&self) -> usize {
207//! // Sum the sizes of the parts
208//! self.id.encode_size() // u64 implements EncodeSize (via FixedSize)
209//! + self.name.encode_size() // Option<u32> implements EncodeSize
210//! + self.metadata.encode_size() // Vec<u8> implements EncodeSize
211//! }
212//! }
213//!
214//! // 3. Implement Read
215//! impl Read for Item {
216//! type Cfg = ItemConfig;
217//! fn read_cfg(buf: &mut impl Buf, cfg: &ItemConfig) -> Result<Self, Error> {
218//! // u64 requires Cfg = (), uses ReadExt::read
219//! let id = <u64>::read(buf)?;
220//!
221//! // Option<u32> requires Cfg = (), uses ReadExt::read
222//! let name = <Option<u32>>::read(buf)?;
223//!
224//! // For Vec<u8>, the required config is (RangeCfg, InnerConfig)
225//! // InnerConfig for u8 is (), so we need (RangeCfg, ())
226//! // We use ReadRangeExt::read_range which handles the () for us.
227//! // The RangeCfg limits the vector length using our ItemConfig.
228//! let metadata_range = 0..=cfg.max_metadata_len; // Create the RangeCfg
229//! let metadata = <Vec<u8>>::read_range(buf, metadata_range)?;
230//!
231//! Ok(Self { id, name, metadata })
232//! }
233//! }
234//!
235//! // Now you can use Encode and Decode:
236//! let item = Item { id: 101, name: None, metadata: vec![1, 2, 3] };
237//! let config = ItemConfig { max_metadata_len: 1024 };
238//!
239//! // Encode the item (uses Write + EncodeSize)
240//! let bytes = item.encode(); // Returns BytesMut
241//!
242//! // Decode the item
243//! // decode_cfg ensures all bytes are consumed.
244//! let decoded_item = Item::decode_cfg(bytes, &config).unwrap();
245//! assert_eq!(item, decoded_item);
246//! ```
247
248#![doc(
249 html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
250 html_favicon_url = "https://commonware.xyz/favicon.ico"
251)]
252#![cfg_attr(not(any(feature = "std", test)), no_std)]
253
254commonware_macros::stability_scope!(BETA {
255 #[cfg(not(feature = "std"))]
256 extern crate alloc;
257
258 mod buf;
259 pub use buf::{Buf, Copying, Input, OwnedBytes, Viewing};
260
261 pub mod codec;
262 pub mod config;
263 pub mod error;
264 pub mod extensions;
265 pub mod mode;
266 pub mod types;
267 pub mod util;
268 pub mod varint;
269
270 // Re-export main types and traits
271 pub use codec::*;
272 pub use commonware_codec_macros::FixedArray;
273 pub use config::RangeCfg;
274 pub use error::Error;
275 pub use extensions::*;
276 pub use mode::{InvalidMode, Mode, Modes};
277});
278
279commonware_macros::stability_scope!(ALPHA {
280 #[cfg(feature = "arbitrary")]
281 pub mod conformance;
282
283 // Re-export paste for use in conformance macros
284 #[cfg(feature = "arbitrary")]
285 #[doc(hidden)]
286 pub use paste;
287});