use crate::core::error::BusError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Width {
U8,
U16,
U32,
U64,
}
impl Width {
#[inline]
pub const fn bytes(self) -> u64 {
match self {
Width::U8 => 1,
Width::U16 => 2,
Width::U32 => 4,
Width::U64 => 8,
}
}
#[inline]
pub const fn bits(self) -> u32 {
match self {
Width::U8 => 8,
Width::U16 => 16,
Width::U32 => 32,
Width::U64 => 64,
}
}
#[inline]
pub const fn from_bytes(n: u64) -> Option<Self> {
match n {
1 => Some(Width::U8),
2 => Some(Width::U16),
4 => Some(Width::U32),
8 => Some(Width::U64),
_ => None,
}
}
#[inline]
pub const fn mask(self) -> u64 {
match self {
Width::U8 => 0xff,
Width::U16 => 0xffff,
Width::U32 => 0xffff_ffff,
Width::U64 => u64::MAX,
}
}
#[inline]
pub const fn is_aligned(self, addr: u64) -> bool {
addr.is_multiple_of(self.bytes())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Endian {
Little,
Big,
}
impl Endian {
pub const HOST: Endian = if cfg!(target_endian = "little") {
Endian::Little
} else {
Endian::Big
};
pub fn load(self, bytes: &[u8], width: Width) -> Result<u64, BusError> {
let n = width.bytes() as usize;
let src = bytes.get(..n).ok_or(BusError::BadAccess)?;
let mut v: u64 = 0;
match self {
Endian::Little => {
for (i, b) in src.iter().enumerate() {
v |= (*b as u64) << (8 * i);
}
}
Endian::Big => {
for b in src {
v = (v << 8) | (*b as u64);
}
}
}
Ok(v)
}
pub fn store(self, bytes: &mut [u8], width: Width, value: u64) -> Result<(), BusError> {
let n = width.bytes() as usize;
let dst = bytes.get_mut(..n).ok_or(BusError::BadAccess)?;
match self {
Endian::Little => {
for (i, b) in dst.iter_mut().enumerate() {
*b = (value >> (8 * i)) as u8;
}
}
Endian::Big => {
for (i, b) in dst.iter_mut().enumerate() {
*b = (value >> (8 * (n - 1 - i))) as u8;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_sizes_and_masks_agree() {
for w in [Width::U8, Width::U16, Width::U32, Width::U64] {
assert_eq!(w.bits(), (w.bytes() * 8) as u32);
assert_eq!(Width::from_bytes(w.bytes()), Some(w));
}
assert_eq!(Width::from_bytes(3), None);
assert_eq!(Width::U8.mask(), 0xff);
assert_eq!(Width::U64.mask(), u64::MAX);
}
#[test]
fn alignment() {
assert!(Width::U32.is_aligned(0x1000));
assert!(!Width::U32.is_aligned(0x1002));
assert!(Width::U8.is_aligned(0x1001));
}
#[test]
fn load_respects_byte_order() {
let bytes = [0x78, 0x56, 0x34, 0x12];
assert_eq!(
Endian::Little.load(&bytes, Width::U32).unwrap(),
0x1234_5678
);
assert_eq!(Endian::Big.load(&bytes, Width::U32).unwrap(), 0x7856_3412);
assert_eq!(Endian::Little.load(&bytes, Width::U16).unwrap(), 0x5678);
}
#[test]
fn store_round_trips_through_load() {
for endian in [Endian::Little, Endian::Big] {
for (width, value) in [
(Width::U8, 0xa5u64),
(Width::U16, 0xbeef),
(Width::U32, 0xdead_beef),
(Width::U64, 0x0123_4567_89ab_cdef),
] {
let mut buf = [0u8; 8];
endian.store(&mut buf, width, value).unwrap();
assert_eq!(
endian.load(&buf, width).unwrap(),
value,
"{endian:?} {width:?}"
);
}
}
}
#[test]
fn a_short_buffer_is_a_bus_fault_not_a_panic() {
let bytes = [0u8; 2];
assert_eq!(
Endian::Little.load(&bytes, Width::U32),
Err(BusError::BadAccess)
);
let mut small = [0u8; 1];
assert_eq!(
Endian::Big.store(&mut small, Width::U64, 0),
Err(BusError::BadAccess)
);
}
}