diff --git a/Cargo.toml b/Cargo.toml index f3b5459873..6595c77b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,11 @@ pinned-nightly = "nightly-2023-10-31" features = ["__internal_use_only_features_that_work_on_stable"] [features] -default = ["byteorder"] - alloc = [] +# Do not enable this feature; it will cause compilation to fail. It exists to +# provide more helpful error messages for crates updating from a previous +# version which provided this feature. +byteorder = [] derive = ["zerocopy-derive"] simd = [] simd-nightly = ["simd"] @@ -50,11 +52,6 @@ __internal_use_only_features_that_work_on_stable = ["alloc", "derive", "simd"] [dependencies] zerocopy-derive = { version = "=0.7.21", path = "zerocopy-derive", optional = true } -[dependencies.byteorder] -version = "1.3" -default-features = false -optional = true - # The "associated proc macro pattern" ensures that the versions of zerocopy and # zerocopy-derive remain equal, even if the 'derive' feature isn't used. # See: https://github.com/matklad/macro-dep-test diff --git a/README.md b/README.md index 3d423bd2ae..ed084c3971 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,6 @@ for network parsing. the `alloc` crate is added as a dependency, and some allocation-related functionality is added. -- **`byteorder`** (enabled by default) - Adds the `byteorder` module and a dependency on the `byteorder` crate. - The `byteorder` module provides byte order-aware equivalents of the - multi-byte primitive numerical types. Unlike their primitive equivalents, - the types in this module have no alignment requirement and support byte - order conversions. This can be useful in handling file formats, network - packet layouts, etc which don't provide alignment guarantees and which may - use a byte order different from that of the execution platform. - - **`derive`** Provides derives for the core marker traits via the `zerocopy-derive` crate. These derives are re-exported from `zerocopy`, so it is not diff --git a/src/byteorder.rs b/src/byteorder.rs index 2769410451..b9e3a5ed28 100644 --- a/src/byteorder.rs +++ b/src/byteorder.rs @@ -71,16 +71,93 @@ use core::{ convert::{TryFrom, TryInto}, fmt::{self, Binary, Debug, Display, Formatter, LowerHex, Octal, UpperHex}, marker::PhantomData, + mem, num::TryFromIntError, }; -// We don't reexport `WriteBytesExt` or `ReadBytesExt` because those are only -// available with the `std` feature enabled, and zerocopy is `no_std` by -// default. -pub use ::byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian, NetworkEndian, BE, LE}; - use super::*; +/// A type-level representation of byte order. +/// +/// This type is implemented by [`BigEndian`] and [`LittleEndian`], which +/// represent big-endian and little-endian byte order respectively. This module +/// also provides a number of useful aliases for those types: [`NativeEndian`], +/// [`NetworkEndian`], [`BE`], and [`LE`]. +/// +/// `ByteOrder` types can be used to specify the byte order of the types in this +/// module - for example, [`U32`] is a 32-bit integer stored in +/// big-endian byte order. +/// +/// [`U32`]: U32 +pub trait ByteOrder: Copy + Clone + Debug + Display + Eq + PartialEq + Ord + PartialOrd { + #[doc(hidden)] + const ORDER: Order; +} + +#[allow(missing_copy_implementations, missing_debug_implementations)] +#[doc(hidden)] +pub enum Order { + BigEndian, + LittleEndian, +} + +/// Big-endian byte order. +/// +/// See [`ByteOrder`] for more details. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum BigEndian {} + +impl ByteOrder for BigEndian { + const ORDER: Order = Order::BigEndian; +} + +impl Display for BigEndian { + fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { + match *self {} + } +} + +/// Little-endian byte order. +/// +/// See [`ByteOrder`] for more details. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum LittleEndian {} + +impl ByteOrder for LittleEndian { + const ORDER: Order = Order::LittleEndian; +} + +impl Display for LittleEndian { + fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { + match *self {} + } +} + +/// The endianness used by this platform. +/// +/// This is a type alias for [`BigEndian`] or [`LittleEndian`] depending on the +/// endianness of the target platform. +#[cfg(target_endian = "big")] +pub type NativeEndian = BigEndian; + +/// The endianness used by this platform. +/// +/// This is a type alias for [`BigEndian`] or [`LittleEndian`] depending on the +/// endianness of the target platform. +#[cfg(target_endian = "little")] +pub type NativeEndian = LittleEndian; + +/// The endianness used in many network protocols. +/// +/// This is a type alias for [`BigEndian`]. +pub type NetworkEndian = BigEndian; + +/// A type alias for [`BigEndian`]. +pub type BE = BigEndian; + +/// A type alias for [`LittleEndian`]. +pub type LE = LittleEndian; + macro_rules! impl_fmt_trait { ($name:ident, $native:ident, $trait:ident) => { impl $trait for $name { @@ -239,8 +316,10 @@ macro_rules! define_type { $native:ident, $bits:expr, $bytes:expr, - $read_method:ident, - $write_method:ident, + $from_be_fn:path, + $to_be_fn:path, + $from_le_fn:path, + $to_le_fn:path, $number_kind:tt, [$($larger_native:ty),*], [$($larger_native_try:ty),*], @@ -312,33 +391,44 @@ example of how it can be used for parsing UDP packets. define_max_value_constant!($name, $bytes, $number_kind); - /// Constructs a new value from bytes which are already in the - /// endianness `O`. + /// Constructs a new value from bytes which are already in `O` byte + /// order. #[inline(always)] pub const fn from_bytes(bytes: [u8; $bytes]) -> $name { $name(bytes, PhantomData) } + + /// Extracts the bytes of `self` without swapping the byte order. + /// + /// The returned bytes will be in `O` byte order. + #[inline(always)] + pub const fn to_bytes(self) -> [u8; $bytes] { + self.0 + } } impl $name { - // TODO(joshlf): Make these const fns if the `ByteOrder` methods - // ever become const fns. - /// Constructs a new value, possibly performing an endianness swap /// to guarantee that the returned value has endianness `O`. #[inline(always)] - pub fn new(n: $native) -> $name { - let mut out = $name::default(); - O::$write_method(&mut out.0[..], n); - out + pub const fn new(n: $native) -> $name { + let bytes = match O::ORDER { + Order::BigEndian => $to_be_fn(n), + Order::LittleEndian => $to_le_fn(n), + }; + + $name(bytes, PhantomData) } /// Returns the value as a primitive type, possibly performing an /// endianness swap to guarantee that the return value has the /// endianness of the native platform. #[inline(always)] - pub fn get(self) -> $native { - O::$read_method(&self.0[..]) + pub const fn get(self) -> $native { + match O::ORDER { + Order::BigEndian => $from_be_fn(self.0), + Order::LittleEndian => $from_le_fn(self.0), + } } /// Updates the value in place as a primitive type, possibly @@ -346,7 +436,7 @@ example of how it can be used for parsing UDP packets. /// has the endianness `O`. #[inline(always)] pub fn set(&mut self, n: $native) { - O::$write_method(&mut self.0[..], n); + *self = Self::new(n); } } @@ -468,13 +558,15 @@ define_type!( u16, 16, 2, - read_u16, - write_u16, + u16::from_be_bytes, + u16::to_be_bytes, + u16::from_le_bytes, + u16::to_le_bytes, "unsigned integer", [u32, u64, u128, usize], [u32, u64, u128, usize], - [U32, U64, U128], - [U32, U64, U128] + [U32, U64, U128, Usize], + [U32, U64, U128, Usize] ); define_type!( A, @@ -482,8 +574,10 @@ define_type!( u32, 32, 4, - read_u32, - write_u32, + u32::from_be_bytes, + u32::to_be_bytes, + u32::from_le_bytes, + u32::to_le_bytes, "unsigned integer", [u64, u128], [u64, u128], @@ -496,28 +590,63 @@ define_type!( u64, 64, 8, - read_u64, - write_u64, + u64::from_be_bytes, + u64::to_be_bytes, + u64::from_le_bytes, + u64::to_le_bytes, "unsigned integer", [u128], [u128], [U128], [U128] ); -define_type!(A, U128, u128, 128, 16, read_u128, write_u128, "unsigned integer", [], [], [], []); +define_type!( + A, + U128, + u128, + 128, + 16, + u128::from_be_bytes, + u128::to_be_bytes, + u128::from_le_bytes, + u128::to_le_bytes, + "unsigned integer", + [], + [], + [], + [] +); +define_type!( + A, + Usize, + usize, + mem::size_of::() * 8, + mem::size_of::(), + usize::from_be_bytes, + usize::to_be_bytes, + usize::from_le_bytes, + usize::to_le_bytes, + "unsigned integer", + [], + [], + [], + [] +); define_type!( An, I16, i16, 16, 2, - read_i16, - write_i16, + i16::from_be_bytes, + i16::to_be_bytes, + i16::from_le_bytes, + i16::to_le_bytes, "signed integer", [i32, i64, i128, isize], [i32, i64, i128, isize], - [I32, I64, I128], - [I32, I64, I128] + [I32, I64, I128, Isize], + [I32, I64, I128, Isize] ); define_type!( An, @@ -525,8 +654,10 @@ define_type!( i32, 32, 4, - read_i32, - write_i32, + i32::from_be_bytes, + i32::to_be_bytes, + i32::from_le_bytes, + i32::to_le_bytes, "signed integer", [i64, i128], [i64, i128], @@ -539,30 +670,107 @@ define_type!( i64, 64, 8, - read_i64, - write_i64, + i64::from_be_bytes, + i64::to_be_bytes, + i64::from_le_bytes, + i64::to_le_bytes, "signed integer", [i128], [i128], [I128], [I128] ); -define_type!(An, I128, i128, 128, 16, read_i128, write_i128, "signed integer", [], [], [], []); +define_type!( + An, + I128, + i128, + 128, + 16, + i128::from_be_bytes, + i128::to_be_bytes, + i128::from_le_bytes, + i128::to_le_bytes, + "signed integer", + [], + [], + [], + [] +); +define_type!( + An, + Isize, + isize, + mem::size_of::() * 8, + mem::size_of::(), + isize::from_be_bytes, + isize::to_be_bytes, + isize::from_le_bytes, + isize::to_le_bytes, + "signed integer", + [], + [], + [], + [] +); + +// TODO(https://github.com/rust-lang/rust/issues/72447): Use the endianness +// conversion methods directly once those are const-stable. +macro_rules! define_float_conversion { + ($ty:ty, $bits:ident, $bytes:expr, $mod:ident) => { + mod $mod { + use super::*; + + define_float_conversion!($ty, $bits, $bytes, from_be_bytes, to_be_bytes); + define_float_conversion!($ty, $bits, $bytes, from_le_bytes, to_le_bytes); + } + }; + ($ty:ty, $bits:ident, $bytes:expr, $from:ident, $to:ident) => { + pub(crate) const fn $from(bytes: [u8; $bytes]) -> $ty { + transmute!($bits::$from(bytes)) + } + + pub(crate) const fn $to(f: $ty) -> [u8; $bytes] { + let bits: $bits = transmute!(f); + bits.$to() + } + }; +} + +define_float_conversion!(f32, u32, 4, f32_ext); +define_float_conversion!(f64, u64, 8, f64_ext); + define_type!( An, F32, f32, 32, 4, - read_f32, - write_f32, + f32_ext::from_be_bytes, + f32_ext::to_be_bytes, + f32_ext::from_le_bytes, + f32_ext::to_le_bytes, "floating point number", [f64], [], [F64], [] ); -define_type!(An, F64, f64, 64, 8, read_f64, write_f64, "floating point number", [], [], [], []); +define_type!( + An, + F64, + f64, + 64, + 8, + f64_ext::from_be_bytes, + f64_ext::to_be_bytes, + f64_ext::from_le_bytes, + f64_ext::to_le_bytes, + "floating point number", + [], + [], + [], + [] +); macro_rules! module { ($name:ident, $trait:ident, $endianness_str:expr) => { @@ -570,7 +778,7 @@ macro_rules! module { #[doc = $endianness_str] /// byte order. pub mod $name { - use byteorder::$trait; + use super::$trait; module!(@ty U16, $trait, "16-bit unsigned integer", $endianness_str); module!(@ty U32, $trait, "32-bit unsigned integer", $endianness_str); @@ -601,8 +809,6 @@ module!(native_endian, NativeEndian, "native-endian"); #[cfg(any(test, kani))] mod tests { - use ::byteorder::NativeEndian; - use { super::*, crate::{AsBytes, FromBytes, Unaligned}, @@ -748,7 +954,7 @@ mod tests { } macro_rules! impl_traits { - ($name:ident, $native:ident, $bytes:expr, $sign:ident $(, @$float:ident)?) => { + ($name:ident, $native:ident, $sign:ident $(, @$float:ident)?) => { impl Native for $native { // For some types, `0 as $native` is required (for example, when // `$native` is a floating-point type; `0` is an integer), but @@ -766,7 +972,7 @@ mod tests { impl ByteOrderType for $name { type Native = $native; - type ByteArray = [u8; $bytes]; + type ByteArray = [u8; mem::size_of::<$native>()]; const ZERO: $name = $name::ZERO; @@ -782,12 +988,12 @@ mod tests { $name::set(self, native) } - fn from_bytes(bytes: [u8; $bytes]) -> $name { + fn from_bytes(bytes: [u8; mem::size_of::<$native>()]) -> $name { $name::from(bytes) } - fn into_bytes(self) -> [u8; $bytes] { - <[u8; $bytes]>::from(self) + fn into_bytes(self) -> [u8; mem::size_of::<$native>()] { + <[u8; mem::size_of::<$native>()]>::from(self) } } @@ -815,16 +1021,18 @@ mod tests { }; } - impl_traits!(U16, u16, 2, unsigned); - impl_traits!(U32, u32, 4, unsigned); - impl_traits!(U64, u64, 8, unsigned); - impl_traits!(U128, u128, 16, unsigned); - impl_traits!(I16, i16, 2, signed); - impl_traits!(I32, i32, 4, signed); - impl_traits!(I64, i64, 8, signed); - impl_traits!(I128, i128, 16, signed); - impl_traits!(F32, f32, 4, signed, @float); - impl_traits!(F64, f64, 8, signed, @float); + impl_traits!(U16, u16, unsigned); + impl_traits!(U32, u32, unsigned); + impl_traits!(U64, u64, unsigned); + impl_traits!(U128, u128, unsigned); + impl_traits!(Usize, usize, unsigned); + impl_traits!(I16, i16, signed); + impl_traits!(I32, i32, signed); + impl_traits!(I64, i64, signed); + impl_traits!(I128, i128, signed); + impl_traits!(Isize, isize, unsigned); + impl_traits!(F32, f32, signed, @float); + impl_traits!(F64, f64, signed, @float); macro_rules! call_for_unsigned_types { ($fn:ident, $byteorder:ident) => { @@ -832,6 +1040,7 @@ mod tests { $fn::>(); $fn::>(); $fn::>(); + $fn::>(); }; } @@ -841,6 +1050,7 @@ mod tests { $fn::>(); $fn::>(); $fn::>(); + $fn::>(); }; } diff --git a/src/lib.rs b/src/lib.rs index c42584d7dc..58367ba1ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,15 +49,6 @@ //! the `alloc` crate is added as a dependency, and some allocation-related //! functionality is added. //! -//! - **`byteorder`** (enabled by default) -//! Adds the [`byteorder`] module and a dependency on the `byteorder` crate. -//! The `byteorder` module provides byte order-aware equivalents of the -//! multi-byte primitive numerical types. Unlike their primitive equivalents, -//! the types in this module have no alignment requirement and support byte -//! order conversions. This can be useful in handling file formats, network -//! packet layouts, etc which don't provide alignment guarantees and which may -//! use a byte order different from that of the execution platform. -//! //! - **`derive`** //! Provides derives for the core marker traits via the `zerocopy-derive` //! crate. These derives are re-exported from `zerocopy`, so it is not @@ -230,7 +221,6 @@ #[macro_use] mod macros; -#[cfg(feature = "byteorder")] pub mod byteorder; #[doc(hidden)] pub mod macro_util; @@ -238,7 +228,6 @@ mod util; // TODO(#252): If we make this pub, come up with a better name. mod wrappers; -#[cfg(feature = "byteorder")] pub use crate::byteorder::*; pub use crate::wrappers::*; #[cfg(any(feature = "derive", test))] @@ -285,6 +274,9 @@ mod zerocopy { pub(crate) use crate::*; } +#[cfg(feature = "byteorder")] +compile_error!("the `byteorder` feature has been removed; its functionality is now available regardless of feature set"); + #[rustversion::nightly] #[cfg(all(test, not(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)))] const _: () = { diff --git a/zerocopy-derive/Cargo.toml b/zerocopy-derive/Cargo.toml index 861c5f9be8..4fe956f5ef 100644 --- a/zerocopy-derive/Cargo.toml +++ b/zerocopy-derive/Cargo.toml @@ -34,4 +34,4 @@ testutil = { path = "../testutil" } # sometimes change the output format slightly, so a version mismatch can cause # CI test failures. trybuild = { version = "=1.0.85", features = ["diff"] } -zerocopy = { path = "../", features = ["default", "derive"] } +zerocopy = { path = "../", features = ["derive"] }