commonware_codec/types/
primitives.rs

1//! Codec implementations for Rust primitive types.
2//!
3//! # Fixed-size vs Variable-size
4//!
5//! Most primitives therefore have a compile-time constant `SIZE` and can be
6//! encoded/decoded without any configuration.
7//!
8//! `usize` is the lone exception: since most values refer to a length or size
9//! of an object in memory, values are biased towards smaller values. Therefore,
10//! it uses variable-length (varint) encoding to save space.  This means that
11//! it **does not implement [`FixedSize`]**.  When decoding a `usize`, callers
12//! must supply a [`RangeCfg`] to bound the allowable value — this protects
13//! against denial-of-service attacks that would allocate oversized buffers.
14//!
15//! ## Safety & portability
16//! * `usize` is restricted to values that fit in a `u32` to keep the on-wire
17//!   format identical across 32-bit and 64-bit architectures.
18//! * All fixed-size integers and floats are written big-endian to avoid host-
19//!   endian ambiguity.
20
21use crate::{
22    util::at_least, varint::UInt, EncodeSize, Error, FixedSize, RangeCfg, Read, ReadExt, Write,
23};
24use bytes::{Buf, BufMut};
25
26// Numeric types implementation
27macro_rules! impl_numeric {
28    ($type:ty, $read_method:ident, $write_method:ident) => {
29        impl Write for $type {
30            #[inline]
31            fn write(&self, buf: &mut impl BufMut) {
32                buf.$write_method(*self);
33            }
34        }
35
36        impl Read for $type {
37            type Cfg = ();
38            #[inline]
39            fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
40                at_least(buf, std::mem::size_of::<$type>())?;
41                Ok(buf.$read_method())
42            }
43        }
44
45        impl FixedSize for $type {
46            const SIZE: usize = std::mem::size_of::<$type>();
47        }
48    };
49}
50
51impl_numeric!(u8, get_u8, put_u8);
52impl_numeric!(u16, get_u16, put_u16);
53impl_numeric!(u32, get_u32, put_u32);
54impl_numeric!(u64, get_u64, put_u64);
55impl_numeric!(u128, get_u128, put_u128);
56impl_numeric!(i8, get_i8, put_i8);
57impl_numeric!(i16, get_i16, put_i16);
58impl_numeric!(i32, get_i32, put_i32);
59impl_numeric!(i64, get_i64, put_i64);
60impl_numeric!(i128, get_i128, put_i128);
61impl_numeric!(f32, get_f32, put_f32);
62impl_numeric!(f64, get_f64, put_f64);
63
64// Usize implementation
65impl Write for usize {
66    #[inline]
67    fn write(&self, buf: &mut impl BufMut) {
68        let self_as_u32 = u32::try_from(*self).expect("write: usize value is larger than u32");
69        UInt(self_as_u32).write(buf);
70    }
71}
72
73impl Read for usize {
74    type Cfg = RangeCfg;
75
76    #[inline]
77    fn read_cfg(buf: &mut impl Buf, range: &Self::Cfg) -> Result<Self, Error> {
78        let self_as_u32: u32 = UInt::read(buf)?.into();
79        let result = usize::try_from(self_as_u32).map_err(|_| Error::InvalidUsize)?;
80        if !range.contains(&result) {
81            return Err(Error::InvalidLength(result));
82        }
83        Ok(result)
84    }
85}
86
87impl EncodeSize for usize {
88    #[inline]
89    fn encode_size(&self) -> usize {
90        let self_as_u32 =
91            u32::try_from(*self).expect("encode_size: usize value is larger than u32");
92        UInt(self_as_u32).encode_size()
93    }
94}
95
96// Bool implementation
97impl Write for bool {
98    #[inline]
99    fn write(&self, buf: &mut impl BufMut) {
100        buf.put_u8(if *self { 1 } else { 0 });
101    }
102}
103
104impl Read for bool {
105    type Cfg = ();
106    #[inline]
107    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
108        match u8::read(buf)? {
109            0 => Ok(false),
110            1 => Ok(true),
111            _ => Err(Error::InvalidBool),
112        }
113    }
114}
115
116impl FixedSize for bool {
117    const SIZE: usize = 1;
118}
119
120// Constant-size array implementation
121impl<const N: usize> Write for [u8; N] {
122    #[inline]
123    fn write(&self, buf: &mut impl BufMut) {
124        buf.put(&self[..]);
125    }
126}
127
128impl<const N: usize> Read for [u8; N] {
129    type Cfg = ();
130    #[inline]
131    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
132        at_least(buf, N)?;
133        let mut dst = [0; N];
134        buf.copy_to_slice(&mut dst);
135        Ok(dst)
136    }
137}
138
139impl<const N: usize> FixedSize for [u8; N] {
140    const SIZE: usize = N;
141}
142
143// Option implementation
144impl<T: Write> Write for Option<T> {
145    #[inline]
146    fn write(&self, buf: &mut impl BufMut) {
147        self.is_some().write(buf);
148        if let Some(inner) = self {
149            inner.write(buf);
150        }
151    }
152}
153
154impl<T: EncodeSize> EncodeSize for Option<T> {
155    #[inline]
156    fn encode_size(&self) -> usize {
157        match self {
158            Some(inner) => 1 + inner.encode_size(),
159            None => 1,
160        }
161    }
162}
163
164impl<T: Read> Read for Option<T> {
165    type Cfg = T::Cfg;
166
167    #[inline]
168    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
169        if bool::read(buf)? {
170            Ok(Some(T::read_cfg(buf, cfg)?))
171        } else {
172            Ok(None)
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use crate::{Decode, DecodeExt, Encode, EncodeFixed};
181    use bytes::{Bytes, BytesMut};
182    use paste::paste;
183
184    // Float tests
185    macro_rules! impl_num_test {
186        ($type:ty, $size:expr) => {
187            paste! {
188                #[test]
189                fn [<test_ $type>]() {
190                    let expected_len = std::mem::size_of::<$type>();
191                    let values: [$type; 5] =
192                        [0 as $type, 1 as $type, 42 as $type, <$type>::MAX, <$type>::MIN];
193                    for value in values.iter() {
194                        let encoded = value.encode();
195                        assert_eq!(encoded.len(), expected_len);
196                        let decoded = <$type>::decode(encoded).unwrap();
197                        assert_eq!(*value, decoded);
198                        assert_eq!(value.encode_size(), expected_len);
199
200                        let fixed: [u8; $size] = value.encode_fixed();
201                        assert_eq!(fixed.len(), expected_len);
202                        let decoded = <$type>::decode(Bytes::copy_from_slice(&fixed)).unwrap();
203                        assert_eq!(*value, decoded);
204                    }
205                }
206            }
207        };
208    }
209    impl_num_test!(u8, 1);
210    impl_num_test!(u16, 2);
211    impl_num_test!(u32, 4);
212    impl_num_test!(u64, 8);
213    impl_num_test!(u128, 16);
214    impl_num_test!(i8, 1);
215    impl_num_test!(i16, 2);
216    impl_num_test!(i32, 4);
217    impl_num_test!(i64, 8);
218    impl_num_test!(i128, 16);
219    impl_num_test!(f32, 4);
220    impl_num_test!(f64, 8);
221
222    #[test]
223    fn test_endianness() {
224        // u16
225        let encoded = 0x0102u16.encode();
226        assert_eq!(encoded, Bytes::from_static(&[0x01, 0x02]));
227
228        // u32
229        let encoded = 0x01020304u32.encode();
230        assert_eq!(encoded, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
231
232        // f32
233        let encoded = 1.0f32.encode();
234        assert_eq!(encoded, Bytes::from_static(&[0x3F, 0x80, 0x00, 0x00])); // Big-endian IEEE 754
235    }
236
237    #[test]
238    fn test_bool() {
239        let values = [true, false];
240        for value in values.iter() {
241            let encoded = value.encode();
242            assert_eq!(encoded.len(), 1);
243            let decoded = bool::decode(encoded).unwrap();
244            assert_eq!(*value, decoded);
245            assert_eq!(value.encode_size(), 1);
246        }
247    }
248
249    #[test]
250    fn test_usize() {
251        let values = [0usize, 1, 42, u32::MAX as usize];
252        for value in values.iter() {
253            let encoded = value.encode();
254            assert_eq!(value.encode_size(), UInt(*value as u32).encode_size());
255            let decoded = usize::decode_cfg(encoded, &(..).into()).unwrap();
256            assert_eq!(*value, decoded);
257        }
258    }
259
260    #[cfg(target_pointer_width = "64")]
261    #[test]
262    #[should_panic(expected = "encode_size: usize value is larger than u32")]
263    fn test_usize_encode_panic() {
264        let value: usize = usize::MAX;
265        let _ = value.encode();
266    }
267
268    #[test]
269    #[should_panic(expected = "write: usize value is larger than u32")]
270    fn test_usize_write_panic() {
271        let mut buf = &mut BytesMut::new();
272        let value: usize = usize::MAX;
273        value.write(&mut buf);
274    }
275
276    #[test]
277    fn test_array() {
278        let values = [1u8, 2, 3];
279        let encoded = values.encode();
280        let decoded = <[u8; 3]>::decode(encoded).unwrap();
281        assert_eq!(values, decoded);
282    }
283
284    #[test]
285    fn test_option() {
286        let option_values = [Some(42u32), None];
287        for value in option_values {
288            let encoded = value.encode();
289            let decoded = Option::<u32>::decode(encoded).unwrap();
290            assert_eq!(value, decoded);
291        }
292    }
293
294    #[test]
295    fn test_option_length() {
296        let some = Some(42u32);
297        assert_eq!(some.encode_size(), 1 + 4);
298        assert_eq!(some.encode().len(), 1 + 4);
299        let none: Option<u32> = None;
300        assert_eq!(none.encode_size(), 1);
301        assert_eq!(none.encode().len(), 1);
302    }
303}