commonware_codec/codec.rs
1//! Core traits for encoding and decoding.
2
3use crate::error::Error;
4use bytes::{Buf, BufMut, Bytes, BytesMut};
5
6/// Trait for types with a known, fixed encoded size.
7///
8/// Implementing this trait signifies that the encoded representation of this type *always* has the
9/// same byte length, regardless of the specific value.
10///
11/// This automatically provides an implementation of [EncodeSize].
12pub trait FixedSize {
13 /// The size of the encoded value (in bytes).
14 const SIZE: usize;
15}
16
17/// Trait for types that can provide their encoded size in bytes.
18///
19/// This must be implemented by all encodable types. For types implementing [FixedSize], this
20/// trait is implemented automatically. For variable-size types, this requires calculating the size
21/// based on the value.
22pub trait EncodeSize {
23 /// Returns the encoded size of this value (in bytes).
24 fn encode_size(&self) -> usize;
25
26 /// Returns the encoded size excluding bytes passed to [`BufsMut::push`]
27 /// during [`Write::write_bufs`]. Used to size the working buffer for inline
28 /// writes. Override alongside [`Write::write_bufs`] for types where large
29 /// [`Bytes`] fields go via push; failing to do so will over-allocate.
30 fn encode_inline_size(&self) -> usize {
31 self.encode_size()
32 }
33}
34
35// Automatically implement `EncodeSize` for types that are `FixedSize`.
36impl<T: FixedSize> EncodeSize for T {
37 fn encode_size(&self) -> usize {
38 Self::SIZE
39 }
40}
41
42/// Trait for types that can be written (encoded) to a byte buffer.
43pub trait Write {
44 /// Writes the binary representation of `self` to the provided buffer `buf`.
45 ///
46 /// Implementations should panic if the buffer doesn't have enough capacity.
47 fn write(&self, buf: &mut impl BufMut);
48
49 /// Writes to a [`BufsMut`], allowing existing [`Bytes`] chunks to be
50 /// appended via [`BufsMut::push`] instead of written inline. Must encode
51 /// to the same format as [`Write::write`]. Defaults to [`Write::write`].
52 fn write_bufs(&self, buf: &mut impl BufsMut) {
53 self.write(buf);
54 }
55}
56
57/// Trait for types that can be read (decoded) from a byte buffer.
58pub trait Read: Sized {
59 /// The `Cfg` type parameter allows passing configuration during the read process. This is
60 /// crucial for safely decoding untrusted data, for example, by providing size limits for
61 /// collections or strings.
62 ///
63 /// Use `Cfg = ()` if no configuration is needed for a specific type.
64 type Cfg: Clone + Send + Sync + 'static;
65
66 /// Reads a value from the buffer using the provided configuration `cfg`.
67 ///
68 /// Implementations should consume the exact number of bytes required from `buf` to reconstruct
69 /// the value.
70 ///
71 /// Returns [Error] if decoding fails due to invalid data, insufficient bytes in the buffer,
72 /// or violation of constraints imposed by the `cfg`.
73 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error>;
74}
75
76/// Trait combining [Write] and [EncodeSize] for types that can be fully encoded.
77///
78/// This trait provides the convenience [Encode::encode] method which handles
79/// buffer allocation, writing, and size assertion in one go.
80pub trait Encode: Write + EncodeSize {
81 /// Encodes `self` into a new [Bytes] buffer.
82 ///
83 /// This method calculates the required size using [EncodeSize::encode_size], allocates a
84 /// buffer of that exact capacity, writes the value using [Write::write], and performs a
85 /// sanity check assertion.
86 ///
87 /// # Panics
88 ///
89 /// Panics if `encode_size()` does not return the same number of bytes actually written by
90 /// `write()`
91 fn encode(&self) -> Bytes {
92 self.encode_mut().freeze()
93 }
94
95 /// Encodes `self` into a new [BytesMut] buffer.
96 ///
97 /// This method calculates the required size using [EncodeSize::encode_size], allocates a
98 /// buffer of that exact capacity, writes the value using [Write::write], and performs a
99 /// sanity check assertion.
100 ///
101 /// # Panics
102 ///
103 /// Panics if `encode_size()` does not return the same number of bytes actually written by
104 /// `write()`
105 fn encode_mut(&self) -> BytesMut {
106 let len = self.encode_size();
107 let mut buffer = BytesMut::with_capacity(len);
108 self.write(&mut buffer);
109 assert_eq!(buffer.len(), len, "write() did not write expected bytes");
110 buffer
111 }
112}
113
114// Automatically implement `Encode` for types that implement `Write` and `EncodeSize`.
115impl<T: Write + EncodeSize> Encode for T {}
116
117/// Convenience trait combining `Encode` with thread-safety bounds.
118///
119/// Represents types that can be fully encoded and safely shared across threads.
120pub trait EncodeShared: Encode + Send + Sync {}
121
122// Automatically implement `EncodeShared` for types that meet all bounds.
123impl<T: Encode + Send + Sync> EncodeShared for T {}
124
125/// Trait combining [Read] with a check for remaining bytes.
126///
127/// Ensures that *all* bytes from the input buffer were consumed during decoding.
128pub trait Decode: Read {
129 /// Decodes a value from `buf` using `cfg`, ensuring the entire buffer is consumed.
130 ///
131 /// Returns [Error] if decoding fails via [Read::read_cfg] or if there are leftover bytes in
132 /// `buf` after reading.
133 fn decode_cfg(mut buf: impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
134 let result = Self::read_cfg(&mut buf, cfg)?;
135
136 // Check that the buffer is fully consumed.
137 let remaining = buf.remaining();
138 if remaining > 0 {
139 return Err(Error::ExtraData(remaining));
140 }
141
142 Ok(result)
143 }
144}
145
146// Automatically implement `Decode` for types that implement `Read`.
147impl<T: Read> Decode for T {}
148
149/// Convenience trait combining [Encode] and [Decode].
150///
151/// Represents types that can be both fully encoded and decoded.
152pub trait Codec: Encode + Decode {}
153
154/// Automatically implement `Codec` for types that implement `Encode` and `Decode`.
155impl<T: Encode + Decode> Codec for T {}
156
157/// Convenience trait for [FixedSize] types that can be encoded directly into a fixed-size array.
158pub trait EncodeFixed: Write + FixedSize {
159 /// Encodes `self` into a fixed-size byte array `[u8; N]`.
160 ///
161 /// # Panics
162 ///
163 /// Panics if `N` is not equal to `<Self as FixedSize>::SIZE`.
164 /// Also panics if the `write()` implementation does not write exactly `N` bytes.
165 fn encode_fixed<const N: usize>(&self) -> [u8; N] {
166 // Ideally this is a compile-time check, but we can't do that in the current Rust version
167 // without adding a new generic parameter to the trait.
168 assert_eq!(
169 N,
170 Self::SIZE,
171 "Can't encode {} bytes into {} bytes",
172 Self::SIZE,
173 N
174 );
175
176 let mut array = [0u8; N];
177 let mut buf = &mut array[..];
178 self.write(&mut buf);
179 assert_eq!(buf.len(), 0);
180 array
181 }
182}
183
184// Automatically implement `EncodeFixed` for types that implement `Write` and `FixedSize`.
185impl<T: Write + FixedSize> EncodeFixed for T {}
186
187/// Convenience trait combining `FixedSize` and `Codec`.
188///
189/// Represents types that can be both fully encoded and decoded from a fixed-size byte sequence.
190pub trait CodecFixed: Codec + FixedSize {}
191
192// Automatically implement `CodecFixed` for types that implement `Codec` and `FixedSize`.
193impl<T: Codec + FixedSize> CodecFixed for T {}
194
195/// Convenience trait combining `Codec` with thread-safety bounds.
196///
197/// Represents types that can be fully encoded/decoded and safely shared across threads.
198pub trait CodecShared: Codec + Send + Sync {}
199
200// Automatically implement `CodecShared` for types that meet all bounds.
201impl<T: Codec + Send + Sync> CodecShared for T {}
202
203/// Convenience trait combining `CodecFixed` with thread-safety bounds and unit config.
204///
205/// Represents fixed-size types that can be fully encoded/decoded, require no configuration,
206/// and can be safely shared across threads.
207pub trait CodecFixedShared: CodecFixed<Cfg = ()> + Send + Sync {}
208
209// Automatically implement `CodecFixedShared` for types that meet all bounds.
210impl<T: CodecFixed<Cfg = ()> + Send + Sync> CodecFixedShared for T {}
211
212/// A [`BufMut`] that can also append pre-existing [`Bytes`] chunks.
213pub trait BufsMut: BufMut {
214 /// Appends a [`Bytes`] chunk instead of writing its contents inline into
215 /// the destination buffer.
216 fn push(&mut self, bytes: impl Into<Bytes>);
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::{
223 extensions::{DecodeExt, ReadExt},
224 Error,
225 };
226 use bytes::Bytes;
227
228 #[test]
229 fn test_insufficient_buffer() {
230 let mut reader = Bytes::from_static(&[0x01, 0x02]);
231 assert!(matches!(u32::read(&mut reader), Err(Error::EndOfBuffer)));
232 }
233
234 #[test]
235 fn test_extra_data() {
236 let encoded = Bytes::from_static(&[0x01, 0x02]);
237 assert!(matches!(u8::decode(encoded), Err(Error::ExtraData(1))));
238 }
239
240 #[test]
241 fn test_encode_fixed() {
242 let value = 42u32;
243 let encoded: [u8; 4] = value.encode_fixed();
244 let decoded = <u32>::decode(&encoded[..]).unwrap();
245 assert_eq!(value, decoded);
246 }
247
248 #[test]
249 #[should_panic(expected = "Can't encode 4 bytes into 5 bytes")]
250 fn test_encode_fixed_panic() {
251 let _: [u8; 5] = 42u32.encode_fixed();
252 }
253}