Skip to main content

hadris_common/types/
endian.rs

1//! Endian types for cross-platform compatibility.
2//!
3//! This module provides a set of types that can be used to read and write data in different endian
4//! formats. The `EndianType` enum represents the endianness of the system, and the `Endianness`
5//! trait provides methods to read and write data in the specified endianness.
6//!
7//! The number types, [`u16`], [`u32`], and [`u64`], have a counterpart with endianness, which are
8//! [`crate::types::number::U16`], [`crate::types::number::U32`], and [`crate::types::number::U64`]. These types are used to read and write data in the specified
9//! endianness, defined at the type level.
10
11/// The endianness of the system.
12///
13/// This enum represents the endianness of the system at runtime. It can be used
14/// to read and write data in the specified endianness.
15///
16/// NativeEndian is the default, and the fastest endianness, due to compatibility with the
17/// current architecture. However, compiler optimizations will also optimize away LittleEndian and
18/// BigEndian if the system is the same endianness.
19#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
20pub enum EndianType {
21    /// Native endianness.
22    #[default]
23    NativeEndian,
24    /// Little endianness.
25    ///
26    /// This means that the least significant byte is stored at the lowest address.
27    /// For example, the byte order of the number `0x1234` is `0x3412`.
28    LittleEndian,
29    /// Big endianness.
30    ///
31    /// This means that the most significant byte is stored at the lowest address.
32    /// For example, the byte order of the number `0x1234` is `0x1234`.
33    BigEndian,
34}
35
36impl core::fmt::Display for EndianType {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        match self {
39            Self::NativeEndian => write!(f, "native"),
40            Self::LittleEndian => write!(f, "little-endian"),
41            Self::BigEndian => write!(f, "big-endian"),
42        }
43    }
44}
45
46impl EndianType {
47    /// Returns whether this byte order is little-endian on the current target.
48    pub const fn is_le(&self) -> bool {
49        #[cfg(target_endian = "little")]
50        {
51            matches!(self, Self::LittleEndian | Self::NativeEndian)
52        }
53        #[cfg(target_endian = "big")]
54        {
55            matches!(self, Self::LittleEndian)
56        }
57    }
58    /// Reads a `u16` from the given bytes in the specified endianness.
59    pub fn read_u16(&self, bytes: [u8; 2]) -> u16 {
60        match self {
61            EndianType::NativeEndian => u16::from_ne_bytes(bytes),
62            EndianType::LittleEndian => u16::from_le_bytes(bytes),
63            EndianType::BigEndian => u16::from_be_bytes(bytes),
64        }
65    }
66
67    /// Reads a `u32` from the given bytes in the specified endianness.
68    pub fn read_u32(&self, bytes: [u8; 4]) -> u32 {
69        match self {
70            EndianType::NativeEndian => u32::from_ne_bytes(bytes),
71            EndianType::LittleEndian => u32::from_le_bytes(bytes),
72            EndianType::BigEndian => u32::from_be_bytes(bytes),
73        }
74    }
75
76    /// Writes a `u32` to the given bytes in the specified endianness.
77    pub fn read_u64(&self, bytes: [u8; 8]) -> u64 {
78        match self {
79            EndianType::NativeEndian => u64::from_ne_bytes(bytes),
80            EndianType::LittleEndian => u64::from_le_bytes(bytes),
81            EndianType::BigEndian => u64::from_be_bytes(bytes),
82        }
83    }
84
85    /// Returns the byte representation of a `u16` in the specified endianness.
86    pub fn u16_bytes(&self, value: u16) -> [u8; 2] {
87        match self {
88            EndianType::NativeEndian => value.to_ne_bytes(),
89            EndianType::LittleEndian => value.to_le_bytes(),
90            EndianType::BigEndian => value.to_be_bytes(),
91        }
92    }
93
94    /// Returns the byte representation of a `u32` in the specified endianness.
95    pub fn u32_bytes(&self, value: u32) -> [u8; 4] {
96        match self {
97            EndianType::NativeEndian => value.to_ne_bytes(),
98            EndianType::LittleEndian => value.to_le_bytes(),
99            EndianType::BigEndian => value.to_be_bytes(),
100        }
101    }
102
103    /// Returns the byte representation of a `u64` in the specified endianness.
104    pub fn u64_bytes(&self, value: u64) -> [u8; 8] {
105        match self {
106            EndianType::NativeEndian => value.to_ne_bytes(),
107            EndianType::LittleEndian => value.to_le_bytes(),
108            EndianType::BigEndian => value.to_be_bytes(),
109        }
110    }
111}
112
113/// A trait that represents the endianness of a type.
114///
115/// This trait shouldn`t be implemented directly, but rather through the [`Endian`] trait.
116/// See [`crate::types::number::U16`], [`crate::types::number::U32`], and [`crate::types::number::U64`] for examples.
117pub trait Endianness: Copy + Sized {
118    /// Returns the endianness at runtime.
119    fn get() -> EndianType;
120
121    /// Reads a `u16` from the given bytes in the specified endianness.
122    fn get_u16(bytes: [u8; 2]) -> u16;
123    /// Writes a `u16` to the given bytes in the specified endianness.
124    fn set_u16(value: u16, bytes: &mut [u8; 2]);
125    /// Reads a `u32` from the given bytes in the specified endianness.
126    fn get_u32(bytes: [u8; 4]) -> u32;
127    /// Writes a `u32` to the given bytes in the specified endianness.
128    fn set_u32(value: u32, bytes: &mut [u8; 4]);
129    /// Reads a `u64` from the given bytes in the specified endianness.
130    fn get_u64(bytes: [u8; 8]) -> u64;
131    /// Writes a `u64` to the given bytes in the specified endianness.
132    fn set_u64(value: u64, bytes: &mut [u8; 8]);
133
134    /// Reads a `u24` (stored in 3 bytes) as a `u32` in the specified endianness.
135    fn get_u24(bytes: [u8; 3]) -> u32 {
136        let mut buf = [0u8; 4];
137        if Self::get().is_le() {
138            buf[..3].copy_from_slice(&bytes);
139        } else {
140            buf[1..].copy_from_slice(&bytes);
141        }
142        Self::get_u32(buf)
143    }
144
145    /// Writes a `u24` value (as `u32`) to 3 bytes in the specified endianness.
146    fn set_u24(value: u32, bytes: &mut [u8; 3]) {
147        let mut buf = [0u8; 4];
148        Self::set_u32(value, &mut buf);
149        if Self::get().is_le() {
150            bytes.copy_from_slice(&buf[..3]);
151        } else {
152            bytes.copy_from_slice(&buf[1..]);
153        }
154    }
155}
156
157/// A type that represents the native endianness.
158///
159/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
160#[repr(transparent)]
161#[derive(Debug, Copy, Clone)]
162#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
163pub struct NativeEndian;
164
165/// A type that represents the little endianness.
166///
167/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
168#[repr(transparent)]
169#[derive(Debug, Copy, Clone)]
170#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
171pub struct LittleEndian;
172
173/// A type that represents the big endianness.
174///
175/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
176#[repr(transparent)]
177#[derive(Debug, Copy, Clone)]
178#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
179pub struct BigEndian;
180
181impl Endianness for NativeEndian {
182    #[inline]
183    fn get() -> EndianType {
184        EndianType::NativeEndian
185    }
186
187    #[inline]
188    fn get_u16(bytes: [u8; 2]) -> u16 {
189        u16::from_ne_bytes(bytes)
190    }
191
192    #[inline]
193    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
194        bytes.copy_from_slice(&value.to_ne_bytes());
195    }
196
197    #[inline]
198    fn get_u32(bytes: [u8; 4]) -> u32 {
199        u32::from_ne_bytes(bytes)
200    }
201
202    #[inline]
203    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
204        bytes.copy_from_slice(&value.to_ne_bytes());
205    }
206
207    #[inline]
208    fn get_u64(bytes: [u8; 8]) -> u64 {
209        u64::from_ne_bytes(bytes)
210    }
211
212    #[inline]
213    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
214        bytes.copy_from_slice(&value.to_ne_bytes());
215    }
216}
217
218impl Endianness for LittleEndian {
219    #[inline]
220    fn get() -> EndianType {
221        EndianType::LittleEndian
222    }
223
224    #[inline]
225    fn get_u16(bytes: [u8; 2]) -> u16 {
226        u16::from_le_bytes(bytes)
227    }
228
229    #[inline]
230    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
231        bytes.copy_from_slice(&value.to_le_bytes());
232    }
233
234    #[inline]
235    fn get_u32(bytes: [u8; 4]) -> u32 {
236        u32::from_le_bytes(bytes)
237    }
238
239    #[inline]
240    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
241        bytes.copy_from_slice(&value.to_le_bytes());
242    }
243
244    #[inline]
245    fn get_u64(bytes: [u8; 8]) -> u64 {
246        u64::from_le_bytes(bytes)
247    }
248
249    #[inline]
250    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
251        bytes.copy_from_slice(&value.to_le_bytes());
252    }
253}
254impl Endianness for BigEndian {
255    #[inline]
256    fn get() -> EndianType {
257        EndianType::BigEndian
258    }
259
260    #[inline]
261    fn get_u16(bytes: [u8; 2]) -> u16 {
262        u16::from_be_bytes(bytes)
263    }
264
265    #[inline]
266    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
267        bytes.copy_from_slice(&value.to_be_bytes());
268    }
269
270    #[inline]
271    fn get_u32(bytes: [u8; 4]) -> u32 {
272        u32::from_be_bytes(bytes)
273    }
274
275    #[inline]
276    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
277        bytes.copy_from_slice(&value.to_be_bytes());
278    }
279
280    #[inline]
281    fn get_u64(bytes: [u8; 8]) -> u64 {
282        u64::from_be_bytes(bytes)
283    }
284
285    #[inline]
286    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
287        bytes.copy_from_slice(&value.to_be_bytes());
288    }
289}
290
291/// A trait that represents a type that can be bytemuck::Pod and bytemuck::Zeroable, if the
292/// `bytemuck` feature is enabled.
293#[cfg(feature = "bytemuck")]
294pub trait MaybePod: bytemuck::Pod + bytemuck::Zeroable {}
295#[cfg(feature = "bytemuck")]
296impl<T: bytemuck::Pod + bytemuck::Zeroable> MaybePod for T {}
297/// Marker trait that accepts any type when the `bytemuck` feature is disabled.
298#[cfg(not(feature = "bytemuck"))]
299pub trait MaybePod {}
300#[cfg(not(feature = "bytemuck"))]
301impl<T> MaybePod for T {}
302
303/// A trait that represents a type with endianness.
304///
305/// This trait is used to read and write data in the specified endianness.
306/// It is implemented for all number types, and can be used to read and write data in the specified
307/// endianness.
308///
309/// The `Output` type parameter represents the type that the trait will return when reading or
310/// writing data. This type should be a primitive type or a struct that implements the `Pod` and
311/// `Zeroable` traits from the `bytemuck` crate, if the `bytemuck` feature is enabled.
312///
313/// The `LsbType` and `MsbType` type parameters are variants of the type that the trait will return
314/// when reading or writing data.
315pub trait Endian {
316    /// The type that the trait will return when reading or writing data.
317    ///
318    /// This type should return a primitive type or a struct that implements the `Pod` and
319    /// `Zeroable` traits from the `bytemuck` crate, if the `bytemuck` feature is enabled.
320    /// This type can be endianness-specific, for example, the `crate::types::number::U16` type is a struct that outputs
321    /// a `u16` value in the specified endianness.
322    type Output: MaybePod;
323
324    /// The Little Endian variant of the type.
325    ///
326    /// This type should return a little-endian variant of the type, for example, the LSB type for
327    /// a `crate::types::number::U16` is a `crate::types::number::U16<LittleEndian>` type.
328    type LsbType: MaybePod + Endian<Output = Self::Output>;
329
330    /// The Big Endian variant of the type.
331    ///
332    /// This type should return a big-endian variant of the type, for example, the MSB type for
333    /// a `crate::types::number::U16` is a `crate::types::number::U16<BigEndian>` type.
334    type MsbType: MaybePod + Endian<Output = Self::Output>;
335
336    /// Creates a new instance of the type with the given value.
337    fn new(value: Self::Output) -> Self;
338    /// Returns the value of the type.
339    fn get(&self) -> Self::Output;
340    /// Sets the value of the type.
341    fn set(&mut self, value: Self::Output);
342}
343
344#[cfg(all(test, feature = "std"))]
345mod tests {
346    #[test]
347    fn test_from_le_bytes() {
348        let value = u16::from_le_bytes([0x12, 0x34]);
349        assert_eq!(value, 0x3412);
350
351        let value = u32::from_le_bytes([0x12, 0x34, 0x56, 0x78]);
352        assert_eq!(value, 0x78563412);
353
354        let value = u64::from_le_bytes([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]);
355        assert_eq!(value, 0xf0debc9a78563412);
356    }
357
358    #[test]
359    fn test_from_be_bytes() {
360        let value = u16::from_be_bytes([0x12, 0x34]);
361        assert_eq!(value, 0x1234);
362
363        let value = u32::from_be_bytes([0x12, 0x34, 0x56, 0x78]);
364        assert_eq!(value, 0x12345678);
365
366        let value = u64::from_be_bytes([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]);
367        assert_eq!(value, 0x123456789abcdef0);
368    }
369}