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