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