Skip to main content

commonware_codec/
codec.rs

1//! Core traits for encoding and decoding.
2
3use crate::error::Error;
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6use bytes::{Buf, BufMut, Bytes, BytesMut};
7#[cfg(feature = "std")]
8use std::vec::Vec;
9
10/// Trait for types with a known, fixed encoded size.
11///
12/// Implementing this trait signifies that the encoded representation of this type *always* has the
13/// same byte length, regardless of the specific value.
14///
15/// This automatically provides an implementation of [EncodeSize].
16pub trait FixedSize {
17    /// The size of the encoded value (in bytes).
18    const SIZE: usize;
19}
20
21/// Trait for types that can provide their encoded size in bytes.
22///
23/// This must be implemented by all encodable types. For types implementing [FixedSize], this
24/// trait is implemented automatically. For variable-size types, this requires calculating the size
25/// based on the value.
26pub trait EncodeSize {
27    /// Returns the encoded size of this value (in bytes).
28    fn encode_size(&self) -> usize;
29
30    /// Returns the total encoded size of a sequence, excluding any container-specific length
31    /// prefix.
32    ///
33    /// Container implementations call this hook so element types can provide a more efficient
34    /// aggregate size calculation. The default preserves normal element-by-element sizing.
35    /// Fixed-size implementations compute `SIZE * len`, which avoids an O(n) sizing prepass
36    /// before containers such as `Vec<T>` allocate their output buffer.
37    ///
38    /// This hook exists because stable Rust cannot express the overlapping specialized
39    /// container impls that would otherwise provide this aggregate path directly.
40    ///
41    /// This is hidden from generated documentation because it is an implementation hook for
42    /// codec's container types, not part of the intended user-facing API. Most users should
43    /// implement [EncodeSize::encode_size] or [FixedSize] instead.
44    #[doc(hidden)]
45    #[inline]
46    fn encode_size_slice(values: &[Self]) -> usize
47    where
48        Self: Sized,
49    {
50        values.iter().map(EncodeSize::encode_size).sum()
51    }
52
53    /// Returns the encoded size excluding bytes passed to [`BufsMut::push`]
54    /// during [`Write::write_bufs`]. Used to size the working buffer for inline
55    /// writes. Override alongside [`Write::write_bufs`] for types where large
56    /// [`Bytes`] fields go via push; failing to do so will over-allocate.
57    #[inline]
58    fn encode_inline_size(&self) -> usize {
59        self.encode_size()
60    }
61
62    /// Returns the total inline encoded size of a sequence, excluding any container-specific
63    /// length prefix.
64    ///
65    /// This hidden hook is the slice equivalent of [EncodeSize::encode_inline_size]. The
66    /// default preserves normal element-by-element sizing. Fixed-size implementations override
67    /// this to compute `SIZE * len`, matching [EncodeSize::encode_size_slice] for the
68    /// [`Write::write_bufs`] path.
69    ///
70    /// This hook exists because stable Rust cannot express the overlapping specialized
71    /// container impls that would otherwise provide this aggregate path directly.
72    ///
73    /// This is hidden from generated documentation for the same reason as
74    /// [EncodeSize::encode_size_slice].
75    #[doc(hidden)]
76    #[inline]
77    fn encode_inline_size_slice(values: &[Self]) -> usize
78    where
79        Self: Sized,
80    {
81        values
82            .iter()
83            .map(EncodeSize::encode_inline_size)
84            .sum::<usize>()
85    }
86}
87
88// Automatically implement `EncodeSize` for types that are `FixedSize`.
89impl<T: FixedSize> EncodeSize for T {
90    #[inline]
91    fn encode_size(&self) -> usize {
92        Self::SIZE
93    }
94
95    #[inline]
96    fn encode_size_slice(values: &[Self]) -> usize
97    where
98        Self: Sized,
99    {
100        Self::SIZE * values.len()
101    }
102
103    #[inline]
104    fn encode_inline_size_slice(values: &[Self]) -> usize
105    where
106        Self: Sized,
107    {
108        Self::encode_size_slice(values)
109    }
110}
111
112/// Trait for types that can be written (encoded) to a byte buffer.
113pub trait Write {
114    /// Writes the binary representation of `self` to the provided buffer `buf`.
115    ///
116    /// Implementations should panic if the buffer doesn't have enough capacity.
117    fn write(&self, buf: &mut impl BufMut);
118
119    /// Writes the encoded payload for a sequence, excluding any container-specific length
120    /// prefix.
121    ///
122    /// Container implementations call this hook so element types can provide a more efficient
123    /// aggregate write path. The default preserves normal element-by-element encoding.
124    ///
125    /// This hook exists because stable Rust cannot express the overlapping specialized
126    /// container impls that would otherwise provide this aggregate path directly.
127    ///
128    /// This is hidden from generated documentation because it is an implementation hook for
129    /// codec's container types, not part of the intended user-facing API. Most users should
130    /// implement [Write::write] instead.
131    #[doc(hidden)]
132    #[inline]
133    fn write_slice(values: &[Self], buf: &mut impl BufMut)
134    where
135        Self: Sized,
136    {
137        for item in values {
138            item.write(buf);
139        }
140    }
141
142    /// Writes to a [`BufsMut`], allowing existing [`Bytes`] chunks to be
143    /// appended via [`BufsMut::push`] instead of written inline. Must encode
144    /// to the same format as [`Write::write`]. Defaults to [`Write::write`].
145    #[inline]
146    fn write_bufs(&self, buf: &mut impl BufsMut) {
147        self.write(buf);
148    }
149
150    /// Writes the encoded payload for a sequence to a [`BufsMut`], excluding any
151    /// container-specific length prefix.
152    ///
153    /// This hidden hook is the slice equivalent of [Write::write_bufs]. The default preserves
154    /// normal element-by-element encoding.
155    ///
156    /// This hook exists because stable Rust cannot express the overlapping specialized
157    /// container impls that would otherwise provide this aggregate path directly.
158    ///
159    /// This is hidden from generated documentation for the same reason as [Write::write_slice].
160    #[doc(hidden)]
161    #[inline]
162    fn write_slice_bufs(values: &[Self], buf: &mut impl BufsMut)
163    where
164        Self: Sized,
165    {
166        for item in values {
167            item.write_bufs(buf);
168        }
169    }
170}
171
172/// Trait for types that can be read (decoded) from a byte buffer.
173pub trait Read: Sized {
174    /// The `Cfg` type parameter allows passing configuration during the read process. This is
175    /// crucial for safely decoding untrusted data, for example, by providing size limits for
176    /// collections or strings.
177    ///
178    /// Use `Cfg = ()` if no configuration is needed for a specific type.
179    type Cfg: Clone + Send + Sync + 'static;
180
181    /// Reads a value from the buffer using the provided configuration `cfg`.
182    ///
183    /// Implementations should consume the exact number of bytes required from `buf` to reconstruct
184    /// the value.
185    ///
186    /// Returns [Error] if decoding fails due to invalid data, insufficient bytes in the buffer,
187    /// or violation of constraints imposed by the `cfg`.
188    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error>;
189
190    /// Reads `len` values from the buffer into a vector.
191    ///
192    /// Container implementations call this hook so element types can provide a more efficient
193    /// vector read path. The default preserves normal element-by-element decoding.
194    ///
195    /// This hook exists because stable Rust cannot express the overlapping specialized
196    /// container impls that would otherwise provide this aggregate path directly.
197    ///
198    /// This is hidden from generated documentation because it is an implementation hook for
199    /// codec's container types, not part of the intended user-facing API. Most users should
200    /// implement [Read::read_cfg] instead.
201    #[doc(hidden)]
202    #[inline]
203    fn read_vec(buf: &mut impl Buf, len: usize, cfg: &Self::Cfg) -> Result<Vec<Self>, Error> {
204        let mut values = Vec::with_capacity(len);
205        for _ in 0..len {
206            values.push(Self::read_cfg(buf, cfg)?);
207        }
208        Ok(values)
209    }
210
211    /// Reads exactly `N` values from the buffer into an array.
212    ///
213    /// This hidden hook is the array equivalent of [Read::read_vec]. The default preserves
214    /// normal element-by-element decoding.
215    ///
216    /// This hook exists because stable Rust cannot express the overlapping specialized array
217    /// impls that would otherwise provide this aggregate path directly.
218    ///
219    /// This is hidden from generated documentation for the same reason as [Read::read_vec].
220    #[doc(hidden)]
221    #[inline]
222    fn read_array<const N: usize>(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<[Self; N], Error> {
223        Ok(Self::read_vec(buf, N, cfg)?
224            .try_into()
225            .unwrap_or_else(|_| unreachable!("array length should match capacity")))
226    }
227}
228
229/// Trait combining [Write] and [EncodeSize] for types that can be fully encoded.
230///
231/// This trait provides the convenience [Encode::encode] method which handles
232/// buffer allocation, writing, and size assertion in one go.
233pub trait Encode: Write + EncodeSize {
234    /// Encodes `self` into a new [Bytes] buffer.
235    ///
236    /// This method calculates the required size using [EncodeSize::encode_size], allocates a
237    /// buffer of that exact capacity, writes the value using [Write::write], and performs a
238    /// sanity check assertion.
239    ///
240    /// # Panics
241    ///
242    /// Panics if `encode_size()` does not return the same number of bytes actually written by
243    /// `write()`
244    fn encode(&self) -> Bytes {
245        self.encode_mut().freeze()
246    }
247
248    /// Encodes `self` into a new [BytesMut] buffer.
249    ///
250    /// This method calculates the required size using [EncodeSize::encode_size], allocates a
251    /// buffer of that exact capacity, writes the value using [Write::write], and performs a
252    /// sanity check assertion.
253    ///
254    /// # Panics
255    ///
256    /// Panics if `encode_size()` does not return the same number of bytes actually written by
257    /// `write()`
258    fn encode_mut(&self) -> BytesMut {
259        let len = self.encode_size();
260        let mut buffer = BytesMut::with_capacity(len);
261        self.write(&mut buffer);
262        assert_eq!(buffer.len(), len, "write() did not write expected bytes");
263        buffer
264    }
265}
266
267// Automatically implement `Encode` for types that implement `Write` and `EncodeSize`.
268impl<T: Write + EncodeSize> Encode for T {}
269
270/// Convenience trait combining `Encode` with thread-safety bounds.
271///
272/// Represents types that can be fully encoded and safely shared across threads.
273pub trait EncodeShared: Encode + Send + Sync {}
274
275// Automatically implement `EncodeShared` for types that meet all bounds.
276impl<T: Encode + Send + Sync> EncodeShared for T {}
277
278/// Trait combining [Read] with a check for remaining bytes.
279///
280/// Ensures that *all* bytes from the input buffer were consumed during decoding.
281pub trait Decode: Read {
282    /// Decodes a value from `buf` using `cfg`, ensuring the entire buffer is consumed.
283    ///
284    /// Returns [Error] if decoding fails via [Read::read_cfg] or if there are leftover bytes in
285    /// `buf` after reading.
286    fn decode_cfg(mut buf: impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
287        let result = Self::read_cfg(&mut buf, cfg)?;
288
289        // Check that the buffer is fully consumed.
290        let remaining = buf.remaining();
291        if remaining > 0 {
292            return Err(Error::ExtraData(remaining));
293        }
294
295        Ok(result)
296    }
297}
298
299// Automatically implement `Decode` for types that implement `Read`.
300impl<T: Read> Decode for T {}
301
302/// Convenience trait combining [Encode] and [Decode].
303///
304/// Represents types that can be both fully encoded and decoded.
305pub trait Codec: Encode + Decode {}
306
307/// Automatically implement `Codec` for types that implement `Encode` and `Decode`.
308impl<T: Encode + Decode> Codec for T {}
309
310/// Convenience trait for [FixedSize] types that can be encoded directly into a fixed-size array.
311pub trait EncodeFixed: Write + FixedSize {
312    /// Encodes `self` into a fixed-size byte array `[u8; N]`.
313    ///
314    /// # Panics
315    ///
316    /// Panics if `N` is not equal to `<Self as FixedSize>::SIZE`.
317    /// Also panics if the `write()` implementation does not write exactly `N` bytes.
318    fn encode_fixed<const N: usize>(&self) -> [u8; N] {
319        // Ideally this is a compile-time check, but we can't do that in the current Rust version
320        // without adding a new generic parameter to the trait.
321        assert_eq!(
322            N,
323            Self::SIZE,
324            "Can't encode {} bytes into {} bytes",
325            Self::SIZE,
326            N
327        );
328
329        let mut array = [0u8; N];
330        let mut buf = &mut array[..];
331        self.write(&mut buf);
332        assert_eq!(buf.len(), 0);
333        array
334    }
335}
336
337// Automatically implement `EncodeFixed` for types that implement `Write` and `FixedSize`.
338impl<T: Write + FixedSize> EncodeFixed for T {}
339
340/// Convenience trait combining `FixedSize` and `Codec`.
341///
342/// Represents types that can be both fully encoded and decoded from a fixed-size byte sequence.
343pub trait CodecFixed: Codec + FixedSize {}
344
345// Automatically implement `CodecFixed` for types that implement `Codec` and `FixedSize`.
346impl<T: Codec + FixedSize> CodecFixed for T {}
347
348/// Convenience trait combining `Codec` with thread-safety bounds.
349///
350/// Represents types that can be fully encoded/decoded and safely shared across threads.
351pub trait CodecShared: Codec + Send + Sync {}
352
353// Automatically implement `CodecShared` for types that meet all bounds.
354impl<T: Codec + Send + Sync> CodecShared for T {}
355
356/// Convenience trait combining `CodecFixed` with thread-safety bounds and unit config.
357///
358/// Represents fixed-size types that can be fully encoded/decoded, require no configuration,
359/// and can be safely shared across threads.
360pub trait CodecFixedShared: CodecFixed<Cfg = ()> + Send + Sync {}
361
362// Automatically implement `CodecFixedShared` for types that meet all bounds.
363impl<T: CodecFixed<Cfg = ()> + Send + Sync> CodecFixedShared for T {}
364
365/// A [`BufMut`] that can also append pre-existing [`Bytes`] chunks.
366pub trait BufsMut: BufMut {
367    /// Appends a [`Bytes`] chunk instead of writing its contents inline into
368    /// the destination buffer.
369    fn push(&mut self, bytes: impl Into<Bytes>);
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use crate::{
376        extensions::{DecodeExt, ReadExt},
377        Error,
378    };
379    use bytes::Bytes;
380
381    #[test]
382    fn test_insufficient_buffer() {
383        let mut reader = Bytes::from_static(&[0x01, 0x02]);
384        assert!(matches!(u32::read(&mut reader), Err(Error::EndOfBuffer)));
385    }
386
387    #[test]
388    fn test_extra_data() {
389        let encoded = Bytes::from_static(&[0x01, 0x02]);
390        assert!(matches!(u8::decode(encoded), Err(Error::ExtraData(1))));
391    }
392
393    #[test]
394    fn test_encode_fixed() {
395        let value = 42u32;
396        let encoded: [u8; 4] = value.encode_fixed();
397        let decoded = <u32>::decode(&encoded[..]).unwrap();
398        assert_eq!(value, decoded);
399    }
400
401    #[test]
402    #[should_panic(expected = "Can't encode 4 bytes into 5 bytes")]
403    fn test_encode_fixed_panic() {
404        let _: [u8; 5] = 42u32.encode_fixed();
405    }
406}