byteable/
lib.rs

1//! # Byteable
2//!
3//! A Rust crate for converting Rust types to and from byte arrays, facilitating
4//! easy serialization and deserialization, especially for network protocols or
5//! embedded systems. It provides traits for working with byte arrays,
6//! byteable types, and handling endianness.
7//!
8//! ## Features
9//! - `derive`: Enables the `Byteable` derive macro for automatic implementation of the `Byteable` trait.
10//! - `tokio`: Provides asynchronous read and write capabilities using `tokio`'s I/O traits.
11//!
12//! ## Usage
13//!
14//! ### Basic Byteable Conversion
15//!
16//! Implement the `Byteable` trait manually or use the `#[derive(Byteable)]` macro (with the `derive` feature enabled):
17//!
18//! ```rust
19//! use byteable::{Byteable, ReadByteable, WriteByteable, LittleEndian};
20//! use std::io::Cursor;
21//!
22//! #[derive(Byteable, Clone, Copy, PartialEq, Debug)]
23//! #[repr(C, packed)]
24//! struct MyPacket {
25//!     id: u16,
26//!     value: LittleEndian<u32>,
27//! }
28//!
29//! let packet = MyPacket {
30//!     id: 123,
31//!     value: LittleEndian::new(0x01020304),
32//! };
33//!
34//! // Convert to byte array
35//! let byte_array = packet.as_bytearray();
36//!
37//! // Write to a writer. Cursor implements `std::io::Write`,
38//! // thus it gains `write_one` from `WriteByteable`.
39//! let mut buffer = Cursor::new(vec![]);
40//! buffer.write_one(packet).unwrap();
41//! assert_eq!(buffer.into_inner(), vec![123, 0, 4, 3, 2, 1]);
42//!
43//! // Read from a reader. Cursor implements `std::io::Read`,
44//! // thus it gains `read_one` from `ReadByteable`.
45//! let mut reader = Cursor::new(vec![123, 0, 4, 3, 2, 1]);
46//! let read_packet: MyPacket = reader.read_one().unwrap();
47//! assert_eq!(read_packet, packet);
48//! ```
49//!
50//! ### Endianness Handling
51//!
52//! Use `BigEndian<T>` or `LittleEndian<T>` wrappers to control the byte order of primitive types.
53//!
54//! ```rust
55//! use byteable::{BigEndian, LittleEndian, Endianable};
56//!
57//! let value_be = BigEndian::new(0x01020304u32);
58//! assert_eq!(value_be.get_raw().to_ne_bytes(), [1, 2, 3, 4]);
59//!
60//! let value_le = LittleEndian::new(0x01020304u32);
61//! assert_eq!(value_le.get_raw().to_ne_bytes(), [4, 3, 2, 1]);
62//! ```
63//!
64//! ### Asynchronous I/O (with `tokio` feature)
65//!
66//! ```rust
67//! #[cfg(feature = "tokio")]
68//! async fn async_example() -> std::io::Result<()> {
69//!     use byteable::{Byteable, AsyncReadByteable, AsyncWriteByteable, LittleEndian};
70//!     use std::io::Cursor;
71//!
72//!     #[derive(Byteable, Clone, Copy, PartialEq, Debug)]
73//!     #[repr(C, packed)]
74//!     struct AsyncPacket {
75//!         sequence: u8,
76//!         data: LittleEndian<u16>,
77//!     }
78//!
79//!     let packet = AsyncPacket {
80//!         sequence: 5,
81//!         data: LittleEndian::new(0xAABB),
82//!     };
83//!
84//!     let mut buffer = Cursor::new(vec![]);
85//!     buffer.write_one(packet).await?;
86//!     assert_eq!(buffer.into_inner(), vec![5, 0xBB, 0xAA]);
87//!
88//!     let mut reader = Cursor::new(vec![5, 0xBB, 0xAA]);
89//!     let read_packet: AsyncPacket = reader.read_one().await?;
90//!     assert_eq!(read_packet, packet);
91//!     Ok(())
92//! }
93//! ```
94
95#[cfg(feature = "tokio")]
96use std::future::Future;
97use std::io::{Read, Write};
98
99#[cfg(feature = "derive")]
100pub use byteable_derive::Byteable;
101
102#[cfg(feature = "tokio")]
103use tokio::io::{AsyncReadExt, AsyncWriteExt};
104
105/// Trait for types that can be represented as a byte array.
106///
107/// This trait provides methods for creating zero-filled byte arrays and
108/// accessing them as mutable or immutable byte slices. It is primarily
109/// used as an associated type for the `Byteable` trait.
110pub trait ByteableByteArray {
111    /// Creates a new byte array filled with zeros.
112    fn create_zeroed() -> Self;
113    /// Returns a mutable slice reference to the underlying byte array.
114    #[must_use]
115    fn as_byteslice_mut(&mut self) -> &mut [u8];
116    /// Returns an immutable slice reference to the underlying byte array.
117    #[must_use]
118    fn as_byteslice(&self) -> &[u8];
119}
120
121/// Implements `ByteableByteArray` for fixed-size arrays `[u8; SIZE]`.
122impl<const SIZE: usize> ByteableByteArray for [u8; SIZE] {
123    fn create_zeroed() -> Self {
124        [0; SIZE]
125    }
126
127    fn as_byteslice_mut(&mut self) -> &mut [u8] {
128        self
129    }
130
131    fn as_byteslice(&self) -> &[u8] {
132        self
133    }
134}
135
136/// Trait for types that can be converted to and from a `ByteableByteArray`.
137///
138/// This trait is central to the `byteable` crate, enabling structured data
139/// to be easily serialized into and deserialized from byte arrays.
140/// It requires the type to implement `Copy`.
141pub trait Byteable: Copy {
142    /// The associated byte array type that can represent `Self`.
143    type ByteArray: ByteableByteArray;
144    /// Converts `self` into its `ByteableByteArray` representation.
145    #[must_use]
146    fn as_bytearray(self) -> Self::ByteArray;
147    /// Creates an instance of `Self` from a `ByteableByteArray`.
148    fn from_bytearray(ba: Self::ByteArray) -> Self;
149}
150
151macro_rules! impl_byteable {
152    ($type:ident) => {
153        impl Byteable for $type {
154            type ByteArray = [u8; std::mem::size_of::<Self>()];
155            fn as_bytearray(self) -> Self::ByteArray {
156                // Safety: This is safe because #[repr(C, packed)] ensures consistent memory layout
157                // and the size of Self matches the size of Self::ByteArray.
158                // The Byteable trait requires that the struct is `Copy`.
159                unsafe { std::mem::transmute(self) }
160            }
161            fn from_bytearray(ba: Self::ByteArray) -> Self {
162                // Safety: This is safe because #[repr(C, packed)] ensures consistent memory layout
163                // and the size of Self matches the size of Self::ByteArray.
164                // The Byteable trait requires that the struct is `Copy`.
165                unsafe { std::mem::transmute(ba) }
166            }
167        }
168    };
169}
170
171macro_rules! impl_byteable_generic {
172    ($type:ident, $generic:ident) => {
173        impl Byteable for $type<$generic> {
174            type ByteArray = [u8; std::mem::size_of::<Self>()];
175            fn as_bytearray(self) -> Self::ByteArray {
176                // Safety: This is safe because #[repr(C, packed)] ensures consistent memory layout
177                // and the size of Self matches the size of Self::ByteArray.
178                // The Byteable trait requires that the struct is `Copy`.
179                unsafe { std::mem::transmute(self) }
180            }
181            fn from_bytearray(ba: Self::ByteArray) -> Self {
182                // Safety: This is safe because #[repr(C, packed)] ensures consistent memory layout
183                // and the size of Self matches the size of Self::ByteArray.
184                // The Byteable trait requires that the struct is `Copy`.
185                unsafe { std::mem::transmute(ba) }
186            }
187        }
188    };
189}
190
191pub trait ByteableRaw<Regular>: Byteable {
192    fn to_regular(self) -> Regular;
193    fn from_regular(regular: Regular) -> Self;
194}
195
196pub trait ByteableRegular: Sized {
197    type Raw: Byteable;
198    fn to_raw(self) -> Self::Raw;
199    fn from_raw(raw: Self::Raw) -> Self;
200}
201
202impl<Raw, Regular> ByteableRaw<Regular> for Raw
203where
204    Regular: ByteableRegular<Raw = Raw>,
205    Raw: Byteable,
206{
207    fn to_regular(self) -> Regular {
208        Regular::from_raw(self)
209    }
210
211    fn from_regular(regular: Regular) -> Self {
212        regular.to_raw()
213    }
214}
215
216/// Extends `std::io::Read` with a method to read a `Byteable` type.
217pub trait ReadByteable: Read {
218    /// Reads one `Byteable` element from the reader.
219    ///
220    /// This method will create a zero-filled byte array, read enough bytes
221    /// from the underlying reader to fill it, and then convert the byte
222    /// array into the specified `Byteable` type.
223    fn read_one<T: Byteable>(&mut self) -> std::io::Result<T> {
224        let mut e = T::ByteArray::create_zeroed();
225        self.read_exact(e.as_byteslice_mut())?;
226        Ok(T::from_bytearray(e))
227    }
228}
229
230/// Implements `ReadByteable` for all types that implement `std::io::Read`.
231impl<T: Read> ReadByteable for T {}
232
233/// Extends `std::io::Write` with a method to write a `Byteable` type.
234pub trait WriteByteable: Write {
235    /// Writes one `Byteable` element to the writer.
236    ///
237    /// This method will convert the `Byteable` data into its byte array
238    /// representation and then write all those bytes to the underlying writer.
239    fn write_one<T: Byteable>(&mut self, data: T) -> std::io::Result<()> {
240        let e = data.as_bytearray();
241        self.write_all(e.as_byteslice())
242    }
243}
244
245/// Implements `WriteByteable` for all types that implement `std::io::Write`.
246impl<T: Write> WriteByteable for T {}
247
248/// Extends `tokio::io::AsyncReadExt` with an asynchronous method to read a `Byteable` type.
249///
250/// This trait is only available when the `tokio` feature is enabled.
251#[cfg(feature = "tokio")]
252pub trait AsyncReadByteable: tokio::io::AsyncReadExt {
253    /// Asynchronously reads one `Byteable` element from the reader.
254    ///
255    /// This method will create a zero-filled byte array, asynchronously read
256    /// enough bytes from the underlying reader to fill it, and then convert
257    /// the byte array into the specified `Byteable` type.
258    fn read_one<T: Byteable>(&mut self) -> impl Future<Output = std::io::Result<T>>
259    where
260        Self: Unpin + Send,
261    {
262        async move {
263            let mut e = T::ByteArray::create_zeroed();
264            self.read_exact(e.as_byteslice_mut()).await?;
265            Ok(T::from_bytearray(e))
266        }
267    }
268}
269
270/// Implements `AsyncReadByteable` for all types that implement `tokio::io::AsyncReadExt`.
271#[cfg(feature = "tokio")]
272impl<T: AsyncReadExt> AsyncReadByteable for T {}
273
274/// Extends `tokio::io::AsyncWriteExt` with an asynchronous method to write a `Byteable` type.
275///
276/// This trait is only available when the `tokio` feature is enabled.
277#[cfg(feature = "tokio")]
278pub trait AsyncWriteByteable: tokio::io::AsyncWriteExt {
279    /// Asynchronously writes one `Byteable` element to the writer.
280    ///
281    /// This method will convert the `Byteable` data into its byte array
282    /// representation and then asynchronously write all those bytes to
283    /// the underlying writer.
284    fn write_one<T: Byteable>(&mut self, data: T) -> impl Future<Output = std::io::Result<()>>
285    where
286        Self: Unpin,
287    {
288        async move {
289            let e = data.as_bytearray();
290            self.write_all(e.as_byteslice()).await
291        }
292    }
293}
294
295/// Implements `AsyncWriteByteable` for all types that implement `tokio::io::AsyncWriteExt`.
296#[cfg(feature = "tokio")]
297impl<T: AsyncWriteExt> AsyncWriteByteable for T {}
298
299/// Trait for types that support endianness conversion.
300///
301/// This trait provides methods to convert values to and from little-endian (LE)
302/// and big-endian (BE) byte orders. It is implemented for most primitive integer
303/// and floating-point types.
304pub trait Endianable: Copy {
305    /// Converts a value from its little-endian representation to the native endianness.
306    fn from_le(self) -> Self;
307    /// Converts a value from its big-endian representation to the native endianness.
308    fn from_be(self) -> Self;
309    /// Converts a value from the native endianness to its little-endian representation.
310    fn to_le(self) -> Self;
311    /// Converts a value from the native endianness to its big-endian representation.
312    fn to_be(self) -> Self;
313}
314
315macro_rules! impl_endianable {
316    ($type:ident) => {
317        impl Endianable for $type {
318            fn from_le(self) -> Self {
319                Self::from_le(self)
320            }
321
322            fn from_be(self) -> Self {
323                Self::from_be(self)
324            }
325
326            fn to_le(self) -> Self {
327                Self::to_le(self)
328            }
329
330            fn to_be(self) -> Self {
331                Self::to_be(self)
332            }
333        }
334    };
335}
336
337macro_rules! impl_endianable_float {
338    ($ftype:ident,$ntype:ident) => {
339        impl Endianable for $ftype {
340            fn from_le(self) -> Self {
341                Self::from_bits($ntype::from_le(self.to_bits()))
342            }
343
344            fn from_be(self) -> Self {
345                Self::from_bits($ntype::from_be(self.to_bits()))
346            }
347
348            fn to_le(self) -> Self {
349                Self::from_bits($ntype::to_le(self.to_bits()))
350            }
351
352            fn to_be(self) -> Self {
353                Self::from_bits($ntype::to_be(self.to_bits()))
354            }
355        }
356    };
357}
358
359impl_endianable!(u8);
360impl_endianable!(u16);
361impl_endianable!(u32);
362impl_endianable!(u64);
363impl_endianable!(u128);
364impl_endianable!(usize);
365impl_endianable!(i8);
366impl_endianable!(i16);
367impl_endianable!(i32);
368impl_endianable!(i64);
369impl_endianable!(i128);
370impl_endianable!(isize);
371
372impl_endianable_float!(f32, u32);
373impl_endianable_float!(f64, u64);
374
375/// A wrapper type that ensures the inner `Endianable` value is treated as Big-Endian.
376///
377/// When creating a `BigEndian` instance, the value is converted to big-endian.
378/// When retrieving the inner value with `get`, it is converted back
379/// to the native endianness.
380#[repr(transparent)]
381#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
382pub struct BigEndian<T: Endianable>(pub(crate) T);
383
384impl_byteable_generic!(BigEndian, u8);
385impl_byteable_generic!(BigEndian, u16);
386impl_byteable_generic!(BigEndian, u32);
387impl_byteable_generic!(BigEndian, u64);
388impl_byteable_generic!(BigEndian, u128);
389impl_byteable_generic!(BigEndian, usize);
390impl_byteable_generic!(BigEndian, i8);
391impl_byteable_generic!(BigEndian, i16);
392impl_byteable_generic!(BigEndian, i32);
393impl_byteable_generic!(BigEndian, i64);
394impl_byteable_generic!(BigEndian, i128);
395impl_byteable_generic!(BigEndian, isize);
396impl_byteable_generic!(BigEndian, f32);
397impl_byteable_generic!(BigEndian, f64);
398
399impl<T: Endianable> BigEndian<T> {
400    /// Creates a new `BigEndian` instance from a value, converting it to big-endian.
401    pub fn new(val: T) -> Self {
402        Self(val.to_be())
403    }
404
405    /// Returns the inner value, converting it from big-endian to the native endianness.
406    pub fn get(self) -> T {
407        self.get_raw().from_be()
408    }
409
410    /// Returns the underlying native representation without any endian conversion.
411    pub fn get_raw(self) -> T {
412        self.0
413    }
414}
415
416impl<T: Endianable + Default> Default for BigEndian<T> {
417    fn default() -> Self {
418        Self::new(T::default())
419    }
420}
421
422/// A wrapper type that ensures the inner `Endianable` value is treated as Little-Endian.
423///
424/// When creating a `LittleEndian` instance, the value is converted to little-endian.
425/// When retrieving the inner value with `get`, it is converted back
426/// to the native endianness.
427#[repr(transparent)]
428#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
429pub struct LittleEndian<T: Endianable>(pub(crate) T);
430
431impl_byteable_generic!(LittleEndian, u8);
432impl_byteable_generic!(LittleEndian, u16);
433impl_byteable_generic!(LittleEndian, u32);
434impl_byteable_generic!(LittleEndian, u64);
435impl_byteable_generic!(LittleEndian, u128);
436impl_byteable_generic!(LittleEndian, usize);
437impl_byteable_generic!(LittleEndian, i8);
438impl_byteable_generic!(LittleEndian, i16);
439impl_byteable_generic!(LittleEndian, i32);
440impl_byteable_generic!(LittleEndian, i64);
441impl_byteable_generic!(LittleEndian, i128);
442impl_byteable_generic!(LittleEndian, isize);
443impl_byteable_generic!(LittleEndian, f32);
444impl_byteable_generic!(LittleEndian, f64);
445
446impl<T: Endianable> LittleEndian<T> {
447    /// Creates a new `LittleEndian` instance from a value, converting it to little-endian.
448    pub fn new(val: T) -> Self {
449        Self(val.to_le())
450    }
451
452    /// Returns the inner value, converting it from little-endian to the native endianness.
453    pub fn get(self) -> T {
454        self.get_raw().from_le()
455    }
456
457    /// Returns the underlying native representation without any endian conversion.
458    pub fn get_raw(self) -> T {
459        self.0
460    }
461}
462
463impl<T: Endianable + Default> Default for LittleEndian<T> {
464    fn default() -> Self {
465        Self::new(T::default())
466    }
467}
468
469#[cfg(test)]
470mod tests {
471
472    mod byteable {
473        #[cfg(feature = "derive")]
474        mod derive {
475            use crate::{BigEndian, Byteable, LittleEndian}; // Corrected use for Byteable
476            #[derive(Byteable, Clone, Copy, PartialEq, Debug)] // Added PartialEq and Debug for assertions
477            #[repr(C, packed)]
478            struct ABC {
479                a: LittleEndian<u16>,
480                b: LittleEndian<u16>,
481                c: BigEndian<u16>,
482            }
483
484            #[test]
485            fn test_derive() {
486                let a = ABC {
487                    a: LittleEndian::new(1),
488                    b: LittleEndian::new(2),
489                    c: BigEndian::new(3),
490                };
491
492                let expected_bytes = [1, 0, 2, 0, 0, 3];
493                assert_eq!(a.as_bytearray(), expected_bytes);
494
495                let read_a = ABC::from_bytearray(expected_bytes);
496                assert_eq!(read_a.a.get(), 1);
497                assert_eq!(read_a.b.get(), 2);
498                assert_eq!(read_a.c.get(), 3);
499                assert_eq!(read_a, a);
500            }
501        }
502    }
503
504    mod endian {
505        use super::super::{BigEndian, LittleEndian};
506        #[test]
507        fn big_endian_test() {
508            // Test with a known big-endian system or convert to bytes and check order
509            let val = 0x01020304u32;
510            let be_val = BigEndian::new(val);
511
512            // get converts from BE to native, so if we create it from a native value,
513            // and then turn it back, it should be the original value.
514            assert_eq!(be_val.get(), val);
515            assert_eq!(be_val.get_raw().to_ne_bytes(), [1, 2, 3, 4]);
516            assert_eq!(u32::from_be_bytes(be_val.get_raw().to_ne_bytes()), val);
517        }
518
519        #[test]
520        fn little_endian_test() {
521            // Test with a known little-endian system or convert to bytes and check order
522            let val = 0x01020304u32;
523            let le_val = LittleEndian::new(val);
524
525            // get converts from LE to native, so if we create it from a native value,
526            // and then turn it back, it should be the original value.
527            assert_eq!(le_val.get(), val);
528            assert_eq!(le_val.get_raw().to_ne_bytes(), [4, 3, 2, 1]);
529            assert_eq!(u32::from_le_bytes(le_val.get_raw().to_ne_bytes()), val);
530        }
531    }
532
533    mod derive {
534        use crate::{Byteable, ByteableRegular};
535
536        #[derive(Clone, Copy)]
537        struct Basic {
538            a: u16,
539            b: u32,
540            c: u16,
541        }
542
543        #[derive(Clone, Copy)]
544        #[repr(C, packed)]
545        struct BasicRaw {
546            a: u16,
547            b: u32,
548            c: u16,
549        }
550        impl_byteable!(BasicRaw);
551
552        impl From<Basic> for BasicRaw {
553            fn from(value: Basic) -> Self {
554                BasicRaw {
555                    a: value.a,
556                    b: value.b,
557                    c: value.c,
558                }
559            }
560        }
561        impl From<BasicRaw> for Basic {
562            fn from(value: BasicRaw) -> Self {
563                Basic {
564                    a: value.a,
565                    b: value.b,
566                    c: value.c,
567                }
568            }
569        }
570
571        impl ByteableRegular for Basic {
572            type Raw = BasicRaw;
573
574            fn to_raw(self) -> Self::Raw {
575                BasicRaw {
576                    a: self.a,
577                    b: self.b,
578                    c: self.c,
579                }
580            }
581
582            fn from_raw(raw: Self::Raw) -> Self {
583                Self {
584                    a: raw.a,
585                    b: raw.b,
586                    c: raw.c,
587                }
588            }
589        }
590    }
591}