Skip to main content

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].
27//!
28//! Helper traits combine these for convenience:
29//!
30//! - [Encode]: Combines [Write] + [EncodeSize]. Provides [Encode::encode()] method.
31//! - [Decode]: Requires [Read]. Provides [Decode::decode_cfg()] method that ensures
32//!   that the entire buffer is consumed.
33//! - [Codec]: Combines [Encode] + [Decode].
34//!
35//! # Specialization
36//!
37//! Byte-oriented container paths use hidden trait hooks on [Write], [Read], and [EncodeSize] to
38//! select bulk-copy implementations while keeping generic fallbacks. Container implementations
39//! call hooks such as `T::write_slice`, `T::read_vec`, and `T::encode_size_slice`. The default
40//! methods preserve element-by-element behavior, while concrete element implementations can
41//! override only the paths they can make faster.
42//!
43//! Encoding specialization has two parts: aggregate sizing and aggregate writing. For example,
44//! `Vec<u8>::encode()` first asks for the output size, then writes the bytes. The [EncodeSize]
45//! slice hooks let fixed-size elements compute `SIZE * len` without scanning every element, while
46//! the [Write] slice hooks let byte containers write the payload with one bulk copy.
47//!
48//! These hooks keep the container code generic: a container like `Vec<T>` calls one element-level
49//! method for sizing, writing, or reading, and the element implementation decides whether the
50//! default element-by-element behavior or a bulk path applies.
51//!
52//! # Supported Types
53//!
54//! Natively supports encoding/decoding for:
55//! - Primitives: [bool],
56//!   [u8], [u16], [u32], [u64], [u128],
57//!   [i8], [i16], [i32], [i64], [i128],
58//!   [f32], [f64], and [usize] (must fit within a [u32] for cross-platform compatibility).
59//! - Arrays: `[T; N]` supports [Write] and [Read] when `T` does, and supports [FixedSize],
60//!   [Encode], [Codec], [EncodeFixed], and [CodecFixed] when `T: FixedSize`.
61//! - Collections: [`Vec`], [`Option`], `BTreeMap`, `BTreeSet`
62//! - Tuples: `(T1, T2, ...)` (up to 12 elements)
63//! - Common External Types: [::bytes::Bytes]
64//!
65//! With the `std` feature (enabled by default):
66//! - Networking:
67//!   [`std::net::Ipv4Addr`],
68//!   [`std::net::Ipv6Addr`],
69//!   [`std::net::SocketAddrV4`],
70//!   [`std::net::SocketAddrV6`],
71//!   [`std::net::IpAddr`],
72//!   [`std::net::SocketAddr`]
73//! - Collections:
74//!   [`std::collections::HashMap`],
75//!   [`std::collections::HashSet`]
76//!
77//! # Implementing for Custom Types
78//!
79//! You typically need to implement [Write], [EncodeSize] (unless [FixedSize]), and [Read]
80//! for your custom structs and enums.
81//!
82//! ## Example 1. Fixed-Size Type
83//!
84//! ```
85//! use bytes::{Buf, BufMut};
86//! use commonware_codec::{Error, FixedSize, Read, ReadExt, Write, Encode, DecodeExt};
87//!
88//! // Define a custom struct
89//! #[derive(Debug, Clone, PartialEq)]
90//! struct Point {
91//!     x: u32, // FixedSize
92//!     y: u32, // FixedSize
93//! }
94//!
95//! // 1. Implement Write: How to serialize the struct
96//! impl Write for Point {
97//!     fn write(&self, buf: &mut impl BufMut) {
98//!         // u32 implements Write
99//!         self.x.write(buf);
100//!         self.y.write(buf);
101//!     }
102//! }
103//!
104//! // 2. Implement FixedSize (provides EncodeSize automatically)
105//! impl FixedSize for Point {
106//!     // u32 implements FixedSize
107//!     const SIZE: usize = u32::SIZE + u32::SIZE;
108//! }
109//!
110//! // 3. Implement Read: How to deserialize the struct (uses default Cfg = ())
111//! impl Read for Point {
112//!     type Cfg = ();
113//!     fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
114//!         // Use ReadExt::read for ergonomic reading when Cfg is ()
115//!         let x = u32::read(buf)?;
116//!         let y = u32::read(buf)?;
117//!         Ok(Self { x, y })
118//!     }
119//! }
120//!
121//! // Point now automatically implements Encode, Decode, Codec
122//! let point = Point { x: 1, y: 2 };
123//!
124//! // Encode is available via FixedSize + Write
125//! let bytes = point.encode();
126//! assert_eq!(bytes.len(), Point::SIZE);
127//!
128//! // Decode is available via Read, use DecodeExt
129//! let decoded_point = Point::decode(bytes).unwrap();
130//! assert_eq!(point, decoded_point);
131//! ```
132//!
133//! ## Example 2. Variable-Size Type
134//!
135//! ```
136//! use bytes::{Buf, BufMut};
137//! use commonware_codec::{
138//!     Decode, Encode, EncodeSize, Error, FixedSize, Read, ReadExt,
139//!     ReadRangeExt, Write, RangeCfg
140//! };
141//! use core::ops::RangeInclusive; // Example RangeCfg
142//!
143//! // Define a simple configuration for reading Item
144//! // Here, it just specifies the maximum allowed metadata length.
145//! #[derive(Clone)]
146//! pub struct ItemConfig {
147//!     max_metadata_len: usize,
148//! }
149//!
150//! // Define a custom struct
151//! #[derive(Debug, Clone, PartialEq)]
152//! struct Item {
153//!     id: u64,           // FixedSize
154//!     name: Option<u32>, // EncodeSize (depends on Option)
155//!     metadata: Vec<u8>, // EncodeSize (variable)
156//! }
157//!
158//! // 1. Implement Write
159//! impl Write for Item {
160//!     fn write(&self, buf: &mut impl BufMut) {
161//!         self.id.write(buf);       // u64 implements Write
162//!         self.name.write(buf);     // Option<u32> implements Write
163//!         self.metadata.write(buf); // Vec<u8> implements Write
164//!     }
165//! }
166//!
167//! // 2. Implement EncodeSize
168//! impl EncodeSize for Item {
169//!     fn encode_size(&self) -> usize {
170//!         // Sum the sizes of the parts
171//!         self.id.encode_size()         // u64 implements EncodeSize (via FixedSize)
172//!         + self.name.encode_size()     // Option<u32> implements EncodeSize
173//!         + self.metadata.encode_size() // Vec<u8> implements EncodeSize
174//!     }
175//! }
176//!
177//! // 3. Implement Read
178//! impl Read for Item {
179//!     type Cfg = ItemConfig;
180//!     fn read_cfg(buf: &mut impl Buf, cfg: &ItemConfig) -> Result<Self, Error> {
181//!         // u64 requires Cfg = (), uses ReadExt::read
182//!         let id = <u64>::read(buf)?;
183//!
184//!         // Option<u32> requires Cfg = (), uses ReadExt::read
185//!         let name = <Option<u32>>::read(buf)?;
186//!
187//!         // For Vec<u8>, the required config is (RangeCfg, InnerConfig)
188//!         // InnerConfig for u8 is (), so we need (RangeCfg, ())
189//!         // We use ReadRangeExt::read_range which handles the () for us.
190//!         // The RangeCfg limits the vector length using our ItemConfig.
191//!         let metadata_range = 0..=cfg.max_metadata_len; // Create the RangeCfg
192//!         let metadata = <Vec<u8>>::read_range(buf, metadata_range)?;
193//!
194//!         Ok(Self { id, name, metadata })
195//!     }
196//! }
197//!
198//! // Now you can use Encode and Decode:
199//! let item = Item { id: 101, name: None, metadata: vec![1, 2, 3] };
200//! let config = ItemConfig { max_metadata_len: 1024 };
201//!
202//! // Encode the item (uses Write + EncodeSize)
203//! let bytes = item.encode(); // Returns BytesMut
204//!
205//! // Decode the item
206//! // decode_cfg ensures all bytes are consumed.
207//! let decoded_item = Item::decode_cfg(bytes, &config).unwrap();
208//! assert_eq!(item, decoded_item);
209//! ```
210
211#![doc(
212    html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
213    html_favicon_url = "https://commonware.xyz/favicon.ico"
214)]
215#![cfg_attr(not(any(feature = "std", test)), no_std)]
216
217commonware_macros::stability_scope!(BETA {
218    #[cfg(not(feature = "std"))]
219    extern crate alloc;
220
221    pub mod codec;
222    pub mod config;
223    pub mod error;
224    pub mod extensions;
225    pub mod types;
226    pub mod util;
227    pub mod varint;
228
229    // Re-export main types and traits
230    pub use codec::*;
231    pub use config::RangeCfg;
232    pub use error::Error;
233    pub use extensions::*;
234});
235
236commonware_macros::stability_scope!(ALPHA {
237    #[cfg(feature = "arbitrary")]
238    pub mod conformance;
239
240    // Re-export paste for use in conformance macros
241    #[cfg(feature = "arbitrary")]
242    #[doc(hidden)]
243    pub use paste;
244});