byte-array-ops 0.4.0

A no_std-compatible library for security-by-default byte array operations. Includes automatic memory zeroization, constant-time utilities, multiple input formats (hex, binary, UTF-8), bitwise operations, and comprehensive type conversions with minimal dependencies.
Documentation
//! This module contains all type conversion definitions to and from [`ByteArray`]

use crate::errors::ByteArrayError;
use crate::model::ByteArray;
use alloc::vec;
use alloc::vec::Vec;
use core::str::FromStr;

impl<const N: usize> From<&[u8; N]> for ByteArray {
    /// converts a byte slice into our Byte array, odd word padding is not needed here as arrays are always aligned
    /// TODO document this as potentially expensive copy
    fn from(value: &[u8; N]) -> Self {
        ByteArray::from(&value[..])
    }
}

impl From<&[u8]> for ByteArray {
    /// converts a byte slice into our Byte array, odd word padding is not needed here as arrays are always aligned
    /// TODO document this as potentially expensive copy
    fn from(value: &[u8]) -> Self {
        ByteArray {
            bytes: value.to_vec(),
        }
    }
}

impl From<Vec<u8>> for ByteArray {
    fn from(bytes: Vec<u8>) -> Self {
        ByteArray {
            bytes, // Zero-cost move
        }
    }
}

impl From<u8> for ByteArray {
    fn from(value: u8) -> Self {
        ByteArray { bytes: vec![value] }
    }
}

impl FromStr for ByteArray {
    type Err = ByteArrayError;
    /// parses a [`&str`] as follows :
    ///  - does it start with a format identifier with `0x`, `0b`, or `0o` then parses in hex, binary or octal respectively (case insensitive)
    ///     - The routines for the conversion here ignore underscore `_` allowing its use as a separator for example `"0b1110_0010_0110"`
    ///  - for all other cases defaults to `UTF-8` as encoding and parses the slice as such
    ///
    /// TODO add example with string slice lower than 2 chars
    ///
    ///
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.is_empty() {
            return Err(ByteArrayError::EmptyInput);
        }

        if s.starts_with("0x") || s.starts_with("0X") {
            return ByteArray::from_hex(&s[2..]);
        }

        if s.starts_with("0b") || s.starts_with("0B") {
            return ByteArray::from_bin(&s[2..]);
        }

        if s.starts_with("0o") || s.starts_with("0O") {
            unimplemented!()
        }

        // It's a regular UTF-8 string
        Ok(ByteArray::from(s.as_bytes()))
    }
}

impl<const N: usize> From<[u8; N]> for ByteArray {
    fn from(value: [u8; N]) -> Self {
        (&value).into()
    }
}

impl ByteArray {
    #[allow(unsafe_code)]
    /// raw pointer access for cases where the data is passed over an ffi interface and needs
    /// to be read
    ///
    /// # Safety
    /// > Do `NOT` use only if you must. use [`ByteArray::as_bytes`] or [`ByteArray::as_ref`] as a safe alternative
    ///
    /// This function returns the raw pointer to the inner type as is and avoids any checks it is thus
    /// marked unsafe
    pub unsafe fn as_ptr(&self) -> *const u8 {
        self.bytes.as_ptr()
    }

    #[allow(unsafe_code)]
    /// raw mutable pointer access for cases where the data is passed over an ffi interface and needs
    /// to be mutated
    ///
    /// # Safety
    /// > Do `NOT` use only if you must. use [`ByteArray::as_mut`] as a safe alternative
    ///
    /// This function returns the raw pointer to the inner type as is and avoids any checks it is thus
    /// marked unsafe
    pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 {
        self.bytes.as_mut_ptr()
    }
}

#[cfg(test)]
#[allow(unsafe_code)]
mod unsafe_tests {
    use crate::try_hex;

