commonware_codec/
codec.rs

1//! Core traits for encoding and decoding.
2
3use crate::error::Error;
4use bytes::{Buf, BufMut, BytesMut};
5use std::ops::RangeBounds;
6
7/// Marker trait for types that can be used as configuration during decoding.
8///
9/// Configuration is primarily used with the [`Read`] trait to pass parameters (like size limits)
10/// needed to safely decode untrusted data. Types implementing `Config` must also be
11/// `Clone + Send + 'static`.
12///
13/// Use the unit type `()` if no configuration is required for a specific [`Read`] implementation.
14pub trait Config: Clone + Send + 'static {}
15
16// Automatically implement `Config` for matching types.
17impl<T: Clone + Send + 'static> Config for T {}
18
19/// A marker trait for a [`Config`] type that is also a [`RangeBounds<usize>`].
20///
21/// This is often used to configure length limits for variable-length collections like `Vec<T>` or
22/// `Bytes`.
23pub trait RangeConfig: Config + RangeBounds<usize> {}
24
25// Automatically implement `RangeConfig` for matching types.
26impl<T: Config + RangeBounds<usize>> RangeConfig for T {}
27
28/// Trait for types with a known, fixed encoded size.
29///
30/// Implementing this trait signifies that the encoded representation of this type *always* has the
31/// same byte length, regardless of the specific value.
32///
33/// This automatically provides an implementation of [`EncodeSize`].
34pub trait FixedSize {
35    /// The size of the encoded value (in bytes).
36    const SIZE: usize;
37}
38
39/// Trait for types that can provide their encoded size in bytes.
40///
41/// This must be implemented by all encodable types. For types implementing [`FixedSize`], this
42/// trait is implemented automatically. For variable-size types, this requires calculating the size
43/// based on the value.
44pub trait EncodeSize {
45    /// Returns the encoded size of this value (in bytes).
46    fn encode_size(&self) -> usize;
47}
48
49// Automatically implement `EncodeSize` for types that are `FixedSize`.
50impl<T: FixedSize> EncodeSize for T {
51    fn encode_size(&self) -> usize {
52        Self::SIZE
53    }
54}
55
56/// Trait for types that can be written (encoded) to a byte buffer.
57pub trait Write {
58    /// Writes the binary representation of `self` to the provided buffer `buf`.
59    ///
60    /// Implementations should panic if the buffer doesn't have enough capacity.
61    fn write(&self, buf: &mut impl BufMut);
62}
63
64/// Trait for types that can be read (decoded) from a byte buffer.
65///
66/// The `Cfg` type parameter allows passing configuration during the read process. This is crucial
67/// for safely decoding untrusted data, for example, by providing size limits for collections or
68/// strings.
69///
70/// Use `Cfg = ()` if no configuration is needed for a specific type.
71pub trait Read<Cfg: Config = ()>: Sized {
72    /// Reads a value from the buffer using the provided configuration `cfg`.
73    ///
74    /// Implementations should consume the exact number of bytes required from `buf` to reconstruct
75    /// the value.
76    ///
77    /// Returns [`Error`] if decoding fails due to invalid data, insufficient bytes in the buffer,
78    /// or violation of constraints imposed by the `cfg`.
79    fn read_cfg(buf: &mut impl Buf, cfg: &Cfg) -> Result<Self, Error>;
80}
81
82/// Trait combining [`Write`] and [`EncodeSize`] for types that can be fully encoded.
83///
84/// This trait provides the convenience [`encode`](Encode::encode) method which handles
85/// buffer allocation, writing, and size assertion in one go.
86pub trait Encode: Write + EncodeSize {
87    /// Encodes `self` into a new [`BytesMut`] buffer.
88    ///
89    /// This method calculates the required size using [`EncodeSize::encode_size`], allocates a
90    /// buffer of that exact capacity, writes the value using [`Write::write`], and performs a
91    /// sanity check assertion.
92    ///
93    /// Panics if `encode_size()` does not return the same number of bytes actually written by
94    /// `write()`
95    fn encode(&self) -> BytesMut {
96        let len = self.encode_size();
97        let mut buffer = BytesMut::with_capacity(len);
98        self.write(&mut buffer);
99        assert_eq!(buffer.len(), len, "write() did not write expected bytes");
100        buffer
101    }
102}
103
104// Automatically implement `Encode` for types that implement `Write` and `EncodeSize`.
105impl<T: Write + EncodeSize> Encode for T {}
106
107/// Trait combining [`Read<Cfg>`] with a check for remaining bytes.
108///
109/// Ensures that *all* bytes from the input buffer were consumed during decoding.
110pub trait Decode<Cfg: Config = ()>: Read<Cfg> {
111    /// Decodes a value from `buf` using `cfg`, ensuring the entire buffer is consumed.
112    ///
113    /// Returns [`Error`] if decoding fails via [`Read::read_cfg`] or if there are leftover bytes in
114    /// `buf` after reading.
115    fn decode_cfg(mut buf: impl Buf, cfg: &Cfg) -> Result<Self, Error> {
116        let result = Self::read_cfg(&mut buf, cfg)?;
117
118        // Check that the buffer is fully consumed.
119        let remaining = buf.remaining();
120        if remaining > 0 {
121            return Err(Error::ExtraData(remaining));
122        }
123
124        Ok(result)
125    }
126}
127
128// Automatically implement `Decode` for types that implement `Read`.
129impl<Cfg: Config, T: Read<Cfg>> Decode<Cfg> for T {}
130
131/// Convenience trait combining [`Encode`] and [`Decode<Cfg>`].
132///
133/// Represents types that can be both fully encoded and decoded.
134pub trait Codec<Cfg: Config = ()>: Encode + Decode<Cfg> {}
135
136/// Automatically implement `Codec` for types that implement `Encode` and `Decode`.
137impl<Cfg: Config, T: Encode + Decode<Cfg>> Codec<Cfg> for T {}
138
139/// Convenience trait for [`FixedSize`] types that can be encoded directly into a fixed-size array.
140pub trait EncodeFixed: Write + FixedSize {
141    /// Encodes `self` into a fixed-size byte array `[u8; N]`.
142    ///
143    /// `N` **must** be equal to `<Self as FixedSize>::SIZE`.
144    /// Panics if `N` is not equal to `<Self as FixedSize>::SIZE`.
145    /// Also panics if the `write()` implementation does not write exactly `N` bytes.
146    fn encode_fixed<const N: usize>(&self) -> [u8; N] {
147        // Ideally this is a compile-time check, but we can't do that in the current Rust version
148        // without adding a new generic parameter to the trait.
149        assert_eq!(
150            N,
151            Self::SIZE,
152            "Can't encode {} bytes into {} bytes",
153            Self::SIZE,
154            N
155        );
156
157        let mut array = [0u8; N];
158        let mut buf = &mut array[..];
159        self.write(&mut buf);
160        assert_eq!(buf.len(), 0);
161        array
162    }
163}
164
165// Automatically implement `EncodeFixed` for types that implement `Write` and `FixedSize`.
166impl<T: Write + FixedSize> EncodeFixed for T {}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::{
172        extensions::{DecodeExt, ReadExt},
173        Error,
174    };
175    use bytes::Bytes;
176
177    #[test]
178    fn test_insufficient_buffer() {
179        let mut reader = Bytes::from_static(&[0x01, 0x02]);
180        assert!(matches!(u32::read(&mut reader), Err(Error::EndOfBuffer)));
181    }
182
183    #[test]
184    fn test_extra_data() {
185        let encoded = Bytes::from_static(&[0x01, 0x02]);
186        assert!(matches!(u8::decode(encoded), Err(Error::ExtraData(1))));
187    }
188
189    #[test]
190    fn test_encode_fixed() {
191        let value = 42u32;
192        let encoded: [u8; 4] = value.encode_fixed();
193        let decoded = <u32>::decode(&encoded[..]).unwrap();
194        assert_eq!(value, decoded);
195    }
196
197    #[test]
198    #[should_panic(expected = "Can't encode 4 bytes into 5 bytes")]
199    fn test_encode_fixed_panic() {
200        let _: [u8; 5] = 42u32.encode_fixed();
201    }
202}