commonware_codec/codec.rs
1//! Core traits for encoding and decoding.
2
3use crate::error::Error;
4use bytes::{Buf, BufMut, 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 [BytesMut] 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) -> BytesMut {
77 let len = self.encode_size();
78 let mut buffer = BytesMut::with_capacity(len);
79 self.write(&mut buffer);
80 assert_eq!(buffer.len(), len, "write() did not write expected bytes");
81 buffer
82 }
83}
84
85// Automatically implement `Encode` for types that implement `Write` and `EncodeSize`.
86impl<T: Write + EncodeSize> Encode for T {}
87
88/// Trait combining [Read] with a check for remaining bytes.
89///
90/// Ensures that *all* bytes from the input buffer were consumed during decoding.
91pub trait Decode: Read {
92 /// Decodes a value from `buf` using `cfg`, ensuring the entire buffer is consumed.
93 ///
94 /// Returns [Error] if decoding fails via [Read::read_cfg] or if there are leftover bytes in
95 /// `buf` after reading.
96 fn decode_cfg(mut buf: impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
97 let result = Self::read_cfg(&mut buf, cfg)?;
98
99 // Check that the buffer is fully consumed.
100 let remaining = buf.remaining();
101 if remaining > 0 {
102 return Err(Error::ExtraData(remaining));
103 }
104
105 Ok(result)
106 }
107}
108
109// Automatically implement `Decode` for types that implement `Read`.
110impl<T: Read> Decode for T {}
111
112/// Convenience trait combining [Encode] and [Decode].
113///
114/// Represents types that can be both fully encoded and decoded.
115pub trait Codec: Encode + Decode {}
116
117/// Automatically implement `Codec` for types that implement `Encode` and `Decode`.
118impl<T: Encode + Decode> Codec for T {}
119
120/// Convenience trait for [FixedSize] types that can be encoded directly into a fixed-size array.
121pub trait EncodeFixed: Write + FixedSize {
122 /// Encodes `self` into a fixed-size byte array `[u8; N]`.
123 ///
124 /// # Panics
125 ///
126 /// Panics if `N` is not equal to `<Self as FixedSize>::SIZE`.
127 /// Also panics if the `write()` implementation does not write exactly `N` bytes.
128 fn encode_fixed<const N: usize>(&self) -> [u8; N] {
129 // Ideally this is a compile-time check, but we can't do that in the current Rust version
130 // without adding a new generic parameter to the trait.
131 assert_eq!(
132 N,
133 Self::SIZE,
134 "Can't encode {} bytes into {} bytes",
135 Self::SIZE,
136 N
137 );
138
139 let mut array = [0u8; N];
140 let mut buf = &mut array[..];
141 self.write(&mut buf);
142 assert_eq!(buf.len(), 0);
143 array
144 }
145}
146
147// Automatically implement `EncodeFixed` for types that implement `Write` and `FixedSize`.
148impl<T: Write + FixedSize> EncodeFixed for T {}
149
150/// Convenience trait combining `FixedSize` and `Codec`.
151///
152/// Represents types that can be both fully encoded and decoded from a fixed-size byte sequence.
153pub trait CodecFixed: Codec + FixedSize {}
154
155// Automatically implement `CodecFixed` for types that implement `Codec` and `FixedSize`.
156impl<T: Codec + FixedSize> CodecFixed for T {}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::{
162 extensions::{DecodeExt, ReadExt},
163 Error,
164 };
165 use bytes::Bytes;
166
167 #[test]
168 fn test_insufficient_buffer() {
169 let mut reader = Bytes::from_static(&[0x01, 0x02]);
170 assert!(matches!(u32::read(&mut reader), Err(Error::EndOfBuffer)));
171 }
172
173 #[test]
174 fn test_extra_data() {
175 let encoded = Bytes::from_static(&[0x01, 0x02]);
176 assert!(matches!(u8::decode(encoded), Err(Error::ExtraData(1))));
177 }
178
179 #[test]
180 fn test_encode_fixed() {
181 let value = 42u32;
182 let encoded: [u8; 4] = value.encode_fixed();
183 let decoded = <u32>::decode(&encoded[..]).unwrap();
184 assert_eq!(value, decoded);
185 }
186
187 #[test]
188 #[should_panic(expected = "Can't encode 4 bytes into 5 bytes")]
189 fn test_encode_fixed_panic() {
190 let _: [u8; 5] = 42u32.encode_fixed();
191 }
192}