Skip to content

Commit

Permalink
feat(s2n-codec): add bytes methods for unaligned types
Browse files Browse the repository at this point in the history
  • Loading branch information
camshaft committed Feb 20, 2024
1 parent 14023eb commit 8a3ed74
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ jobs:
strategy:
fail-fast: false
matrix:
crate: [quic/s2n-quic-core, quic/s2n-quic-platform]
crate: [common/s2n-codec, quic/s2n-quic-core, quic/s2n-quic-platform]
steps:
- uses: actions/checkout@v4
with:
Expand Down
8 changes: 8 additions & 0 deletions common/s2n-codec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ bolero-generator = { version = "0.10", default-features = false, optional = true
byteorder = { version = "1.1", default-features = false }
bytes = { version = "1", default-features = false, optional = true }
zerocopy = { version = "0.7", features = ["derive"] }

[dev-dependencies]
bolero = "0.10"
bolero-generator = "0.10"

[package.metadata.kani]
flags = { tests = true }
unstable = { stubbing = true }
17 changes: 17 additions & 0 deletions common/s2n-codec/src/decoder/checked_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ impl CheckedRange {
&slice[self.start..self.end]
}

#[cfg(feature = "checked_range_unsafe")]
#[inline]
pub fn get_mut<'a>(&self, slice: &'a mut [u8]) -> &'a mut [u8] {
unsafe {
#[cfg(debug_assertions)]
debug_assert_eq!(slice.as_ptr().add(self.start), self.original_ptr);

slice.get_unchecked_mut(self.start..self.end)
}
}

#[cfg(not(feature = "checked_range_unsafe"))]
#[inline]
pub fn get_mut<'a>(&self, slice: &'a mut [u8]) -> &'a mut [u8] {
&mut slice[self.start..self.end]
}

#[inline]
pub fn len(&self) -> usize {
self.end - self.start
Expand Down
2 changes: 1 addition & 1 deletion common/s2n-codec/src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ pub use buffer_mut::*;
pub use checked_range::*;
pub use value::*;

#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub enum DecoderError {
UnexpectedEof(usize),
UnexpectedBytes(usize),
Expand Down
2 changes: 1 addition & 1 deletion common/s2n-codec/src/decoder/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ macro_rules! decoder_value_unaligned_integer {
let (value, buffer) = buffer.decode_slice($bitsize / 8)?;
let value = value.as_less_safe_slice();
let value = NetworkEndian::$call(value);
Ok(($ty(value), buffer))
Ok(($ty::new_truncated(value), buffer))
}
}
);
Expand Down
18 changes: 18 additions & 0 deletions common/s2n-codec/src/encoder/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,24 @@ impl EncoderValue for () {
}
}

impl<A: EncoderValue, B: EncoderValue> EncoderValue for (A, B) {
#[inline]
fn encode<E: Encoder>(&self, encoder: &mut E) {
self.0.encode(encoder);
self.1.encode(encoder);
}

#[inline]
fn encoding_size(&self) -> usize {
self.0.encoding_size() + self.1.encoding_size()
}

#[inline]
fn encoding_size_for_encoder<E: Encoder>(&self, encoder: &E) -> usize {
self.0.encoding_size_for_encoder(encoder) + self.1.encoding_size_for_encoder(encoder)
}
}

