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}