use crate::error::{Result, ZiporaError};
use std::mem::size_of;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endianness {
Little,
Big,
Native,
}
impl Endianness {
#[inline]
pub const fn native() -> Self {
#[cfg(target_endian = "little")]
{
Self::Little
}
#[cfg(target_endian = "big")]
{
Self::Big
}
}
#[inline]
pub const fn is_native(self) -> bool {
matches!(
(self, Self::native()),
(Self::Native, _) | (Self::Little, Self::Little) | (Self::Big, Self::Big)
)
}
#[inline]
pub const fn needs_conversion(self) -> bool {
!self.is_native()
}
}
pub trait EndianConvert: Sized + Copy {
fn from_le(self) -> Self;
fn from_be(self) -> Self;
fn to_le(self) -> Self;
fn to_be(self) -> Self;
#[inline]
fn from_endian(self, endian: Endianness) -> Self {
match endian {
Endianness::Little => self.from_le(),
Endianness::Big => self.from_be(),
Endianness::Native => self,
}
}
#[inline]
fn to_endian(self, endian: Endianness) -> Self {
match endian {
Endianness::Little => self.to_le(),
Endianness::Big => self.to_be(),
Endianness::Native => self,
}
}
#[inline]
fn needs_swap_for(endian: Endianness) -> bool {
endian.needs_conversion()
}
}
macro_rules! impl_endian_convert {
($($t:ty),*) => {
$(
impl EndianConvert for $t {
#[inline]
fn from_le(self) -> Self {
<$t>::from_le(self)
}
#[inline]
fn from_be(self) -> Self {
<$t>::from_be(self)
}
#[inline]
fn to_le(self) -> Self {
<$t>::to_le(self)
}
#[inline]
fn to_be(self) -> Self {
<$t>::to_be(self)
}
}
)*
};
}
impl_endian_convert!(u16, u32, u64, u128, usize);
impl_endian_convert!(i16, i32, i64, i128, isize);
impl EndianConvert for u8 {
#[inline]
fn from_le(self) -> Self { self }
#[inline]
fn from_be(self) -> Self { self }
#[inline]
fn to_le(self) -> Self { self }
#[inline]
fn to_be(self) -> Self { self }
#[inline]
fn needs_swap_for(_endian: Endianness) -> bool { false }
}
impl EndianConvert for i8 {
#[inline]
fn from_le(self) -> Self { self }
#[inline]
fn from_be(self) -> Self { self }
#[inline]
fn to_le(self) -> Self { self }
#[inline]
fn to_be(self) -> Self { self }
#[inline]
fn needs_swap_for(_endian: Endianness) -> bool { false }
}
impl EndianConvert for f32 {
#[inline]
fn from_le(self) -> Self {
f32::from_bits(self.to_bits().from_le())
}
#[inline]
fn from_be(self) -> Self {
f32::from_bits(self.to_bits().from_be())
}
#[inline]
fn to_le(self) -> Self {
f32::from_bits(self.to_bits().to_le())
}
#[inline]
fn to_be(self) -> Self {
f32::from_bits(self.to_bits().to_be())
}
}
impl EndianConvert for f64 {
#[inline]
fn from_le(self) -> Self {
f64::from_bits(self.to_bits().from_le())
}
#[inline]
fn from_be(self) -> Self {
f64::from_bits(self.to_bits().from_be())
}
#[inline]
fn to_le(self) -> Self {
f64::from_bits(self.to_bits().to_le())
}
#[inline]
fn to_be(self) -> Self {
f64::from_bits(self.to_bits().to_be())
}
}
pub struct EndianIO<T> {
endianness: Endianness,
_phantom: std::marker::PhantomData<T>,
}
impl<T: EndianConvert> EndianIO<T> {
#[inline]
pub const fn new(endianness: Endianness) -> Self {
Self {
endianness,
_phantom: std::marker::PhantomData,
}
}
#[inline]
pub const fn little_endian() -> Self {
Self::new(Endianness::Little)
}
#[inline]
pub const fn big_endian() -> Self {
Self::new(Endianness::Big)
}
#[inline]
pub const fn native_endian() -> Self {
Self::new(Endianness::Native)
}
#[inline]
pub fn read_from_bytes(&self, bytes: &[u8]) -> Result<T> {
if bytes.len() < size_of::<T>() {
return Err(ZiporaError::invalid_data(
"Insufficient bytes for type"
));
}
let value = unsafe {
std::ptr::read_unaligned(bytes.as_ptr() as *const T)
};
Ok(value.from_endian(self.endianness))
}
#[inline]
pub fn write_to_bytes(&self, value: T, bytes: &mut [u8]) -> Result<()> {
if bytes.len() < size_of::<T>() {
return Err(ZiporaError::invalid_data(
"Insufficient buffer size for type"
));
}
let converted = value.to_endian(self.endianness);
unsafe {
std::ptr::write_unaligned(bytes.as_mut_ptr() as *mut T, converted);
}
Ok(())
}
pub fn convert_slice_to_endian(&self, values: &mut [T]) {
if !self.endianness.needs_conversion() {
return; }
for value in values.iter_mut() {
*value = value.to_endian(self.endianness);
}
}
pub fn convert_slice_from_endian(&self, values: &mut [T]) {
if !self.endianness.needs_conversion() {
return; }
for value in values.iter_mut() {
*value = value.from_endian(self.endianness);
}
}
#[inline]
pub const fn endianness(&self) -> Endianness {
self.endianness
}
#[inline]
pub const fn needs_conversion(&self) -> bool {
self.endianness.needs_conversion()
}
}
#[cfg(target_arch = "x86_64")]
pub mod simd {
#[cfg(target_feature = "sse2")]
pub fn convert_u16_slice_simd(values: &mut [u16], from_little: bool) {
if !from_little == cfg!(target_endian = "little") {
return; }
#[cfg(target_feature = "sse2")]
{
use std::arch::x86_64::*;
let mut chunks = values.chunks_exact_mut(8);
let chunk_iter: Vec<_> = chunks.by_ref().collect();
let remainder = chunks.into_remainder();
for chunk in chunk_iter {
unsafe {
let ptr = chunk.as_mut_ptr() as *mut __m128i;
let data = _mm_loadu_si128(ptr);
let swapped = _mm_or_si128(
_mm_slli_epi16(data, 8),
_mm_srli_epi16(data, 8)
);
_mm_storeu_si128(ptr, swapped);
}
}
for value in remainder {
*value = value.swap_bytes();
}
}
}
#[cfg(target_feature = "sse2")]
pub fn convert_u32_slice_simd(values: &mut [u32], from_little: bool) {
if !from_little == cfg!(target_endian = "little") {
return; }
#[cfg(target_feature = "sse2")]
{
use std::arch::x86_64::*;
let mut chunks = values.chunks_exact_mut(4);
let chunk_iter: Vec<_> = chunks.by_ref().collect();
let remainder = chunks.into_remainder();
for chunk in chunk_iter {
unsafe {
let ptr = chunk.as_mut_ptr() as *mut __m128i;
let data = _mm_loadu_si128(ptr);
let swapped = _mm_shuffle_epi8(data,
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3));
_mm_storeu_si128(ptr, swapped);
}
}
for value in remainder {
*value = value.swap_bytes();
}
}
}
}
pub struct EndianConfig {
default_endianness: Endianness,
auto_detect: bool,
simd_acceleration: bool,
}
impl EndianConfig {
pub fn new() -> Self {
Self {
default_endianness: Endianness::Native,
auto_detect: false,
simd_acceleration: true,
}
}
pub fn with_default_endianness(mut self, endianness: Endianness) -> Self {
self.default_endianness = endianness;
self
}
pub fn with_auto_detect(mut self, enable: bool) -> Self {
self.auto_detect = enable;
self
}
pub fn with_simd_acceleration(mut self, enable: bool) -> Self {
self.simd_acceleration = enable;
self
}
pub fn performance_optimized() -> Self {
Self {
default_endianness: Endianness::Native,
auto_detect: false,
simd_acceleration: true,
}
}
pub fn cross_platform() -> Self {
Self {
default_endianness: Endianness::Little, auto_detect: true,
simd_acceleration: true,
}
}
}
impl Default for EndianConfig {
fn default() -> Self {
Self::new()
}
}
pub const ENDIAN_MAGIC_LITTLE: u32 = 0x12345678;
pub const ENDIAN_MAGIC_BIG: u32 = 0x78563412;
pub fn detect_endianness_from_magic(magic: u32) -> Option<Endianness> {
match magic {
ENDIAN_MAGIC_LITTLE => Some(Endianness::Little),
ENDIAN_MAGIC_BIG => Some(Endianness::Big),
_ => None,
}
}
pub fn write_endianness_magic(endianness: Endianness) -> u32 {
match endianness {
Endianness::Little | Endianness::Native if Endianness::native() == Endianness::Little => {
ENDIAN_MAGIC_LITTLE
}
Endianness::Big | Endianness::Native if Endianness::native() == Endianness::Big => {
ENDIAN_MAGIC_BIG
}
_ => ENDIAN_MAGIC_LITTLE, }
}
#[cfg(test)]
mod tests {
use super::{Endianness, EndianConvert, EndianIO, EndianConfig, detect_endianness_from_magic, ENDIAN_MAGIC_LITTLE, ENDIAN_MAGIC_BIG};
#[test]
fn test_endianness_detection() {
assert_eq!(Endianness::native().is_native(), true);
#[cfg(target_endian = "little")]
{
assert_eq!(Endianness::native(), Endianness::Little);
assert!(Endianness::Little.is_native());
assert!(!Endianness::Big.is_native());
}
#[cfg(target_endian = "big")]
{
assert_eq!(Endianness::native(), Endianness::Big);
assert!(Endianness::Big.is_native());
assert!(!Endianness::Little.is_native());
}
}
#[test]
fn test_basic_endian_conversion() {
let value: u32 = 0x12345678;
assert_eq!(value.to_le().from_le(), value);
assert_eq!(value.to_be().from_be(), value);
let le_bytes = value.to_le_bytes();
let be_bytes = value.to_be_bytes();
assert_eq!(u32::from_le_bytes(le_bytes), value);
assert_eq!(u32::from_be_bytes(be_bytes), value);
}
#[test]
fn test_endian_io_operations() {
let value: u32 = 0x12345678;
let mut buffer = [0u8; 4];
let le_io = EndianIO::<u32>::little_endian();
le_io.write_to_bytes(value, &mut buffer).unwrap();
let read_value = le_io.read_from_bytes(&buffer).unwrap();
assert_eq!(read_value, value);
let be_io = EndianIO::<u32>::big_endian();
be_io.write_to_bytes(value, &mut buffer).unwrap();
let read_value = be_io.read_from_bytes(&buffer).unwrap();
assert_eq!(read_value, value);
let mut le_buffer = [0u8; 4];
let mut be_buffer = [0u8; 4];
le_io.write_to_bytes(value, &mut le_buffer).unwrap();
be_io.write_to_bytes(value, &mut be_buffer).unwrap();
if cfg!(target_endian = "little") {
assert_ne!(le_buffer, be_buffer);
}
}
#[test]
fn test_single_byte_types() {
let value: u8 = 0x42;
assert!(!u8::needs_swap_for(Endianness::Little));
assert!(!u8::needs_swap_for(Endianness::Big));
assert!(!i8::needs_swap_for(Endianness::Little));
assert!(!i8::needs_swap_for(Endianness::Big));
assert_eq!(value.to_le(), value);
assert_eq!(value.to_be(), value);
assert_eq!(value.from_le(), value);
assert_eq!(value.from_be(), value);
}
#[test]
fn test_floating_point_endian() {
let value: f32 = 3.14159;
assert_eq!(value.to_le().from_le(), value);
assert_eq!(value.to_be().from_be(), value);
let value: f64 = 2.718281828459045;
assert_eq!(value.to_le().from_le(), value);
assert_eq!(value.to_be().from_be(), value);
}
#[test]
fn test_slice_conversion() {
let mut values = vec![0x1234u16, 0x5678u16, 0x9abcu16, 0xdef0u16];
let original = values.clone();
let le_io = EndianIO::<u16>::little_endian();
le_io.convert_slice_to_endian(&mut values);
le_io.convert_slice_from_endian(&mut values);
assert_eq!(values, original);
}
#[test]
fn test_magic_number_detection() {
assert_eq!(
detect_endianness_from_magic(ENDIAN_MAGIC_LITTLE),
Some(Endianness::Little)
);
assert_eq!(
detect_endianness_from_magic(ENDIAN_MAGIC_BIG),
Some(Endianness::Big)
);
assert_eq!(detect_endianness_from_magic(0xdeadbeef), None);
}
#[test]
fn test_endian_config() {
let config = EndianConfig::performance_optimized();
assert_eq!(config.default_endianness, Endianness::Native);
assert!(!config.auto_detect);
assert!(config.simd_acceleration);
let config = EndianConfig::cross_platform();
assert_eq!(config.default_endianness, Endianness::Little);
assert!(config.auto_detect);
assert!(config.simd_acceleration);
}
#[test]
fn test_insufficient_buffer_error() {
let value: u32 = 0x12345678;
let mut small_buffer = [0u8; 2];
let io = EndianIO::<u32>::native_endian();
let result = io.write_to_bytes(value, &mut small_buffer);
assert!(result.is_err());
let result = io.read_from_bytes(&small_buffer);
assert!(result.is_err());
}
#[test]
fn test_needs_conversion() {
let native_io = EndianIO::<u32>::native_endian();
assert!(!native_io.needs_conversion());
#[cfg(target_endian = "little")]
{
let be_io = EndianIO::<u32>::big_endian();
assert!(be_io.needs_conversion());
let le_io = EndianIO::<u32>::little_endian();
assert!(!le_io.needs_conversion());
}
#[cfg(target_endian = "big")]
{
let le_io = EndianIO::<u32>::little_endian();
assert!(le_io.needs_conversion());
let be_io = EndianIO::<u32>::big_endian();
assert!(!be_io.needs_conversion());
}
}
#[test]
fn test_endian_conversion_traits() {
let value: u32 = 0x12345678;
assert_eq!(value.from_endian(Endianness::Native), value);
assert_eq!(value.to_endian(Endianness::Native), value);
let le_converted = value.to_endian(Endianness::Little);
assert_eq!(le_converted.from_endian(Endianness::Little), value);
let be_converted = value.to_endian(Endianness::Big);
assert_eq!(be_converted.from_endian(Endianness::Big), value);
}
}