impl<T: EncoderValue> EncoderValue for Option<T> {
#[inline]
fn encode<E: Encoder>(&self, buffer: &mut E) {
Expand Down
58 changes: 54 additions & 4 deletions common/s2n-codec/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,66 @@ pub fn ensure_decoding_mut_matches<'a, T: DecoderValueMut<'a> + PartialEq + core

#[cfg(test)]
mod tests {
use crate::{i24, i48, u24, u48};
use bolero::check;

#[test]
fn test_u8_round_trip_value() {
for i in 0..core::u8::MAX {
fn u8_round_trip_value_test() {
for i in 0..=core::u8::MAX {
ensure_codec_round_trip_value!(u8, i).unwrap();
}
}

#[test]
fn test_u8_round_trip_bytes() {
let bytes = (0..core::u8::MAX).collect::<Vec<_>>();
fn u8_round_trip_bytes_test() {
let bytes = (0..=core::u8::MAX).collect::<Vec<_>>();
ensure_codec_round_trip_bytes!(u8, &bytes).unwrap();
}

#[test]
fn u16_round_trip_value_test() {
for i in 0..=u16::MAX {
ensure_codec_round_trip_value!(u16, i).unwrap();
}
}

#[test]
fn u16_round_trip_bytes_test() {
let mut bytes = vec![];
for v in 0..=u16::MAX {
bytes.extend_from_slice(&v.to_be_bytes());
}
ensure_codec_round_trip_bytes!(u16, &bytes).unwrap();
}

macro_rules! fuzz_tests {
($ty:ident, $value_test:ident, $bytes_test:ident) => {
#[test]
#[cfg_attr(kani, kani::proof, kani::solver(cadical))]
fn $value_test() {
check!().with_type().cloned().for_each(|v| {
ensure_codec_round_trip_value!($ty, v).unwrap();
let bytes = v.to_be_bytes();
let actual = $ty::from_be_bytes(bytes);
assert_eq!(v, actual);
});
}

#[test]
fn $bytes_test() {
check!().for_each(|bytes| {
let _ = ensure_codec_round_trip_bytes!($ty, &bytes);
});
}
};
}

fuzz_tests!(u24, u24_round_trip_value_test, u24_round_trip_bytes_test);
fuzz_tests!(i24, i24_round_trip_value_test, i24_round_trip_bytes_test);
fuzz_tests!(u32, u32_round_trip_value_test, u32_round_trip_bytes_test);
fuzz_tests!(i32, i32_round_trip_value_test, i32_round_trip_bytes_test);
fuzz_tests!(u48, u48_round_trip_value_test, u48_round_trip_bytes_test);
fuzz_tests!(i48, i48_round_trip_value_test, i48_round_trip_bytes_test);
fuzz_tests!(u64, u64_round_trip_value_test, u64_round_trip_bytes_test);
fuzz_tests!(i64, i64_round_trip_value_test, i64_round_trip_bytes_test);
}
111 changes: 104 additions & 7 deletions common/s2n-codec/src/unaligned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,34 @@ use core::{
//
// 48-bit integers are also implemented for completeness.
macro_rules! unaligned_integer_type {
($name:ident, $bitsize:expr, $storage_type:ty, [$($additional_conversions:ty),*]) => {
($name:ident, $bitsize:expr, $storage_type:ty, $min:expr, $max:expr, [$($additional_conversions:ty),*]) => {
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
pub struct $name(pub(crate) $storage_type);
pub struct $name($storage_type);

impl $name {
pub const ZERO: Self = Self(0);
pub const MIN: Self = Self($min);
pub const MAX: Self = Self($max);

/// Truncate the storage value into the allowed range
#[inline]
pub fn new_truncated(value: $storage_type) -> Self {
Self(value & ((1 << $bitsize) - 1))
}

#[inline]
pub fn from_be_bytes(bytes: [u8; ($bitsize / 8)]) -> Self {
Self(UnalignedBytes::be_bytes_to_storage(bytes) as _)
}

#[inline]
pub fn to_be_bytes(self) -> [u8; ($bitsize / 8)] {
UnalignedBytes::storage_to_be_bytes(self.0 as _)
}
}

#[cfg(feature = "generator")]
#[cfg(any(test, feature = "generator"))]
impl bolero_generator::TypeGenerator for $name {
fn generate<D: bolero_generator::Driver>(driver: &mut D) -> Option<Self> {
Some(Self::new_truncated(driver.gen()?))
Expand All @@ -38,6 +53,7 @@ macro_rules! unaligned_integer_type {
impl TryFrom<$storage_type> for $name {
type Error = TryFromIntError;

#[inline]
fn try_from(value: $storage_type) -> Result<Self, Self::Error> {
if value < (1 << $bitsize) {
Ok(Self(value))
Expand All @@ -48,19 +64,22 @@ macro_rules! unaligned_integer_type {
}

impl From<$name> for $storage_type {
#[inline]
fn from(value: $name) -> $storage_type {
value.0
}
}

$(
impl From<$additional_conversions> for $name {
#[inline]
fn from(value: $additional_conversions) -> Self {
$name(value.into())
}
}

impl From<$name> for $additional_conversions {
#[inline]
fn from(value: $name) -> Self {
value.0 as $additional_conversions
}
Expand All @@ -70,38 +89,116 @@ macro_rules! unaligned_integer_type {
impl Deref for $name {
type Target = $storage_type;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
};
}

unaligned_integer_type!(u24, 24, u32, [u8, u16]);
unaligned_integer_type!(i24, 24, i32, [u8, i8, u16, i16]);
/// A trait defining how to convert between storage types and unaligned bytes
trait UnalignedBytes: Sized {
type Storage;

fn storage_to_be_bytes(storage: Self::Storage) -> Self;
fn be_bytes_to_storage(self) -> Self::Storage;
}

impl UnalignedBytes for [u8; 3] {
type Storage = u32;

#[inline]
fn storage_to_be_bytes(storage: Self::Storage) -> Self {
let [_, a, b, c] = storage.to_be_bytes();
[a, b, c]
}

#[inline]
fn be_bytes_to_storage(self) -> Self::Storage {
let [a, b, c] = self;
let bytes = [0, a, b, c];
Self::Storage::from_be_bytes(bytes)
}
}

impl UnalignedBytes for [u8; 6] {
type Storage = u64;

#[inline]
fn storage_to_be_bytes(storage: Self::Storage) -> Self {
let [_, _, a, b, c, d, e, f] = storage.to_be_bytes();
[a, b, c, d, e, f]
}

#[inline]
fn be_bytes_to_storage(self) -> Self::Storage {
let [a, b, c, d, e, f] = self;
let bytes = [0, 0, a, b, c, d, e, f];
Self::Storage::from_be_bytes(bytes)
}
}

macro_rules! signed_min {
($bitsize:expr) => {
-(1 << ($bitsize - 1))
};
}

macro_rules! signed_max {
($bitsize:expr) => {
((1 << ($bitsize - 1)) - 1)
};
}

#[test]
fn signed_min_max_test() {
assert_eq!(i8::MIN as i16, signed_min!(8));
assert_eq!(i8::MAX as i16, signed_max!(8));
}

unaligned_integer_type!(u24, 24, u32, 0, (1 << 24) - 1, [u8, u16]);
unaligned_integer_type!(
i24,
24,
i32,
signed_min!(24),
signed_max!(24),
[u8, i8, u16, i16]
);

impl TryFrom<u64> for u24 {
type Error = TryFromIntError;

#[inline]
fn try_from(value: u64) -> Result<Self, Self::Error> {
let storage_value: u32 = value.try_into()?;
storage_value.try_into()
}
}

impl From<u24> for u64 {
#[inline]
fn from(value: u24) -> u64 {
value.0.into()
}
}

unaligned_integer_type!(u48, 24, u64, [u8, u16, u32]);
unaligned_integer_type!(i48, 24, i64, [u8, i8, u16, i16, u32, i32]);
unaligned_integer_type!(u48, 48, u64, 0, (1 << 48) - 1, [u8, u16, u32]);
unaligned_integer_type!(
i48,
48,
i64,
signed_min!(48),
signed_max!(48),
[u8, i8, u16, i16, u32, i32]
);

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct TryFromIntError(());

impl From<core::num::TryFromIntError> for TryFromIntError {
#[inline]
fn from(_: core::num::TryFromIntError) -> Self {
Self(())
}
Expand Down

0 comments on commit 8a3ed74

Please sign in to comment.