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