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    #[inline]
136    fn get_u24(bytes: [u8; 3]) -> u32 {
137        let mut buf = [0u8; 4];
138        if Self::get().is_le() {
139            buf[..3].copy_from_slice(&bytes);
140        } else {
141            buf[1..].copy_from_slice(&bytes);
142        }
143        Self::get_u32(buf)
144    }
145
146    /// Writes a `u24` value (as `u32`) to 3 bytes in the specified endianness.
147    #[inline]
148    fn set_u24(value: u32, bytes: &mut [u8; 3]) {
149        let mut buf = [0u8; 4];
150        Self::set_u32(value, &mut buf);
151        if Self::get().is_le() {
152            bytes.copy_from_slice(&buf[..3]);
153        } else {
154            bytes.copy_from_slice(&buf[1..]);
155        }
156    }
157}
158
159/// A type that represents the native endianness.
160///
161/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
162#[repr(transparent)]
163#[derive(Debug, Copy, Clone)]
164#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
165pub struct NativeEndian;
166
167/// A type that represents the little endianness.
168///
169/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
170#[repr(transparent)]
171#[derive(Debug, Copy, Clone)]
172#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
173pub struct LittleEndian;
174
175/// A type that represents the big endianness.
176///
177/// This zero-sized-type can be used where a generic type parameter is expected for endianness.
178#[repr(transparent)]
179#[derive(Debug, Copy, Clone)]
180#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
181pub struct BigEndian;
182
183impl Endianness for NativeEndian {
184    #[inline]
185    fn get() -> EndianType {
186        EndianType::NativeEndian
187    }
188
189    #[inline]
190    fn get_u16(bytes: [u8; 2]) -> u16 {
191        u16::from_ne_bytes(bytes)
192    }
193
194    #[inline]
195    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
196        bytes.copy_from_slice(&value.to_ne_bytes());
197    }
198
199    #[inline]
200    fn get_u32(bytes: [u8; 4]) -> u32 {
201        u32::from_ne_bytes(bytes)
202    }
203
204    #[inline]
205    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
206        bytes.copy_from_slice(&value.to_ne_bytes());
207    }
208
209    #[inline]
210    fn get_u64(bytes: [u8; 8]) -> u64 {
211        u64::from_ne_bytes(bytes)
212    }
213
214    #[inline]
215    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
216        bytes.copy_from_slice(&value.to_ne_bytes());
217    }
218}
219
220impl Endianness for LittleEndian {
221    #[inline]
222    fn get() -> EndianType {
223        EndianType::LittleEndian
224    }
225
226    #[inline]
227    fn get_u16(bytes: [u8; 2]) -> u16 {
228        u16::from_le_bytes(bytes)
229    }
230
231    #[inline]
232    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
233        bytes.copy_from_slice(&value.to_le_bytes());
234    }
235
236    #[inline]
237    fn get_u32(bytes: [u8; 4]) -> u32 {
238        u32::from_le_bytes(bytes)
239    }
240
241    #[inline]
242    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
243        bytes.copy_from_slice(&value.to_le_bytes());
244    }
245
246    #[inline]
247    fn get_u64(bytes: [u8; 8]) -> u64 {
248        u64::from_le_bytes(bytes)
249    }
250
251    #[inline]
252    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
253        bytes.copy_from_slice(&value.to_le_bytes());
254    }
255}
256impl Endianness for BigEndian {
257    #[inline]
258    fn get() -> EndianType {
259        EndianType::BigEndian
260    }
261
262    #[inline]
263    fn get_u16(bytes: [u8; 2]) -> u16 {
264        u16::from_be_bytes(bytes)
265    }
266
267    #[inline]
268    fn set_u16(value: u16, bytes: &mut [u8; 2]) {
269        bytes.copy_from_slice(&value.to_be_bytes());
270    }
271
272    #[inline]
273    fn get_u32(bytes: [u8; 4]) -> u32 {
274        u32::from_be_bytes(bytes)
275    }
276
277    #[inline]
278    fn set_u32(value: u32, bytes: &mut [u8; 4]) {
279        bytes.copy_from_slice(&value.to_be_bytes());
280    }
281
282    #[inline]
283    fn get_u64(bytes: [u8; 8]) -> u64 {
284        u64::from_be_bytes(bytes)
285    }
286
287    #[inline]
288    fn set_u64(value: u64, bytes: &mut [u8; 8]) {
289        bytes.copy_from_slice(&value.to_be_bytes());
290    }
291}
292
293/// A trait that represents a type that can be bytemuck::Pod and bytemuck::Zeroable, if the
294/// `bytemuck` feature is enabled.
295#[cfg(feature = "bytemuck")]
296pub trait MaybePod: bytemuck::Pod + bytemuck::Zeroable {}
297#[cfg(feature = "bytemuck")]
298impl<T: bytemuck::Pod + bytemuck::Zeroable> MaybePod for T {}
299/// Marker trait that accepts any type when the `bytemuck` feature is disabled.
300#[cfg(not(feature = "bytemuck"))]
301pub trait MaybePod {}
302#[cfg(not(feature = "bytemuck"))]
303impl<T> MaybePod for T {}
304
305/// A trait that represents a type with endianness.
306///
307/// This trait is used to read and write data in the specified endianness.
308/// It is implemented for all number types, and can be used to read and write data in the specified
309/// endianness.
310///
311/// The `Output` type parameter represents the type that the trait will return when reading or
312/// writing data. This type should be a primitive type or a struct that implements the `Pod` and
313/// `Zeroable` traits from the `bytemuck` crate, if the `bytemuck` feature is enabled.
314///
315/// The `LsbType` and `MsbType` type parameters are variants of the type that the trait will return
316/// when reading or writing data.
317pub trait Endian {
318    /// The type that the trait will return when reading or writing data.
319    ///
320    /// This type should return a primitive type or a struct that implements the `Pod` and
321    /// `Zeroable` traits from the `bytemuck` crate, if the `bytemuck` feature is enabled.
322    /// This type can be endianness-specific, for example, the `crate::types::number::U16` type is a struct that outputs
323    /// a `u16` value in the specified endianness.
324    type Output: MaybePod;
325
326    /// The Little Endian variant of the type.
327    ///
328    /// This type should return a little-endian variant of the type, for example, the LSB type for
329    /// a `crate::types::number::U16` is a `crate::types::number::U16<LittleEndian>` type.
330    type LsbType: MaybePod + Endian<Output = Self::Output>;
331
332    /// The Big Endian variant of the type.
333    ///
334    /// This type should return a big-endian variant of the type, for example, the MSB type for
335    /// a `crate::types::number::U16` is a `crate::types::number::U16<BigEndian>` type.
336    type MsbType: MaybePod + Endian<Output = Self::Output>;
337
338    /// Creates a new instance of the type with the given value.
339    fn new(value: Self::Output) -> Self;
340    /// Returns the value of the type.
341    fn get(&self) -> Self::Output;
342    /// Sets the value of the type.
343    fn set(&mut self, value: Self::Output);
344}
345
346#[cfg(all(test, feature = "std"))]
347mod tests {
348    #[test]
349    fn test_from_le_bytes() {
350        let value = u16::from_le_bytes([0x12, 0x34]);
351        assert_eq!(value, 0x3412);
352
353        let value = u32::from_le_bytes([0x12, 0x34, 0x56, 0x78]);
354        assert_eq!(value, 0x78563412);
355
356        let value = u64::from_le_bytes([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]);
357        assert_eq!(value, 0xf0debc9a78563412);
358    }
359
360    #[test]
361    fn test_from_be_bytes() {
362        let value = u16::from_be_bytes([0x12, 0x34]);
363        assert_eq!(value, 0x1234);
364
365        let value = u32::from_be_bytes([0x12, 0x34, 0x56, 0x78]);
366        assert_eq!(value, 0x12345678);
367
368        let value = u64::from_be_bytes([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]);
369        assert_eq!(value, 0x123456789abcdef0);
370    }
371}