blob: 71b7a4a89f12d5fb910348fe3a665e54ee5b064a [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Generic utilities for encoding/decoding packets.
/// Generates an enum value where each variant can be converted into a constant in the given
/// raw_type.
///
/// For example:
/// decodable_enum! {
/// pub(crate) enum Color<u8, MyError, MyError::Variant> {
/// Red = 1,
/// Blue = 2,
/// Green = 3,
/// }
/// }
///
/// Color::try_from(2) -> Color::Red
/// u8::from(&Color::Red) -> 1.
#[macro_export]
macro_rules! decodable_enum {
($(#[$meta:meta])* $visibility:vis enum $name:ident<
$raw_type:ty,
$error_type:ident,
$error_path:ident
> {
$($(#[$variant_meta:meta])* $variant:ident = $val:expr),*,
}) => {
$(#[$meta])*
#[derive(
::core::clone::Clone,
::core::marker::Copy,
::core::fmt::Debug,
::core::cmp::Eq,
::core::hash::Hash,
::core::cmp::PartialEq)]
$visibility enum $name {
$($(#[$variant_meta])* $variant = $val),*
}
impl $name {
pub const VALUES : &'static [$raw_type] = &[$($val),*,];
pub const VARIANTS : &'static [$name] = &[$($name::$variant),*,];
pub fn name(&self) -> &'static ::core::primitive::str {
match self {
$($name::$variant => ::core::stringify!($variant)),*
}
}
}
impl ::core::convert::From<&$name> for $raw_type {
fn from(v: &$name) -> $raw_type {
match v {
$($name::$variant => $val),*,
}
}
}
impl ::core::convert::TryFrom<$raw_type> for $name {
type Error = $error_type;
fn try_from(value: $raw_type) -> ::core::result::Result<Self, $error_type> {
match value {
$($val => ::core::result::Result::Ok($name::$variant)),*,
_ => ::core::result::Result::Err($error_type::$error_path),
}
}
}
}
}
/// A decodable type can be created from a byte buffer.
/// The type returned is separate (copied) from the buffer once decoded.
pub trait Decodable: ::core::marker::Sized {
type Error;
/// Decodes into a new object, or returns an error.
fn decode(buf: &[u8]) -> ::core::result::Result<Self, Self::Error>;
}
/// An encodable type can write itself into a byte buffer.
pub trait Encodable: ::core::marker::Sized {
type Error;
/// Returns the number of bytes necessary to encode |self|.
fn encoded_len(&self) -> ::core::primitive::usize;
/// Writes the encoded version of |self| at the start of |buf|.
/// |buf| must be at least |self.encoded_len()| length.
fn encode(&self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error>;
}
#[macro_export]
macro_rules! codable_as_bitmask {
($type:ty, $raw_type:ty, $error_type:ident, $error_path:ident) => {
impl $type {
pub fn from_bits(
v: $raw_type,
) -> impl ::std::iter::Iterator<Item = ::core::result::Result<$type, $error_type>> {
(0..<$raw_type>::BITS).map(|bit| 1 << bit).filter(move |val| (v & val) != 0).map(
|val| {
::std::convert::TryInto::<$type>::try_into(val)
.map_err(|_| $error_type::$error_path)
},
)
}
pub fn to_bits<'a>(
mut it: impl ::std::iter::Iterator<Item = &'a $type>,
) -> ::core::result::Result<$raw_type, $error_type> {
it.try_fold(0, |acc, item| {
let v = ::std::convert::Into::<$raw_type>::into(item);
if v == 0 {
return ::core::result::Result::Err($error_type::$error_path);
}
if (v as f64).log2().ceil() != (v as f64).log2().floor() {
return ::core::result::Result::Err($error_type::$error_path);
}
::core::result::Result::Ok(acc | v)
})
}
}
};
}
#[cfg(test)]
#[no_implicit_prelude]
mod test {
use ::assert_matches::assert_matches;
use ::core::result::Result;
use ::core::{
assert, assert_eq,
convert::{From, TryFrom},
option::Option::Some,
panic,
};
use ::std::collections::HashSet;
use ::std::iter::{IntoIterator, Iterator};
use ::std::vec;
#[derive(Debug, PartialEq)]
pub(crate) enum TestError {
OutOfRange,
}
decodable_enum! {
pub(crate) enum TestEnum<u16, TestError, OutOfRange> {
One = 0x0001,
Two = 0x0002,
Max = 0xFFFF,
}
}
codable_as_bitmask!(TestEnum, u16, TestError, OutOfRange);
decodable_enum! {
pub(crate) enum TestEnum2<u16, TestError, OutOfRange> {
One = 0x0001, // bit 0
Two = 0x0002, // bit 1
Big = 0x4000, // bit 14
}
}
codable_as_bitmask!(TestEnum2, u16, TestError, OutOfRange);
#[test]
fn try_from_success() {
let one = TestEnum::try_from(1);
assert!(one.is_ok());
assert_eq!(TestEnum::One, one.unwrap());
let two = TestEnum::try_from(2);
assert!(two.is_ok());
assert_eq!(TestEnum::Two, two.unwrap());
let max = TestEnum::try_from(65535);
assert!(max.is_ok());
assert_eq!(TestEnum::Max, max.unwrap());
}
#[test]
fn try_from_error() {
let err = TestEnum::try_from(5);
assert_matches!(err.err(), Some(TestError::OutOfRange));
}
#[test]
fn into_rawtype() {
let raw = u16::from(&TestEnum::One);
assert_eq!(1, raw);
let raw = u16::from(&TestEnum::Two);
assert_eq!(2, raw);
let raw = u16::from(&TestEnum::Max);
assert_eq!(65535, raw);
}
#[test]
fn test_values() {
let v = TestEnum::VALUES.to_vec();
assert_eq!(3, v.len());
assert_eq!(1, v[0]);
assert_eq!(2, v[1]);
assert_eq!(65535, v[2]);
let v = TestEnum2::VALUES.to_vec();
assert_eq!(v, vec![1, 2, 16384]);
}
#[test]
fn test_variants() {
let v = TestEnum::VARIANTS.to_vec();
assert_eq!(3, v.len());
assert_eq!(TestEnum::One, v[0]);
assert_eq!(TestEnum::Two, v[1]);
assert_eq!(TestEnum::Max, v[2]);
}
#[test]
fn test_name() {
assert_eq!("One", TestEnum::One.name());
assert_eq!("Two", TestEnum::Two.name());
assert_eq!("Max", TestEnum::Max.name());
assert_eq!("Big", TestEnum2::Big.name());
}
#[test]
fn as_bitmask() {
let one_and_big = 0x4001;
let enums: HashSet<TestEnum2> = TestEnum2::from_bits(one_and_big)
.collect::<Result<HashSet<_>, _>>()
.expect("should not fail");
assert_eq!(2, enums.len());
let expected_enums = [TestEnum2::One, TestEnum2::Big].into_iter().collect();
assert_eq!(enums, expected_enums);
let all = TestEnum2::VARIANTS;
let value = TestEnum2::to_bits(all.iter()).expect("should work");
assert_eq!(0x4003, value);
}
#[test]
fn bitmask_errors() {
// Max value has both bit one and two set which are valid TestEnum variants.
// The rest of the set bits are not valid TestEnum variants.
let max = 65535;
// Collecting as result shows failure.
let res: Result<HashSet<TestEnum>, _> = TestEnum::from_bits(max).collect();
let _ = res.expect_err("should have failed");
// Collecting as vector of results show only 2 "bit" values were valid enums.
let res: vec::Vec<Result<TestEnum, TestError>> = TestEnum::from_bits(max).collect();
let valid: vec::Vec<TestEnum> = res.into_iter().filter_map(|v| v.ok()).collect();
assert_eq!(valid.len(), 2);
// Fails because TestEnum::Max variant is not a bitwise flag value.
let all = TestEnum::VARIANTS;
let _ = TestEnum::to_bits(all.iter()).expect_err("should fail");
}
}