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
#[doc(hidden)]
#[macro_export]
macro_rules! count_args {
    () => (0usize);
    ($x:tt $($xs:tt)*) => (1usize + count_args!($($xs)*));
}

/// Macro for creating a `ByteArray` from a string literal with automatic format detection.
///
/// # Format Detection
/// - **With `0x` prefix**: Parses as hexadecimal
/// - **With `0b` prefix**: Parses as binary
/// - **With `0o` prefix**: Parses as octal (not yet implemented)
/// - **No prefix**: **CAVEAT - Silently converts to UTF-8 encoding**
///
/// # CAVEAT
/// When no format prefix (`0x`, `0b`, `0o`) is provided, this macro will **silently interpret
/// the input as UTF-8** and convert it to bytes. If you intend to parse hex or binary data,
/// you **must** include the appropriate prefix, or use `try_hex!` or `try_bin!` instead.
///
/// # Examples
/// ```
/// use byte_array_ops::try_bytes;
///
/// // Hex with prefix
/// let hex = try_bytes!("0xdeadbeef").unwrap();
/// assert_eq!(hex.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
///
/// // Binary with prefix
/// let bin = try_bytes!("0b11110000").unwrap();
/// assert_eq!(bin.as_bytes(), [0xf0]);
///
/// // No prefix - silently converts to UTF-8
/// let utf8 = try_bytes!("hello").unwrap();
/// assert_eq!(utf8.as_bytes(), b"hello");
/// ```
#[macro_export]
macro_rules! try_bytes {
    ($x:literal) => {{
        use core::str::FromStr;
        use $crate::ByteArray;

        ByteArray::from_str($x)
    }};
}

/// Macro for creating a `ByteArray` from a hexadecimal string literal (without `0x` prefix).
///
/// # CAVEAT
/// This macro **always** interprets the input as hexadecimal. It does **not** perform UTF-8
/// conversion. If you need automatic format detection with UTF-8 fallback, use [`try_bytes!`] instead.
///
/// # Examples
/// ```
/// use byte_array_ops::try_hex;
///
/// let hex = try_hex!("deadbeef").unwrap();
/// assert_eq!(hex.as_bytes(), [0xde, 0xad, 0xbe, 0xef]);
/// ```
#[macro_export]
macro_rules! try_hex {
    ($x:literal) => {{
        use $crate::ByteArray;

        ByteArray::from_hex($x)
    }};
}

/// Macro for creating a `ByteArray` from a binary string literal (without `0b` prefix).
///
/// # CAVEAT
/// This macro **always** interprets the input as binary. It does **not** perform UTF-8
/// conversion. If you need automatic format detection with UTF-8 fallback, use [`try_bytes!`] instead.
///
/// # Examples
/// ```
/// use byte_array_ops::try_bin;
///
/// let bin = try_bin!("11110000").unwrap();
/// assert_eq!(bin.as_bytes(), [0xf0]);
/// ```
#[macro_export]
macro_rules! try_bin {
    ($x:literal) => {{
        use $crate::ByteArray;

        ByteArray::from_bin($x)
    }};
}

/// Macro to create a [`ByteArray`] with a value and a count similar to `vec![val; count]`
///
/// # Examples
///```
/// use byte_array_ops::bytes;
///
/// let bytes = bytes![5;10];
///
/// assert_eq!(bytes.as_bytes(),&vec![5u8;10]);
///```
#[macro_export]
macro_rules! bytes {
    [$pat:expr;$len:expr] => {{
        use $crate::ByteArray;

        ByteArray::init_value($pat,$len)
    }};
}

#[cfg(test)]
mod tests {
    use crate::ByteArray;
    use crate::errors::ByteArrayError;
    use alloc::vec;

    #[test]
    fn test_malformed_input() {
        let res = try_bytes!("0xZZFEFE").err().unwrap();
        assert_eq!(res, ByteArrayError::InvalidHexChar('Z'));
    }

    #[test]
    fn test_empty_bytes() {
        let res = try_bytes!("").err().unwrap();
        assert_eq!(res, ByteArrayError::EmptyInput);
    }

    #[test]
    fn test_standard_utf() {
        let res = try_bytes!("hello").unwrap();
        assert_eq!(res.as_bytes(), b"hello");
    }

    #[test]
    fn test_hex_with_prefix() {
        let res = try_bytes!("0xDEADBEEF").unwrap();
        assert_eq!(res.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
    }

    #[test]
    fn test_hex_without_prefix() {
        // treated as utf8
        let res = try_bytes!("DEADBEEF").unwrap();
        assert_eq!(res.as_bytes(), b"DEADBEEF");
    }

    #[test]
    fn test_lowercase_hex() {
        let res = try_bytes!("0xdeadbeef").unwrap();
        assert_eq!(res.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
    }

    #[test]
    fn test_mixed_case_hex() {
        let res = try_bytes!("0xDeAdBeEf").unwrap();
        assert_eq!(res.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
    }

    #[test]
    fn test_single_byte_hex() {
        let res = try_bytes!("0xFF").unwrap();
        assert_eq!(res.as_bytes(), &[0xFF]);
    }

    #[test]
    fn test_two_byte_hex() {
        let res = try_bytes!("0xCAFE").unwrap();
        assert_eq!(res.as_bytes(), &[0xCA, 0xFE]);
    }

    #[test]
    fn test_odd_length_hex() {
        // Test your implementation's behavior with odd-length hex strings
        let res = try_bytes!("0xFFF");
        // Adjust assertion based on whether you pad or error
        assert!(res.is_ok() || res.is_err());
    }

    #[test]
    fn test_special_characters_utf8() {
        let res = try_bytes!("hello world!").unwrap();
        assert_eq!(res.as_bytes(), b"hello world!");
    }

    #[test]
    fn test_unicode_utf8() {
        let res = try_bytes!("\u{1F980}").unwrap();
        assert_eq!(res.as_bytes(), &[0xF0, 0x9F, 0xa6, 0x80]);
    }

    // Test that array construction still works via From/Into
    #[test]
    fn test_from_array() {
        let res = ByteArray::from([0x01, 0x02, 0x03, 0xFF]);
        assert_eq!(res.as_bytes(), &[0x01, 0x02, 0x03, 0xFF]);
    }

    #[test]
    fn test_from_vec() {
        let res = ByteArray::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
        assert_eq!(res.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
    }

    #[test]
    fn test_from_slice() {
        let slice: &[u8] = &[0xCA, 0xFE];
        let res = ByteArray::from(slice);
        assert_eq!(res.as_bytes(), &[0xCA, 0xFE]);
    }

    #[test]
    fn test_try_hex_macro() {
        let res = try_hex!("deadbeef").unwrap();
        assert_eq!(res.as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
    }

    #[test]
    fn test_try_bin_macro() {
        let res = try_bin!("11110000").unwrap();
        assert_eq!(res.as_bytes(), &[0xf0]);
    }

    // test bytes empty
    // test bytes 0 0
    // test bytes pat 0
    // test bytes 0 count

    #[test]
    fn test_bytes_macro_zeros() {
        let bytes = bytes![0;0];

        assert_eq!(bytes, ByteArray::default());
    }

    #[test]
    fn test_bytes_macro_pat_zero() {
        let bytes = bytes![8u8;0];

        assert_eq!(bytes, ByteArray::default());
    }

    #[test]
    fn test_bytes_macro_general() {
        let bytes = bytes![0xff;1065];

        assert_eq!(bytes.as_bytes(), vec![0xff; 1065]);
    }
}