//! Serialize structured data. //! //! # Overview //! //! Provides traits and implementations for efficient and safe binary serialization and //! deserialization of structured data. The library focuses on: //! //! - **Performance:** Uses the [bytes] crate and aims to minimize allocations. //! - **Safety:** Deserialization of untrusted data is made safer via the `Cfg` associated type in //! the [Read] trait, allowing users to impose limits (like maximum lengths) or other strict //! constraints on the data. //! - **Ease of Use:** Provides implementations for common Rust types and uses extension traits //! ([ReadExt], [DecodeExt], etc.) for ergonomic usage. //! //! # Core Concepts //! //! The library revolves around a few core traits: //! //! - [Write]: Implement this to define how your type is written to a byte buffer. //! - [Read]: Implement this to define how your type is read from a byte buffer. //! It has an associated `Cfg` type, primarily used to enforce constraints (e.g., size limits) //! when reading untrusted data. Use `()` if no config is needed. //! - [EncodeSize]: Implement this to calculate the exact encoded byte size of a value. //! Required for efficient buffer pre-allocation. //! - [FixedSize]: Marker trait for types whose encoded size is constant. Automatically //! implements [EncodeSize] and [MaxEncodeSize]. //! - [MaxEncodeSize]: Provides an upper bound on encoded size that does not depend on //! caller-supplied limits. //! //! Helper traits combine these for convenience: //! //! - [Encode]: Combines [Write] + [EncodeSize]. Provides [Encode::encode()] method. //! - [Decode]: Requires [Read]. Provides [Decode::decode_cfg()] method that ensures //! that the entire buffer is consumed. //! - [Codec]: Combines [Encode] + [Decode]. //! //! # Size Bounds //! //! Bounds reserve each scalar's maximum encoded type width, even when validation restricts its //! values. For example, a [`UInt`](varint::UInt) field contributes ten bytes even when its //! valid values fit in one. A bound therefore need not be attained by any valid value. //! //! Size functions accept caller-supplied limits for variable contents, such as item counts or //! byte lengths. Length prefixes still contribute their type's maximum width. For example, //! [`util::max_items_size`] gives a 133-byte bound for a `Vec` limited to 128 bytes: 128 //! bytes of data and a five-byte [`UInt`](varint::UInt) length prefix. //! //! [FixedSize] types contribute their fixed size. Composite types sum their fields' bounds, //! and enums add their tag's bound to the largest variant bound. A size helper belongs with //! the encoding it describes, so enclosing types can reuse its result. //! //! # Decode Inputs //! //! Readers accept [Buf] inputs so decoded byte fields can share the input allocation. Pass owned //! buffers such as [::bytes::Bytes] directly, or borrow [::bytes::Bytes] through [Viewing] for an //! independent cursor without cloning the handle. Both preserve shared storage for retained byte //! fields. Decoders also accept owned [`Vec`] values through [Input] and take ownership of //! their allocation without copying the payload. //! //! Borrowed slices require [Copying]. Creating this adapter does not allocate, so scalar //! and byte-array reads can use reusable scratch storage. Fields that retain bytes copy //! their contents when decoded through the adapter. //! //! Use [Buf] for generic readers of serialized values and every helper on that read path, //! including fixed-size reads and length or padding validation. Use [Input] at entry //! points that convert owned inputs into readable buffers, then preserve [Buf] internally. //! //! Use [::bytes::Buf] for raw buffer implementations and byte-stream inputs to I/O, //! encoding, or hashing. When its cursor methods must be in scope alongside [Buf], //! import it as `use bytes::Buf as _;`. //! //! # Specialization //! //! Byte-oriented container paths use hidden trait hooks on [Write], [Read], and [EncodeSize] to //! select bulk-copy implementations while keeping generic fallbacks. Container implementations //! call hooks such as `T::write_slice`, `T::read_vec`, and `T::encode_size_slice`. The default //! methods preserve element-by-element behavior, while concrete element implementations can //! override only the paths they can make faster. //! //! Encoding specialization has two parts: aggregate sizing and aggregate writing. For example, //! `Vec::encode()` first asks for the output size, then writes the bytes. The [EncodeSize] //! slice hooks let fixed-size elements compute `SIZE * len` without scanning every element, while //! the [Write] slice hooks let byte containers write the payload with one bulk copy. //! //! These hooks keep the container code generic: a container like `Vec` calls one element-level //! method for sizing, writing, or reading, and the element implementation decides whether the //! default element-by-element behavior or a bulk path applies. //! //! # Supported Types //! //! Natively supports encoding/decoding for: //! - Primitives: [bool], //! [u8], [u16], [u32], [u64], [u128], //! [i8], [i16], [i32], [i64], [i128], //! [f32], [f64], and [usize] (must fit within a [u32] for cross-platform compatibility). //! - Arrays: `[T; N]` supports [Write] and [Read] when `T` does, and supports [FixedSize], //! [Encode], [Codec], [EncodeFixed], and [CodecFixed] when `T: FixedSize`. //! - Collections: [`Vec`], [`Option`], `BTreeMap`, `BTreeSet` //! - Tuples: `(T1, T2, ...)` (up to 12 elements) //! - Common External Types: [::bytes::Bytes], `Arc` (delegates to `T`) //! //! With the `std` feature (enabled by default): //! - Networking: //! [`std::net::Ipv4Addr`], //! [`std::net::Ipv6Addr`], //! [`std::net::SocketAddrV4`], //! [`std::net::SocketAddrV6`], //! [`std::net::IpAddr`], //! [`std::net::SocketAddr`] //! - Collections: //! [`std::collections::HashMap`], //! [`std::collections::HashSet`] //! //! # Implementing for Custom Types //! //! You typically need to implement [Write], [EncodeSize] (unless [FixedSize]), and [Read] //! for your custom structs and enums. //! //! ## Example 1. Fixed-Size Type //! //! ``` //! use bytes::BufMut; //! use commonware_codec::{Buf, Error, FixedSize, Read, ReadExt, Write, Encode, DecodeExt}; //! //! // Define a custom struct //! #[derive(Debug, Clone, PartialEq)] //! struct Point { //! x: u32, // FixedSize //! y: u32, // FixedSize //! } //! //! // 1. Implement Write: How to serialize the struct //! impl Write for Point { //! fn write(&self, buf: &mut impl BufMut) { //! // u32 implements Write //! self.x.write(buf); //! self.y.write(buf); //! } //! } //! //! // 2. Implement FixedSize (provides EncodeSize automatically) //! impl FixedSize for Point { //! // u32 implements FixedSize //! const SIZE: usize = u32::SIZE + u32::SIZE; //! } //! //! // 3. Implement Read: How to deserialize the struct (uses default Cfg = ()) //! impl Read for Point { //! type Cfg = (); //! fn read_cfg(buf: &mut impl Buf, _: &()) -> Result { //! // Use ReadExt::read for ergonomic reading when Cfg is () //! let x = u32::read(buf)?; //! let y = u32::read(buf)?; //! Ok(Self { x, y }) //! } //! } //! //! // Point now automatically implements Encode, Decode, Codec //! let point = Point { x: 1, y: 2 }; //! //! // Encode is available via FixedSize + Write //! let bytes = point.encode(); //! assert_eq!(bytes.len(), Point::SIZE); //! //! // Decode is available via Read, use DecodeExt //! let decoded_point = Point::decode(bytes).unwrap(); //! assert_eq!(point, decoded_point); //! ``` //! //! ## Example 2. Variable-Size Type //! //! ``` //! use bytes::BufMut; //! use commonware_codec::{Buf, //! Decode, Encode, EncodeSize, Error, FixedSize, Read, ReadExt, //! ReadRangeExt, Write, RangeCfg //! }; //! use core::ops::RangeInclusive; // Example RangeCfg //! //! // Define a simple configuration for reading Item //! // Here, it just specifies the maximum allowed metadata length. //! #[derive(Clone)] //! pub struct ItemConfig { //! max_metadata_len: usize, //! } //! //! // Define a custom struct //! #[derive(Debug, Clone, PartialEq)] //! struct Item { //! id: u64, // FixedSize //! name: Option, // EncodeSize (depends on Option) //! metadata: Vec, // EncodeSize (variable) //! } //! //! // 1. Implement Write //! impl Write for Item { //! fn write(&self, buf: &mut impl BufMut) { //! self.id.write(buf); // u64 implements Write //! self.name.write(buf); // Option implements Write //! self.metadata.write(buf); // Vec implements Write //! } //! } //! //! // 2. Implement EncodeSize //! impl EncodeSize for Item { //! fn encode_size(&self) -> usize { //! // Sum the sizes of the parts //! self.id.encode_size() // u64 implements EncodeSize (via FixedSize) //! + self.name.encode_size() // Option implements EncodeSize //! + self.metadata.encode_size() // Vec implements EncodeSize //! } //! } //! //! // 3. Implement Read //! impl Read for Item { //! type Cfg = ItemConfig; //! fn read_cfg(buf: &mut impl Buf, cfg: &ItemConfig) -> Result { //! // u64 requires Cfg = (), uses ReadExt::read //! let id = ::read(buf)?; //! //! // Option requires Cfg = (), uses ReadExt::read //! let name = >::read(buf)?; //! //! // For Vec, the required config is (RangeCfg, InnerConfig) //! // InnerConfig for u8 is (), so we need (RangeCfg, ()) //! // We use ReadRangeExt::read_range which handles the () for us. //! // The RangeCfg limits the vector length using our ItemConfig. //! let metadata_range = 0..=cfg.max_metadata_len; // Create the RangeCfg //! let metadata = >::read_range(buf, metadata_range)?; //! //! Ok(Self { id, name, metadata }) //! } //! } //! //! // Now you can use Encode and Decode: //! let item = Item { id: 101, name: None, metadata: vec![1, 2, 3] }; //! let config = ItemConfig { max_metadata_len: 1024 }; //! //! // Encode the item (uses Write + EncodeSize) //! let bytes = item.encode(); // Returns BytesMut //! //! // Decode the item //! // decode_cfg ensures all bytes are consumed. //! let decoded_item = Item::decode_cfg(bytes, &config).unwrap(); //! assert_eq!(item, decoded_item); //! ``` #![doc( html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg", html_favicon_url = "https://commonware.xyz/favicon.ico" )] #![cfg_attr(not(any(feature = "std", test)), no_std)] commonware_macros::stability_scope!(BETA { #[cfg(not(feature = "std"))] extern crate alloc; mod buf; pub use buf::{Buf, Copying, Input, OwnedBytes, Viewing}; pub mod codec; pub mod config; pub mod error; pub mod extensions; pub mod mode; pub mod types; pub mod util; pub mod varint; // Re-export main types and traits pub use codec::*; pub use commonware_codec_macros::FixedArray; pub use config::RangeCfg; pub use error::Error; pub use extensions::*; pub use mode::{InvalidMode, Mode, Modes}; }); commonware_macros::stability_scope!(ALPHA { #[cfg(feature = "arbitrary")] pub mod conformance; // Re-export paste for use in conformance macros #[cfg(feature = "arbitrary")] #[doc(hidden)] pub use paste; });