use crate::{EncodeSize, Error, Read, ReadExt, Write};
use bytes::{Buf, BufMut};
const CONTINUATION_BIT: u8 = 1 << 7;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[error("mode value must fit in seven bits")]
pub struct InvalidMode;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Mode(u8);
impl Mode {
pub const fn new(value: u8) -> Option<Self> {
if value < CONTINUATION_BIT {
Some(Self(value))
} else {
None
}
}
}
impl TryFrom<u8> for Mode {
type Error = InvalidMode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::new(value).ok_or(InvalidMode)
}
}
impl From<Mode> for u8 {
fn from(mode: Mode) -> Self {
mode.0
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Mode {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self(u.int_in_range(0..=(CONTINUATION_BIT - 1))?))
}
}
#[cfg(not(any(
commonware_stability_GAMMA,
commonware_stability_DELTA,
commonware_stability_EPSILON,
commonware_stability_RESERVED
)))] #[macro_export]
macro_rules! mode {
($value:literal) => {
const { $crate::Mode::new($value).expect("mode value must fit in seven bits") }
};
($value:expr) => {
$crate::Mode::new($value).expect("mode value must fit in seven bits")
};
}
#[cfg(not(any(
commonware_stability_GAMMA,
commonware_stability_DELTA,
commonware_stability_EPSILON,
commonware_stability_RESERVED
)))] #[macro_export]
macro_rules! modes {
($($mode:expr),* $(,)?) => {
$crate::Modes::new([
$(::core::convert::Into::<$crate::Mode>::into($mode)),*
])
};
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Modes<const N: usize> {
encoded: [u8; N],
len: usize,
}
impl<const N: usize> Modes<N> {
pub fn new(modes: [Mode; N]) -> Option<Self> {
const {
assert!(N > 0, "N must be greater than 0");
}
let mut encoded = modes.map(u8::from);
let last = encoded.iter().rposition(|&mode| mode != 0)?;
for mode in &mut encoded[..last] {
*mode |= CONTINUATION_BIT;
}
Some(Self {
encoded,
len: last + 1,
})
}
}
impl<const N: usize> Write for Modes<N> {
fn write(&self, buf: &mut impl BufMut) {
buf.put_slice(&self.encoded[..self.len]);
}
}
impl<const N: usize> EncodeSize for Modes<N> {
fn encode_size(&self) -> usize {
self.len
}
}
impl<const N: usize> Read for Modes<N> {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
const {
assert!(N > 0, "N must be greater than 0");
}
let mut encoded = [0; N];
for index in 0..N {
let byte = u8::read(buf)?;
encoded[index] = byte;
if byte & CONTINUATION_BIT == 0 {
if byte == 0 {
return Err(Error::Invalid("Modes", "trailing mode must be non-zero"));
}
return Ok(Self {
encoded,
len: index + 1,
});
}
}
Err(Error::Invalid("Modes", "too many mode values"))
}
}
#[cfg(feature = "arbitrary")]
impl<'a, const N: usize> arbitrary::Arbitrary<'a> for Modes<N> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
const {
assert!(N > 0, "N must be greater than 0");
}
let len = u.int_in_range(1..=N)?;
let mut modes = [Mode(0); N];
for mode in &mut modes[..len - 1] {
*mode = u.arbitrary()?;
}
modes[len - 1] = Mode(u.int_in_range(1..=(CONTINUATION_BIT - 1))?);
Self::new(modes).ok_or(arbitrary::Error::IncorrectFormat)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DecodeExt, Encode};
fn assert_encoding<const N: usize>(modes: [u8; N], expected: &[u8]) {
let modes = Modes::new(modes.map(|value| mode!(value))).unwrap();
assert_eq!(modes.encode_size(), expected.len());
let encoded = modes.encode();
assert_eq!(encoded.as_ref(), expected);
assert_eq!(Modes::<N>::decode(encoded).unwrap(), modes);
}
#[test]
fn encodes_continuations() {
assert!(Modes::new([mode!(0), mode!(0)]).is_none());
assert_encoding([1, 0], &[0x01]);
assert_encoding([0, 1], &[0x80, 0x01]);
assert_encoding([1, 0, 1], &[0x81, 0x80, 0x01]);
assert_encoding([0x7f, 0x7f], &[0xff, 0x7f]);
}
#[test]
fn macro_converts_heterogeneous_values() {
struct Enabled;
impl From<Enabled> for Mode {
fn from(_: Enabled) -> Self {
mode!(1)
}
}
let modes = modes![Enabled, mode!(0), Enabled].unwrap();
assert_eq!(modes.encode().as_ref(), &[0x81, 0x80, 0x01]);
}
#[test]
fn mode_enforces_seven_bit_values() {
for value in [0, 0x7f] {
let mode = Mode::new(value).unwrap();
assert_eq!(u8::from(mode), value);
assert_eq!(Mode::try_from(value), Ok(mode));
}
for value in [0x80, 0xff] {
assert_eq!(Mode::new(value), None);
assert_eq!(Mode::try_from(value), Err(InvalidMode));
}
}
#[test]
fn mode_macro_constructs_literals_and_expressions() {
const MAX: Mode = mode!(0x7f);
let value = 1u8;
assert_eq!(u8::from(MAX), 0x7f);
assert_eq!(mode!(value), mode!(1));
}
#[test]
#[should_panic(expected = "mode value must fit in seven bits")]
fn mode_macro_rejects_invalid_expressions() {
let value = 0x80u8;
let _ = mode!(value);
}
#[test]
fn rejects_truncated_and_oversized_packets() {
assert!(matches!(
Modes::<2>::decode(&[][..]),
Err(Error::EndOfBuffer)
));
assert!(matches!(
Modes::<2>::decode(&[0x80][..]),
Err(Error::EndOfBuffer)
));
assert!(matches!(
Modes::<1>::decode(&[0x80][..]),
Err(Error::Invalid("Modes", _))
));
assert!(matches!(
Modes::<2>::decode(&[0x80, 0x80][..]),
Err(Error::Invalid("Modes", _))
));
assert!(matches!(
Modes::<2>::decode(&[0x80, 0x80, 0x01][..]),
Err(Error::Invalid("Modes", _))
));
}
#[test]
fn rejects_non_canonical_packets() {
assert!(matches!(
Modes::<1>::decode(&[0x00][..]),
Err(Error::Invalid("Modes", _))
));
assert!(matches!(
Modes::<2>::decode(&[0x80, 0x00][..]),
Err(Error::Invalid("Modes", _))
));
assert!(matches!(
Modes::<2>::decode(&[0x81, 0x00][..]),
Err(Error::Invalid("Modes", _))
));
}
#[test]
fn read_stops_at_packet_boundary() {
let mut encoded = &[0x01, 0x02][..];
let modes = Modes::<2>::read(&mut encoded).unwrap();
assert_eq!(modes.encode().as_ref(), &[0x01]);
assert_eq!(encoded, &[0x02]);
assert!(matches!(
Modes::<2>::decode(&[0x01, 0x02][..]),
Err(Error::ExtraData(1))
));
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use crate::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Modes<1>>,
CodecConformance<Modes<2>>,
}
}
}