Skip to content

Commit

Permalink
Add tests and example for byte ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Oct 26, 2023
1 parent bf95e8d commit d7156cd
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 84 deletions.
4 changes: 2 additions & 2 deletions crates/musli-zerocopy/src/buf/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ where
}
}

impl<O: Size> Load for Ref<str, O> {
impl<O: Size, E: ByteOrder> Load for Ref<str, O, E> {
type Target = str;

#[inline]
Expand Down Expand Up @@ -88,7 +88,7 @@ where
}
}

impl<O: Size> LoadMut for Ref<str, O> {
impl<O: Size, E: ByteOrder> LoadMut for Ref<str, O, E> {
#[inline]
fn load_mut<'buf>(&self, buf: &'buf mut Buf) -> Result<&'buf mut Self::Target, Error> {
buf.load_unsized_mut(*self)
Expand Down
3 changes: 1 addition & 2 deletions crates/musli-zerocopy/src/buf/owned_buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,7 @@ impl<O: Size, E: ByteOrder> OwnedBuf<O, E> {
/// # Examples
///
/// ```
/// use musli_zerocopy::OwnedBuf;
/// use musli_zerocopy::endian::LittleEndian;
/// use musli_zerocopy::{LittleEndian, OwnedBuf};
///
/// let mut buf = OwnedBuf::with_capacity(1024)
/// .with_byte_order::<LittleEndian>();
Expand Down
2 changes: 1 addition & 1 deletion crates/musli-zerocopy/src/endian.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Marker types which define a [`ByteOrder`] to use.

/// Default [`Endianness`].
/// Default [`ByteOrder`].
pub type DefaultEndian = NativeEndian;

/// Alias for the native endian [`ByteOrder`].
Expand Down
121 changes: 111 additions & 10 deletions crates/musli-zerocopy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@
//! accessing. So for random access we only need to validate the parts that
//! are being accessed.
//!
//! Overview:
//! * [Why should I consider `musli-zerocopy` over X?](#why-should-i-consider-musli-zerocopy-over-x)
//! * [Guide](#guide)
//! * [Reading data](#reading-data)
//! * [Writing data at offset zero](#writing-data-at-offset-zero)
//! * [Portability](#portability)
//! * [Limits](#limits)
//!
//! <br>
//!
//! ## Why should I consider `musli-zerocopy` over X?
Expand Down Expand Up @@ -306,6 +314,95 @@
//!
//! <br>
//!
//! ## Portability
//!
//! By default archives will use the native [`ByteOrder`]. In order to construct
//! and load a portable archive, the byte order in use has to be explicitly
//! specified.
//!
//! This is done by specifying the byte order in use during buffer construction
//! and expliclty setting the `E` parameter in types which received it such as
//! [`Ref<T, O, E>`].
//!
//! We can start of by defining a fully `Portable` archive structure, which
//! received both size and [`ByteOrder`]. Note that it could also just
//! explicitly specify a desired byte order but doing it like this makes it
//! maximally flexible as an example:
//!
//! ```
//! use musli_zerocopy::{Size, ByteOrder, Ref, Ordered, ZeroCopy};
//!
//! #[derive(ZeroCopy)]
//! #[repr(C)]
//! struct Archive<O, E> where O: Size, E: ByteOrder {
//! string: Ref<str, O, E>,
//! number: Ordered<u32, E>,
//! }
//! ```
//!
//! Building a buffer out of the structure is fairly straight forward,
//! [`OwnedBuf`] has the [`with_byte_order::<E>()`] method which allows us to
//! specify a "sticky" [`ByteOrder`] to use in types which interact with it
//! during construction:
//!
//! ```
//! use musli_zerocopy::{BigEndian, LittleEndian, Ordered, OwnedBuf};
//! # use musli_zerocopy::{Size, ByteOrder, Ref, ZeroCopy};
//! # #[derive(ZeroCopy)]
//! # #[repr(C)]
//! # struct Archive<O, E> where O: Size, E: ByteOrder {
//! # string: Ref<str, O, E>,
//! # number: Ordered<u32, E>
//! # }
//!
//! let mut buf = OwnedBuf::new().with_byte_order::<LittleEndian>();
//!
//! let first = buf.store(&Ordered::le(42u16));
//! let portable = Archive {
//! string: buf.store_unsized("Hello World!"),
//! number: Ordered::new(10),
//! };
//! let portable = buf.store(&portable);
//!
//! assert_eq!(&buf[..], &[
//! 42, 0, // 42u16
//! 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, // "Hello World!"
//! 0, 0, // padding
//! 2, 0, 0, 0, 12, 0, 0, 0, // Ref<str>
//! 10, 0, 0, 0 // 10u32
//! ]);
//!
//! let portable = buf.load(portable)?;
//! # assert_eq!(buf.load(first)?.to_ne(), 42);
//! # assert_eq!(buf.load(portable.string)?, "Hello World!");
//! # assert_eq!(portable.number.to_ne(), 10);
//!
//! let mut buf = OwnedBuf::new().with_byte_order::<BigEndian>();
//!
//! let first = buf.store(&Ordered::be(42u16));
//! let portable = Archive {
//! string: buf.store_unsized("Hello World!"),
//! number: Ordered::new(10),
//! };
//! let portable = buf.store(&portable);
//!
//! assert_eq!(&buf[..], &[
//! 0, 42, // 42u16
//! 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, // "Hello World!"
//! 0, 0, // padding
//! 0, 0, 0, 2, 0, 0, 0, 12, // Ref<str>
//! 0, 0, 0, 10 // 10u32
//! ]);
//!
//! let portable = buf.load(portable)?;
//! # assert_eq!(buf.load(first)?.to_ne(), 42);
//! # assert_eq!(buf.load(portable.string)?, "Hello World!");
//! # assert_eq!(portable.number.to_ne(), 10);
//! # Ok::<_, musli_zerocopy::Error>(())
//! ```
//!
//! <br>
//!
//! ## Limits
//!
//! Offset, the size of unsized values, and slice lengths are all limited to
Expand Down Expand Up @@ -398,20 +495,23 @@
//!
//! <br>
//!
//! [`swiss`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/swiss/index.html
//! [`phf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/phf/index.html
//! [`phf` crate]: https://docs.rs/phf
//! [`aligned_buf(bytes, align)`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html
//! [`ByteOrder`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/trait.ByteOrder.html
//! [`hashbrown` crate]: https://docs.rs/phf
//! [`OwnedBuf::with_size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_size
//! [`OwnedBuf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html
//! [`with_byte_order::<E>()`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_byte_order
//! [`phf` crate]: https://docs.rs/phf
//! [`phf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/phf/index.html
//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [`Ref<str>`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [`Ref<T, O, E>`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [`requested()`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/struct.OwnedBuf.html#method.requested
//! [`Size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html
//! [`swiss`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/swiss/index.html
//! [`ZeroCopy`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/trait.ZeroCopy.html
//! [derive]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/derive.ZeroCopy.html
//! [`Ref`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [ref-u32]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [`Ref<str>`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/struct.Ref.html
//! [`OwnedBuf`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html
//! [`OwnedBuf::with_size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/buf/struct.OwnedBuf.html#method.with_size
//! [`Size`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html
//! [`aligned_buf(bytes, align)`]: https://docs.rs/musli-zerocopy/latest/musli_zerocopy/pointer/trait.Size.html

#![no_std]
#![allow(clippy::module_inception)]
Expand Down Expand Up @@ -451,12 +551,13 @@ pub mod phf;
pub mod swiss;

#[doc(inline)]
pub use self::pointer::Ref;
pub use self::pointer::{Ref, Size};
pub mod pointer;

mod ordered;
pub use self::ordered::Ordered;

pub use self::endian::{BigEndian, ByteOrder, LittleEndian};
pub mod endian;

/// Derive macro to implement [`ZeroCopy`].
Expand Down
127 changes: 81 additions & 46 deletions crates/musli-zerocopy/src/ordered.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,59 @@
use core::marker::PhantomData;
use core::{any, fmt};

use crate::buf::{Padder, Validator};
use crate::endian::ByteOrder;
use crate::endian::{BigEndian, ByteOrder, LittleEndian};
use crate::ZeroCopy;

/// A value capable of enforcing a custom [`ByteOrder`].
///
/// This can be used to store values in a zero-copy container in a portable
/// manner, which is especially important to transfer types such as `char` which
/// have a limited supported bit-pattern.
///
/// # Examples
///
/// ```
/// use musli_zerocopy::{Ordered, ZeroCopy};
/// use musli_zerocopy::endian::{BigEndian, LittleEndian};
///
/// let mut a: Ordered<_, BigEndian> = Ordered::new('a' as u32);
/// let mut b: Ordered<_, LittleEndian> = Ordered::new('a' as u32);
///
/// assert_eq!(a.into_value(), 'a' as u32);
/// assert_eq!(a.to_bytes(), &[0, 0, 0, 97]);
///
/// assert_eq!(b.into_value(), 'a' as u32);
/// assert_eq!(b.to_bytes(), &[97, 0, 0, 0]);
/// ```
#[derive(ZeroCopy)]
#[zero_copy(crate, swap_bytes, bounds = {T: ZeroCopy})]
#[repr(transparent)]
pub struct Ordered<T, E: ByteOrder> {
value: T,
#[zero_copy(ignore)]
_marker: PhantomData<E>,
}

impl<T: ZeroCopy> Ordered<T, LittleEndian> {
/// Construct new value wrapper with [`LittleEndian`] [`ByteOrder`].
///
/// # Examples
///
/// ```
/// use musli_zerocopy::Ordered;
///
/// let value = Ordered::le(42u32);
/// assert_eq!(value.to_ne(), 42);
/// assert_eq!(value.to_raw(), 42u32.to_le());
/// ```
#[inline]
pub fn le(value: T) -> Self {
Self::new(value)
}
}

impl<T: ZeroCopy> Ordered<T, BigEndian> {
/// Construct new value wrapper with [`BigEndian`] [`ByteOrder`].
///
/// # Examples
///
/// ```
/// use musli_zerocopy::Ordered;
///
/// let value = Ordered::be(42u32);
/// assert_eq!(value.to_ne(), 42);
/// assert_eq!(value.to_raw(), 42u32.to_be());
/// ```
#[inline]
pub fn be(value: T) -> Self {
Self::new(value)
}
}

impl<T: ZeroCopy, E: ByteOrder> Ordered<T, E> {
/// Construct new value wrapper with the specified [`ByteOrder`].
///
Expand All @@ -41,11 +63,25 @@ impl<T: ZeroCopy, E: ByteOrder> Ordered<T, E> {
/// byte-ordered.
///
/// ```should_panic
/// use musli_zerocopy::Ordered;
/// use musli_zerocopy::endian::LittleEndian;
/// use musli_zerocopy::{LittleEndian, Ordered};
///
/// let _: Ordered<_, LittleEndian> = Ordered::new('a');
/// ```
///
/// # Examples
///
/// ```
/// use musli_zerocopy::{BigEndian, LittleEndian, Ordered, ZeroCopy};
///
/// let mut a: Ordered<_, BigEndian> = Ordered::new('a' as u32);
/// let mut b: Ordered<_, LittleEndian> = Ordered::new('a' as u32);
///
/// assert_eq!(a.to_ne(), 'a' as u32);
/// assert_eq!(a.to_bytes(), &[0, 0, 0, 97]);
///
/// assert_eq!(b.to_ne(), 'a' as u32);
/// assert_eq!(b.to_bytes(), &[97, 0, 0, 0]);
/// ```
#[inline]
pub fn new(value: T) -> Self {
assert!(
Expand All @@ -60,37 +96,36 @@ impl<T: ZeroCopy, E: ByteOrder> Ordered<T, E> {
}
}

/// Get interior value with the desired native alignment.
/// Get interior value in native [`ByteOrder`].
///
/// # Examples
///
/// ```
/// use musli_zerocopy::Ordered;
///
/// let value = Ordered::le(42u32);
/// assert_eq!(value.to_ne(), 42);
/// assert_eq!(value.to_raw(), 42u32.to_le());
/// ```
#[inline]
pub fn into_value(self) -> T {
pub fn to_ne(self) -> T {
T::swap_bytes::<E>(self.value)
}
}

unsafe impl<T, E> ZeroCopy for Ordered<T, E>
where
T: ZeroCopy,
E: ByteOrder,
{
const ANY_BITS: bool = T::ANY_BITS;
const PADDED: bool = T::PADDED;
const CAN_SWAP_BYTES: bool = true;

#[inline]
unsafe fn pad(padder: &mut Padder<'_, Self>) {
T::pad(padder.transparent())
}

#[inline]
unsafe fn validate(validator: &mut Validator<'_, Self>) -> Result<(), crate::Error> {
T::validate(validator.transparent())
}

/// Get the raw inner value.
///
/// # Examples
///
/// ```
/// use musli_zerocopy::Ordered;
///
/// let value = Ordered::le(42u32);
/// assert_eq!(value.to_ne(), 42);
/// assert_eq!(value.to_raw(), 42u32.to_le());
/// ```
#[inline]
fn swap_bytes<__E: ByteOrder>(self) -> Self {
// NB: This is a no-op, since the byte-ordering is recorded at the type
// level and doesn't change no matter what ordering is requested.
self
pub fn to_raw(self) -> T {
self.value
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/musli-zerocopy/src/phf/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ where
#[inline]
fn bind(self, buf: &Buf) -> Result<Self::Bound<'_>, Error> {
Ok(Map {
key: self.key.into_value(),
key: self.key.to_ne(),
entries: buf.load(self.entries)?,
displacements: buf.load(self.displacements)?,
buf,
Expand Down Expand Up @@ -337,7 +337,7 @@ where
return Ok(None);
}

let hashes = crate::phf::hashing::hash(buf, key, &self.key.into_value())?;
let hashes = crate::phf::hashing::hash(buf, key, &self.key.to_ne())?;

let displacements = |index| match self.displacements.get(index) {
Some(entry) => Ok(Some(buf.load(entry)?)),
Expand Down
4 changes: 2 additions & 2 deletions crates/musli-zerocopy/src/phf/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ where
#[inline]
fn bind(self, buf: &Buf) -> Result<Self::Bound<'_>, Error> {
Ok(Set {
key: self.key.into_value(),
key: self.key.to_ne(),
entries: buf.load(self.entries)?,
displacements: buf.load(self.displacements)?,
buf,
Expand Down Expand Up @@ -205,7 +205,7 @@ where
return Ok(false);
}

let hashes = crate::phf::hashing::hash(buf, key, &self.key.into_value())?;
let hashes = crate::phf::hashing::hash(buf, key, &self.key.to_ne())?;

let displacements = |index| match self.displacements.get(index) {
Some(entry) => Ok(Some(buf.load(entry)?)),
Expand Down
Loading

0 comments on commit d7156cd

Please sign in to comment.