explicit-endian 0.1.6

Transparent traits to explicitely declare in-memory endianness for a virable or struct record
Documentation
/*
 * Copyright 2023 Alberto Ruiz <aruiz@gnome.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

//! # Explicit endian conversion no_std crate
//!
//! A lightweight, no_std crate for convenient data conversion between different endianness formats. Simplifies managing binary data on systems with varying endianness.
//!
//! ## Example
//!
//! ```rust
//! extern crate explicit_endian as ee;
//!
//! use ee::{LittleEndian, BigEndian, Swappable};
//!
//! fn main() {
//!     let value = 42u32;
//!
//!     // Convert to little-endian
//!     let le_value: LittleEndian<u32> = value.into();
//!
//!     // Convert to big-endian
//!     let be_value: BigEndian<u32> = value.into();
//!
//!     // You can now work with le_value and be_value in their respective endianness formats.
//! }
//! ```
//!
//! ## Supported Data Types
//!
//! - `u16`, `u32`, `u64`, `u128`
//! - `i16`, `i32`, `i64`, `i128`
//! - `usize`, `isize`
//! - `f32`, `f64`

#![no_std]
#![feature(core_intrinsics)]

use core::intrinsics::transmute;

/// A trait representing data types that can be swapped between endianness formats.
pub trait Swappable {}

impl Swappable for u128 {}
impl Swappable for u64 {}
impl Swappable for u32 {}
impl Swappable for u16 {}

impl Swappable for i128 {}
impl Swappable for i64 {}
impl Swappable for i32 {}
impl Swappable for i16 {}

impl Swappable for usize {}
impl Swappable for isize {}

impl Swappable for f32 {}
impl Swappable for f64 {}

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

/// A wrapper to store data in little endian format
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LittleEndian<T: Swappable>(T);

/// A wrapper to store data in big endian format
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BigEndian<T: Swappable>(T);

pub trait ReadAs<T: Swappable> {
    fn read_as(&self) -> T;
}

/// A macro to implement Into<T: Swappable> and From<T: Swappable> for either LittleEndian<T> or BigEndian<T>
macro_rules! into_from {
    ($type: ident, $end: ident, $a: tt, $b: tt, $swap: tt) => {
        impl Into<$type> for $end<$type> {
            fn into(self) -> $type {
                #[cfg(target_endian = $a)]
                return self.0;
                #[cfg(target_endian = $b)]
                return unsafe { transmute(self.0.$swap()) };
            }
        }

        impl From<$type> for $end<$type> {
            fn from(value: $type) -> Self {
                #[cfg(target_endian = $a)]
                return Self(value);
                #[cfg(target_endian = $b)]
                return Self(unsafe { transmute(value.$swap()) });
            }
        }

        impl ReadAs<$type> for $end<$type> {
            fn read_as(&self) -> $type {
                (*self).into()
            }
        }
    };
}

/// Wrapper macro to implement From/Into<T: Swappable> for LittleEndian<T: Swappable>
macro_rules! le_into_from {
    ($type: ident) => {
        into_from! {$type, LittleEndian, "little", "big", to_le_bytes}
    };
}

/// Wrapper macro to implement From/Into<T: Swappable> for BigEndian<T: Swappable>
macro_rules! be_into_from {
    ($type: ident) => {
        into_from! {$type, BigEndian, "big", "little", to_be_bytes}
    };
}

le_into_from! {u16}
le_into_from! {u32}
le_into_from! {u64}
le_into_from! {u128}
le_into_from! {usize}

le_into_from! {i16}
le_into_from! {i32}
le_into_from! {i64}
le_into_from! {i128}
le_into_from! {isize}

le_into_from! {f32}
le_into_from! {f64}

be_into_from! {u16}
be_into_from! {u32}
be_into_from! {u64}
be_into_from! {u128}
be_into_from! {usize}

be_into_from! {i16}
be_into_from! {i32}
be_into_from! {i64}
be_into_from! {i128}
be_into_from! {isize}

be_into_from! {f32}
be_into_from! {f64}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! test_type {
        ($fun: ident, $type: ident, $val: expr, $rev: expr) => {
            #[test]
            fn $fun() {
                let l: LittleEndian<$type> = $val.into();
                #[cfg(target_endian = "little")]
                assert_eq!(l.0, $val);
                #[cfg(target_endian = "big")]
                assert_eq!(l.0, $rev);

                let b: BigEndian<$type> = $val.into();
                #[cfg(target_endian = "little")]
                assert_eq!(b.0, $rev);
                #[cfg(target_endian = "big")]
                assert_eq!(b.0, $val);
            }
        };
    }

    #[test]
    fn test_read_value() {
        let foo: LittleEndian<u16> = 15.into();
        foo.read_as() as u16;
    }

    test_type! {test_u128, u128, 0xaa000000000000000000000000000000, 0x000000000000000000000000000000aa}
    test_type! {test_u64, u64, 0xaa00000000000000, 0x00000000000000aa}
    test_type! {test_u32, u32, 0xaa000000, 0x000000aa}
    test_type! {test_u16, u16, 0xaa00, 0x00aa}

    #[cfg(target_pointer_width = "32")]
    test_type! {test_usize, usize, 0xaa000000, 0x000000aa}
    #[cfg(target_pointer_width = "64")]
    test_type! {test_usize, usize, 0xaa00000000000000, 0x00000000000000aa}

    #[cfg(target_pointer_width = "32")]
    test_type! {test_isize, isize, -2isize, -16777217isize}
    #[cfg(target_pointer_width = "64")]
    test_type! {test_isize, isize, -2isize, -72057594037927937isize}

    test_type! {test_i16, i16, -2i16, -257i16}
    test_type! {test_i32, i32, -2i32, -16777217i32}
    test_type! {test_i64, i64, -2i64, -72057594037927937i64}
    test_type! {test_i128, i128, -2i128, -1329227995784915872903807060280344577i128}
    test_type! {test_f32, f32, 1.3_f32, 272302750000000000000000_f32}
    test_type! {test_f64, f64, 1.3_f64, -6065988000116450000000000000000000000000000000000000000000000000000_f64}
}