arcium-primitives 0.8.0

Arcium primitives
Documentation
//! [`InPlaceCodec`] for primitive scalar types used as `HeapArray` elements.
//!
//! All encodings are fixed-width little-endian, hence architecture-independent. `usize` is encoded
//! as a fixed 8 bytes (not the platform word size) so the format is portable across 32/64-bit
//! targets; decoding validates the value fits into the local `usize`.

use std::mem::MaybeUninit;

use super::InPlaceCodec;
use crate::errors::PrimitiveError;

macro_rules! impl_inplace_codec_le {
    ($($ty:ty),* $(,)?) => {$(
        // SAFETY: `write_le_bytes` writes all `size_of::<$ty>()` little-endian bytes (initializing
        // every one); every bit pattern of that width is a valid `$ty`, so `read_le_bytes` is a
        // total, unbiased inverse. Architecture-independent by construction (fixed LE).
        unsafe impl InPlaceCodec for $ty {
            const ENCODED_SIZE: usize = std::mem::size_of::<$ty>();

            fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
                for (slot, &b) in out.iter_mut().zip(self.to_le_bytes().iter()) {
                    slot.write(b);
                }
            }

            fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
                let bytes = bytes.try_into().map_err(|_| {
                    PrimitiveError::InvalidSize(Self::ENCODED_SIZE, bytes.len())
                })?;
                Ok(<$ty>::from_le_bytes(bytes))
            }
        }
    )*};
}

impl_inplace_codec_le!(u8, u16, u32, u64);

// SAFETY: `bool` encodes as a single canonical byte (`0`/`1`); `write_le_bytes` initializes it and
// `read_le_bytes` rejects any other value, making the round-trip unbiased.
unsafe impl InPlaceCodec for bool {
    const ENCODED_SIZE: usize = 1;

    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
        out[0].write(*self as u8);
    }

    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
        match bytes[0] {
            0 => Ok(false),
            1 => Ok(true),
            other => Err(PrimitiveError::DeserializationFailed(format!(
                "non-canonical bool byte: {other}"
            ))),
        }
    }
}

// SAFETY: `usize` is encoded as a fixed 8-byte little-endian value (architecture-independent);
// `write_le_bytes` initializes all 8 bytes and `read_le_bytes` rejects values that don't fit the
// local `usize` (relevant on 32-bit targets), so the round-trip is unbiased where it succeeds.
unsafe impl InPlaceCodec for usize {
    const ENCODED_SIZE: usize = 8;

    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
        for (slot, &b) in out.iter_mut().zip((*self as u64).to_le_bytes().iter()) {
            slot.write(b);
        }
    }

    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
        let bytes = bytes
            .try_into()
            .map_err(|_| PrimitiveError::InvalidSize(Self::ENCODED_SIZE, bytes.len()))?;
        let value = u64::from_le_bytes(bytes);
        usize::try_from(value).map_err(|_| {
            PrimitiveError::DeserializationFailed(format!("usize out of range: {value}"))
        })
    }
}

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

    fn roundtrip<T: InPlaceCodec + PartialEq + std::fmt::Debug>(v: T) {
        let bytes = v.to_inplace_bytes();
        assert_eq!(bytes.len(), T::ENCODED_SIZE);
        assert_eq!(T::from_inplace_bytes(&bytes).unwrap(), v);
    }

    #[test]
    fn prim_inplace_roundtrips() {
        roundtrip(true);
        roundtrip(false);
        roundtrip(0xABu8);
        roundtrip(0x1234u16);
        roundtrip(0xDEAD_BEEFu32);
        roundtrip(0x0123_4567_89AB_CDEFu64);
        roundtrip(1234567usize);
    }

    #[test]
    fn bool_rejects_non_canonical() {
        assert!(bool::from_inplace_bytes(&[2]).is_err());
    }
}