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
//! Vec-like operations for ByteArray
//!
//! Provides explicit access to length, emptiness, and indexed access

use crate::model::ByteArray;
use zeroize::Zeroize;

impl ByteArray {
    /// Returns the number of bytes in the array
    pub fn len(&self) -> usize {
        self.bytes.len()
    }

    /// Returns true if there are no bytes in the array
    pub fn is_empty(&self) -> bool {
        self.bytes.is_empty()
    }

    /// Returns a reference to the byte at the given index, or None if out of bounds
    pub fn get(&self, index: usize) -> Option<&u8> {
        self.bytes.get(index)
    }

    /// Truncate function similar to [`alloc::vec::Vec::truncate`] but zeroizes the discarded
    /// portions before truncating to avoid leaking sensitive data. the `len` represents the
    /// new [`ByteArray`] length in bytes.
    #[inline]
    pub fn truncate(&mut self, len: usize) {
        if len >= self.len() {
            return;
        }
        // we zeroize the rest of the bytes before truncating
        self.bytes[len..].zeroize();

        self.bytes.truncate(len);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::try_hex;
    use alloc::vec;
    use core::ops::Range;

    fn util_validate_zero(ptr: *const u8, bounds: Range<usize>) {
        #[allow(unsafe_code)]
        unsafe {
            for i in bounds {
                assert_eq!(*ptr.add(i), 0u8, "Failed zero validation at index {i}");
            }
        }
    }
    #[test]
    fn test_len() {
        let arr = ByteArray::from_hex("ffab12345bc").expect("error converting");

        assert_eq!(arr.len(), 6);
    }

    #[test]
    fn test_is_empty() {
        let arr = ByteArray::default();
        assert_eq!(arr.is_empty(), true);

        let arr = ByteArray::from_hex("bb").expect("error converting");

        assert_eq!(arr.is_empty(), false);
    }

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

        assert_eq!(arr.get(0), Some(&0xaa));
        assert_eq!(arr.get(1), Some(&0xbb));
        assert_eq!(arr.get(2), Some(&0xcc));
        assert_eq!(arr.get(3), None);
    }

    /// function to avoid repetive code for truncate tests
    /// - `len`: if None then uses the bytes length else uses the passed length
    /// - `expected`: if None uses the same byte array else uses the passed array
    fn bootstrap_truncate(len: Option<usize>, expected: Option<ByteArray>) {
        let mut bytes = try_hex!("deadbeefdeadbeefdeadbeef").unwrap();
        let exp_b = bytes.clone();

        bytes.truncate(len.unwrap_or(bytes.len()));

        if let Some(start) = len {
            util_validate_zero(bytes.bytes.as_ptr(), start..bytes.len());
        }

        assert_eq!(bytes, expected.unwrap_or(exp_b));
    }
    #[test]
    fn test_truncate_zero_length() {
        bootstrap_truncate(Some(0), Some(ByteArray::default()));
    }

    #[test]
    fn test_truncate_equal_length() {
        bootstrap_truncate(None, None);
    }

    #[test]
    fn test_truncate_normal_length() {
        let expected = try_hex!("deadbe").unwrap();
        bootstrap_truncate(Some(3), Some(expected));
    }
}