    #[test]
    fn test_unsafe_ptr() {
        let data = try_hex!("00fe0abcd0abcdfeab").unwrap();
        unsafe {
            assert_eq!(*data.as_ptr().add(1), 0xfe);
            assert_eq!(*data.as_ptr().add(7), 0xfe);
        }
    }

    #[test]
    fn test_unsafe_mut_ptr() {
        let mut data = try_hex!("cafebeef").unwrap();
        unsafe {
            *data.as_mut_ptr().add(3) = 0xed;
        }
        assert_eq!(*data.get(3).unwrap(), 0xed);
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_slice_reference() {
        let bytes = [0xde, 0xad, 0xbe, 0xef];
        let bytes_slice = &bytes;

        let byte_array: ByteArray = bytes_slice.into();

        assert_eq!(byte_array.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_from_vec() {
        let bytes: Vec<u8> = vec![0xde, 0xad, 0xbe, 0xef];

        let arr = ByteArray::from(bytes);

        assert_eq!(arr.as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_from_single_byte() {
        let arr: ByteArray = 0xff.into();

        assert_eq!(arr.as_bytes(), [0xff]);
    }

    #[test]
    fn test_from_array_literal() {
        let arr: ByteArray = [0xaa, 0xbb, 0xcc].into();

        assert_eq!(arr.as_bytes(), [0xaa, 0xbb, 0xcc]);
    }

    #[test]
    fn test_from_array_reference() {
        let bytes = [0x01, 0x02, 0x03, 0x04];
        let arr: ByteArray = (&bytes).into();

        assert_eq!(arr.as_bytes(), [0x01, 0x02, 0x03, 0x04]);
    }

    #[test]
    fn test_from_empty_slice() {
        let empty: &[u8] = &[];
        let arr: ByteArray = empty.into();

        assert!(arr.is_empty());
    }

    #[test]
    fn test_from_empty_vec() {
        let empty: Vec<u8> = Vec::new();
        let arr = ByteArray::from(empty);

        assert!(arr.is_empty());
    }

    #[test]
    fn test_from_str_hex() {
        let arr: ByteArray = "0xdeadbeef".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_from_str_hex_uppercase() {
        let arr: ByteArray = "0XDEADBEEF".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_from_str_hex_mixed_case() {
        let arr: ByteArray = "0xDeAdBeEf".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_from_str_hex_odd_length() {
        let arr: ByteArray = "0xfff".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0x0f, 0xff]);
    }

    #[test]
    fn test_from_str_binary() {
        let arr: ByteArray = "0b11011110".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0xde]);
    }

    #[test]
    fn test_from_str_binary_uppercase() {
        let arr: ByteArray = "0B11011110".parse().unwrap();

        assert_eq!(arr.as_bytes(), [0xde]);
    }

    #[test]
    fn test_from_str_utf8() {
        let arr: ByteArray = "hello".parse().unwrap();

        assert_eq!(arr.as_bytes(), b"hello");
    }

    #[test]
    fn test_from_str_utf8_single_char() {
        let arr: ByteArray = "A".parse().unwrap();

        assert_eq!(arr.as_bytes(), b"A");
    }

    #[test]
    fn test_from_str_empty_error() {
        let result: Result<ByteArray, ByteArrayError> = "".parse();

        assert!(result.is_err());
        if let Err(e) = result {
            assert!(matches!(e, ByteArrayError::EmptyInput));
        }
    }

    #[test]
    fn test_from_str_hex_invalid_char() {
        let result: Result<ByteArray, ByteArrayError> = "0xgggg".parse();

        assert!(result.is_err());
    }

    #[test]
    fn test_from_str_binary_invalid_char() {
        let result: Result<ByteArray, ByteArrayError> = "0b12345".parse();

        assert!(result.is_err());
    }

    #[test]
    fn test_from_large_array() {
        let large: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
        let arr = ByteArray::from(large.clone());

        assert_eq!(arr.len(), 1000);
        assert_eq!(arr.as_bytes(), large.as_slice());
    }
}