12 releases
new 0.0.50 | May 3, 2025 |
---|---|
0.0.49 | May 2, 2025 |
0.0.48 | Apr 28, 2025 |
0.0.43 | Mar 16, 2025 |
0.0.1 | Feb 27, 2025 |
#15 in Magic Beans
1,198 downloads per month
Used in 14 crates
(11 directly)
78KB
1.5K
SLoC
commonware-codec
Serialize structured data.
Status
commonware-codec
is ALPHA software and is not yet recommended for production use. Developers should expect breaking changes and occasional instability.
lib.rs
:
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 theRead
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 associatedCfg
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 implementsEncodeSize
.
Helper traits combine these for convenience:
Encode
: CombinesWrite
+EncodeSize
. Provides [Encode::encode()
] method.Decode
: RequiresRead
. Provides [Decode::decode_cfg()
] method that ensures that the entire buffer is consumed.Codec
: CombinesEncode
+Decode
.
Supported Types
Natively supports encoding/decoding for:
- Primitives:
bool
, [u8
], [u16
], [u32
], [u64
],u128
, [i8
], [i16
], [i32
], [i64
],i128
, [f32
], [f64
],[u8; N]
, andusize
(must fit within a [u32
] for cross-platform compatibility). - Collections:
Vec<T>
,Option<T>
- Tuples:
(T1, T2, ...)
(up to 12 elements) - Networking:
Ipv4Addr
,Ipv6Addr
,SocketAddrV4
,SocketAddrV6
,SocketAddr
- Common External Types:
::bytes::Bytes
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::{Buf, BufMut};
use commonware_codec::{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<Self, Error> {
// 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::{Buf, BufMut};
use commonware_codec::{
Decode, Encode, EncodeSize, Error, FixedSize, Read, ReadExt,
ReadRangeExt, Write, RangeCfg
};
use std::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<u32>, // EncodeSize (depends on Option)
metadata: Vec<u8>, // 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<u32> implements Write
self.metadata.write(buf); // Vec<u8> 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<u32> implements EncodeSize
+ self.metadata.encode_size() // Vec<u8> implements EncodeSize
}
}
// 3. Implement Read
impl Read for Item {
type Cfg = ItemConfig;
fn read_cfg(buf: &mut impl Buf, cfg: &ItemConfig) -> Result<Self, Error> {
// u64 requires Cfg = (), uses ReadExt::read
let id = <u64>::read(buf)?;
// Option<u32> requires Cfg = (), uses ReadExt::read
let name = <Option<u32>>::read(buf)?;
// For Vec<u8>, 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 = <Vec<u8>>::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);
Dependencies
~0.4–0.9MB
~19K SLoC