Skip to main content

commonware_codec/
mode.rs

1//! Compact encoding for ordered, extensible mode values.
2
3use crate::{EncodeSize, Error, Read, ReadExt, Write};
4use bytes::{Buf, BufMut};
5
6// The high bit is packet framing rather than mode value data.
7const CONTINUATION_BIT: u8 = 1 << 7;
8
9/// Error returned when a value cannot be represented as a [`Mode`].
10#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
11#[error("mode value must fit in seven bits")]
12pub struct InvalidMode;
13
14/// A seven-bit value in an ordered [`Modes`] packet.
15///
16/// The high bit is reserved for packet framing and cannot be represented by this type.
17///
18/// # Examples
19///
20/// ```
21/// use commonware_codec::Mode;
22///
23/// let mode = Mode::new(0x7f).unwrap();
24/// assert_eq!(u8::from(mode), 0x7f);
25/// assert!(Mode::new(0x80).is_none());
26/// ```
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
28pub struct Mode(u8);
29
30impl Mode {
31    /// Creates a mode value, or returns `None` when the reserved high bit is set.
32    pub const fn new(value: u8) -> Option<Self> {
33        if value < CONTINUATION_BIT {
34            Some(Self(value))
35        } else {
36            None
37        }
38    }
39}
40
41impl TryFrom<u8> for Mode {
42    type Error = InvalidMode;
43
44    fn try_from(value: u8) -> Result<Self, Self::Error> {
45        Self::new(value).ok_or(InvalidMode)
46    }
47}
48
49impl From<Mode> for u8 {
50    fn from(mode: Mode) -> Self {
51        mode.0
52    }
53}
54
55#[cfg(feature = "arbitrary")]
56impl<'a> arbitrary::Arbitrary<'a> for Mode {
57    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
58        Ok(Self(u.int_in_range(0..=(CONTINUATION_BIT - 1))?))
59    }
60}
61
62/// Creates a [`Mode`] from a `u8` literal or expression.
63///
64/// Literals are validated at compile time. Expressions are validated at runtime.
65///
66/// # Panics
67///
68/// The expression form panics if the reserved high bit is set. Use [`Mode::new`] or
69/// [`Mode::try_from`] to validate untrusted values without panicking.
70///
71/// # Examples
72///
73/// ```
74/// use commonware_codec::{Mode, mode};
75///
76/// const ENABLED: Mode = mode!(1);
77/// assert_eq!(u8::from(ENABLED), 1);
78/// ```
79///
80/// ```compile_fail
81/// use commonware_codec::{Mode, mode};
82///
83/// const INVALID: Mode = mode!(0x80);
84/// ```
85#[cfg(not(any(
86    commonware_stability_GAMMA,
87    commonware_stability_DELTA,
88    commonware_stability_EPSILON,
89    commonware_stability_RESERVED
90)))] // BETA
91#[macro_export]
92macro_rules! mode {
93    ($value:literal) => {
94        const { $crate::Mode::new($value).expect("mode value must fit in seven bits") }
95    };
96    ($value:expr) => {
97        $crate::Mode::new($value).expect("mode value must fit in seven bits")
98    };
99}
100
101/// Creates a canonical [`Modes`] packet from values convertible to [`Mode`].
102///
103/// Each expression is converted independently before the packet is constructed, so mode values
104/// may have different source types. Returns `None` when every converted value is zero.
105///
106/// # Examples
107///
108/// ```
109/// use commonware_codec::{Encode, mode, modes};
110///
111/// let modes = modes![mode!(1), mode!(1)].unwrap();
112/// assert_eq!(modes.encode().as_ref(), &[0x81, 0x01]);
113/// ```
114///
115/// ```compile_fail
116/// use commonware_codec::modes;
117///
118/// let _ = modes![1u8];
119/// ```
120#[cfg(not(any(
121    commonware_stability_GAMMA,
122    commonware_stability_DELTA,
123    commonware_stability_EPSILON,
124    commonware_stability_RESERVED
125)))] // BETA
126#[macro_export]
127macro_rules! modes {
128    ($($mode:expr),* $(,)?) => {
129        $crate::Modes::new([
130            $(::core::convert::Into::<$crate::Mode>::into($mode)),*
131        ])
132    };
133}
134
135/// A canonical packet of ordered mode values.
136///
137/// `N` is the maximum number of modes and must be greater than zero.
138///
139/// The high bit indicates that another mode follows, and trailing zero-valued
140/// modes are omitted. Appending a new mode with value zero therefore preserves
141/// the existing encoding. A `Modes` value denotes a present packet. An all-zero
142/// list denotes no packet.
143///
144/// # Examples
145///
146/// ```
147/// use commonware_codec::{DecodeExt, Encode, Modes, mode};
148///
149/// let modes = Modes::new([mode!(1), mode!(0), mode!(2)]).unwrap();
150/// let encoded = modes.encode();
151/// assert_eq!(encoded.as_ref(), &[0x81, 0x80, 0x02]);
152/// assert_eq!(Modes::<3>::decode(encoded).unwrap(), modes);
153/// ```
154///
155/// ```compile_fail
156/// use commonware_codec::Modes;
157///
158/// let _ = Modes::<0>::new([]);
159/// ```
160#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
161pub struct Modes<const N: usize> {
162    encoded: [u8; N],
163    len: usize,
164}
165
166impl<const N: usize> Modes<N> {
167    /// Creates a canonical packet from `modes`.
168    ///
169    /// Returns `None` when every mode is zero. Callers must represent this as an
170    /// absent packet rather than an empty packet.
171    ///
172    pub fn new(modes: [Mode; N]) -> Option<Self> {
173        const {
174            assert!(N > 0, "N must be greater than 0");
175        }
176
177        let mut encoded = modes.map(u8::from);
178        let last = encoded.iter().rposition(|&mode| mode != 0)?;
179        for mode in &mut encoded[..last] {
180            *mode |= CONTINUATION_BIT;
181        }
182        Some(Self {
183            encoded,
184            len: last + 1,
185        })
186    }
187}
188
189impl<const N: usize> Write for Modes<N> {
190    fn write(&self, buf: &mut impl BufMut) {
191        buf.put_slice(&self.encoded[..self.len]);
192    }
193}
194
195impl<const N: usize> EncodeSize for Modes<N> {
196    fn encode_size(&self) -> usize {
197        self.len
198    }
199}
200
201impl<const N: usize> Read for Modes<N> {
202    type Cfg = ();
203
204    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
205        const {
206            assert!(N > 0, "N must be greater than 0");
207        }
208
209        // Preserve framing bits in the stored representation while locating the
210        // canonical non-zero terminator.
211        let mut encoded = [0; N];
212        for index in 0..N {
213            let byte = u8::read(buf)?;
214            encoded[index] = byte;
215            if byte & CONTINUATION_BIT == 0 {
216                if byte == 0 {
217                    return Err(Error::Invalid("Modes", "trailing mode must be non-zero"));
218                }
219                return Ok(Self {
220                    encoded,
221                    len: index + 1,
222                });
223            }
224        }
225
226        Err(Error::Invalid("Modes", "too many mode values"))
227    }
228}
229
230#[cfg(feature = "arbitrary")]
231impl<'a, const N: usize> arbitrary::Arbitrary<'a> for Modes<N> {
232    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
233        const {
234            assert!(N > 0, "N must be greater than 0");
235        }
236
237        let len = u.int_in_range(1..=N)?;
238        let mut modes = [Mode(0); N];
239        for mode in &mut modes[..len - 1] {
240            *mode = u.arbitrary()?;
241        }
242        modes[len - 1] = Mode(u.int_in_range(1..=(CONTINUATION_BIT - 1))?);
243        Self::new(modes).ok_or(arbitrary::Error::IncorrectFormat)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use crate::{DecodeExt, Encode};
251
252    fn assert_encoding<const N: usize>(modes: [u8; N], expected: &[u8]) {
253        let modes = Modes::new(modes.map(|value| mode!(value))).unwrap();
254        assert_eq!(modes.encode_size(), expected.len());
255        let encoded = modes.encode();
256        assert_eq!(encoded.as_ref(), expected);
257        assert_eq!(Modes::<N>::decode(encoded).unwrap(), modes);
258    }
259
260    #[test]
261    fn encodes_continuations() {
262        // All-default mode lists have no packet.
263        assert!(Modes::new([mode!(0), mode!(0)]).is_none());
264
265        // A trailing default is absent, preserving the shorter encoding.
266        assert_encoding([1, 0], &[0x01]);
267
268        // Defaults before or between later values remain positionally encoded.
269        assert_encoding([0, 1], &[0x80, 0x01]);
270        assert_encoding([1, 0, 1], &[0x81, 0x80, 0x01]);
271
272        // The largest mode value remains valid while continuation uses its high bit.
273        assert_encoding([0x7f, 0x7f], &[0xff, 0x7f]);
274    }
275
276    #[test]
277    fn macro_converts_heterogeneous_values() {
278        struct Enabled;
279
280        impl From<Enabled> for Mode {
281            fn from(_: Enabled) -> Self {
282                mode!(1)
283            }
284        }
285
286        let modes = modes![Enabled, mode!(0), Enabled].unwrap();
287        assert_eq!(modes.encode().as_ref(), &[0x81, 0x80, 0x01]);
288    }
289
290    #[test]
291    fn mode_enforces_seven_bit_values() {
292        for value in [0, 0x7f] {
293            let mode = Mode::new(value).unwrap();
294            assert_eq!(u8::from(mode), value);
295            assert_eq!(Mode::try_from(value), Ok(mode));
296        }
297
298        for value in [0x80, 0xff] {
299            assert_eq!(Mode::new(value), None);
300            assert_eq!(Mode::try_from(value), Err(InvalidMode));
301        }
302    }
303
304    #[test]
305    fn mode_macro_constructs_literals_and_expressions() {
306        const MAX: Mode = mode!(0x7f);
307        let value = 1u8;
308
309        assert_eq!(u8::from(MAX), 0x7f);
310        assert_eq!(mode!(value), mode!(1));
311    }
312
313    #[test]
314    #[should_panic(expected = "mode value must fit in seven bits")]
315    fn mode_macro_rejects_invalid_expressions() {
316        let value = 0x80u8;
317        let _ = mode!(value);
318    }
319
320    #[test]
321    fn rejects_truncated_and_oversized_packets() {
322        assert!(matches!(
323            Modes::<2>::decode(&[][..]),
324            Err(Error::EndOfBuffer)
325        ));
326        assert!(matches!(
327            Modes::<2>::decode(&[0x80][..]),
328            Err(Error::EndOfBuffer)
329        ));
330        assert!(matches!(
331            Modes::<1>::decode(&[0x80][..]),
332            Err(Error::Invalid("Modes", _))
333        ));
334        assert!(matches!(
335            Modes::<2>::decode(&[0x80, 0x80][..]),
336            Err(Error::Invalid("Modes", _))
337        ));
338        assert!(matches!(
339            Modes::<2>::decode(&[0x80, 0x80, 0x01][..]),
340            Err(Error::Invalid("Modes", _))
341        ));
342    }
343
344    #[test]
345    fn rejects_non_canonical_packets() {
346        assert!(matches!(
347            Modes::<1>::decode(&[0x00][..]),
348            Err(Error::Invalid("Modes", _))
349        ));
350        assert!(matches!(
351            Modes::<2>::decode(&[0x80, 0x00][..]),
352            Err(Error::Invalid("Modes", _))
353        ));
354        assert!(matches!(
355            Modes::<2>::decode(&[0x81, 0x00][..]),
356            Err(Error::Invalid("Modes", _))
357        ));
358    }
359
360    #[test]
361    fn read_stops_at_packet_boundary() {
362        let mut encoded = &[0x01, 0x02][..];
363        let modes = Modes::<2>::read(&mut encoded).unwrap();
364        assert_eq!(modes.encode().as_ref(), &[0x01]);
365        assert_eq!(encoded, &[0x02]);
366        assert!(matches!(
367            Modes::<2>::decode(&[0x01, 0x02][..]),
368            Err(Error::ExtraData(1))
369        ));
370    }
371
372    #[cfg(feature = "arbitrary")]
373    mod conformance {
374        use super::*;
375        use crate::conformance::CodecConformance;
376
377        commonware_conformance::conformance_tests! {
378            CodecConformance<Modes<1>>,
379            CodecConformance<Modes<2>>,
380        }
381    }
382}