blob: 55d8875413774a6a8ae12f739e2444326673dd92 [file] [log] [blame]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
use core::fmt::Debug;
use zerocopy::{FromBytes, IntoBytes};
/// Marker trait for all cipher modes.
pub trait CipherMode: core::fmt::Debug + Clone + Copy {}
/// Marker trait for block cipher modes (e.g., CBC, CTR).
pub trait BlockCipherMode: CipherMode {}
/// Marker trait for AEAD modes (e.g., GCM, CCM).
pub trait AeadCipherMode: CipherMode {}
/// Marker trait for stream cipher modes (e.g., ChaCha20).
pub trait StreamCipherMode: CipherMode {}
/// Common error kinds for symmetric cipher operations.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
/// Failed to initialize the cipher context.
InitializationError,
/// General hardware failure during cipher operation.
HardwareFailure,
/// Insufficient permissions to access hardware or perform the operation.
PermissionDenied,
/// The cipher context is in an invalid or uninitialized state.
InvalidState,
/// The input data is invalid (e.g., wrong length or format).
InvalidInput,
/// The specified algorithm or mode is not supported.
UnsupportedAlgorithm,
/// Key or IV is invalid or missing.
KeyError,
}
/// Trait for converting implementation-specific errors into a generic [`ErrorKind`].
pub trait Error: Debug {
/// Returns a generic error kind corresponding to the specific error.
fn kind(&self) -> ErrorKind;
}
/// Trait for associating a type with an error type.
pub trait ErrorType {
/// The associated error type.
type Error: Error;
}
/// Trait for symmetric cipher algorithms.
///
/// This trait defines the core types and constraints for symmetric cipher implementations.
/// All types must support zero-copy serialization via the `zerocopy` crate traits,
/// enabling efficient operation with both software and hardware implementations.
///
/// # Zero-Copy Requirements
///
/// The `FromBytes` and `IntoBytes` trait bounds ensure that:
/// - Types can be safely constructed from byte arrays without validation
/// - Types can be converted to byte arrays for hardware or network transmission
/// - No unnecessary copying occurs during cryptographic operations
/// - Memory layout is well-defined and predictable
///
/// # Security Considerations
///
/// - Key types should implement `Zeroize` for secure memory cleanup
/// - Plaintext and ciphertext types should be handled securely in memory
/// - Consider using `secrecy` crate for sensitive data protection
///
/// # Example Implementation
///
/// ```ignore
/// impl SymmetricCipher for MyAesCipher {
/// type Key = [u8; 32]; // AES-256 key
/// type Nonce = [u8; 16]; // 128-bit nonce/IV
/// type PlainText = [u8; 64]; // Fixed-size plaintext buffer
/// type CipherText = [u8; 80]; // Fixed-size ciphertext buffer (with padding)
/// type Error = CryptoError;
/// }
/// ```
pub trait SymmetricCipher: ErrorType {
/// The cryptographic key type.
///
/// This type represents the secret key material used for encryption and decryption.
/// The key can be provided through various mechanisms including software keys,
/// key vault references, or hardware-managed keys.
///
/// # Security Requirements
///
/// - Should implement `Zeroize` for secure memory cleanup when stored in software
/// - May reference keys stored in secure hardware or key vaults
/// - Size should match the algorithm's key requirements (e.g., 32 bytes for AES-256)
/// - Consider using masked key shares for side-channel protection in hardware
///
/// # Key Sources
///
/// - **Software Keys**: `[u8; 32]` for AES-256, `[u8; 16]` for AES-128
/// - **Key Vault References**: `KeyVaultHandle`, `KeyId`, or similar abstract types
/// - **Hardware Keys**: Sideloaded keys from key managers or crypto coprocessors
/// - **Masked Keys**: Split key shares for side-channel attack resistance
///
/// # Common Types
///
/// - `[u8; 32]` for AES-256, ChaCha20 (software keys)
/// - `[u8; 16]` for AES-128 (software keys)
/// - `KeyVaultId` for key vault managed keys
/// - `HardwareKeyHandle` for hardware-managed keys
/// - Custom types for hardware-specific key formats or masked implementations
type Key;
/// The nonce or initialization vector type.
///
/// This type represents the nonce (number used once) or initialization vector
/// for the cipher operation. It must be unique for each encryption with the same key.
///
/// # Security Requirements
///
/// - Must never be reused with the same key (critical for CTR mode and stream ciphers)
/// - Should be generated using cryptographically secure random number generation
/// - Size must match the algorithm's requirements
///
/// # Common Types
///
/// - `[u8; 16]` for AES-CTR, AES-CBC
/// - `[u8; 12]` for ChaCha20, AES-GCM
/// - `[u8; 8]` for some legacy ciphers
type Nonce: FromBytes + IntoBytes;
/// The plaintext data type.
///
/// This type represents the unencrypted data input to the cipher.
/// It must support zero-copy operations for efficient processing.
///
/// # Performance Considerations
///
/// - Should minimize copying and allocation
/// - Consider using slices or references where possible
/// - Support both fixed-size and variable-length data
///
/// # Common Types
///
/// - `&[u8]` for read-only operations
/// - `[u8; N]` for fixed-size owned data
/// - Custom container types for variable-length data
type PlainText: FromBytes + IntoBytes;
/// The ciphertext data type.
///
/// This type represents the encrypted data output from the cipher.
/// It must support zero-copy operations for efficient processing.
///
/// # Size Considerations
///
/// - For stream ciphers: same size as plaintext
/// - For block ciphers with padding: may be larger than plaintext
/// - For AEAD modes: includes authentication tag
///
/// # Common Types
///
/// - `[u8; N]` for fixed-size encrypted blocks
/// - Custom container types for variable-length encrypted data
/// - Types that include metadata or authentication tags
type CipherText: FromBytes + IntoBytes;
}
/// Trait for initializing a cipher with a specific mode.
pub trait CipherInit<M: CipherMode>: SymmetricCipher {
/// The operational context for performing encryption/decryption.
type CipherContext<'a>: CipherOp<M>
where
Self: 'a;
/// Initializes the cipher with the given parameters.
///
/// # Parameters
///
/// - `key`: A reference to the key used for the cipher.
/// - `nonce`: A reference to the nonce or IV used for the cipher.
/// - `mode`: The cipher mode to use.
///
/// # Returns
///
/// A result containing the operational context or an error.
fn init<'a>(
&'a mut self,
key: &Self::Key,
nonce: &Self::Nonce,
mode: M,
) -> Result<Self::CipherContext<'a>, Self::Error>;
}
/// Trait for basic encryption/decryption operations.
pub trait CipherOp<M: CipherMode>: SymmetricCipher + ErrorType {
/// Encrypts the given plaintext.
///
/// # Parameters
///
/// - `plaintext`: The data to encrypt.
///
/// # Returns
///
/// A result containing the ciphertext or an error.
fn encrypt(&mut self, plaintext: Self::PlainText) -> Result<Self::CipherText, Self::Error>;
/// Decrypts the given ciphertext.
///
/// # Parameters
///
/// - `ciphertext`: The data to decrypt.
///
/// # Returns
///
/// A result containing the plaintext or an error.
fn decrypt(&mut self, ciphertext: Self::CipherText) -> Result<Self::PlainText, Self::Error>;
}
/// Optional trait for cipher contexts that support resetting to their initial state.
pub trait ResettableCipherOp: ErrorType {
/// Resets the cipher context.
///
/// # Returns
///
/// A result indicating success or failure.
fn reset(&mut self) -> Result<(), Self::Error>;
}
/// Optional trait for cipher contexts that support rekeying.
pub trait CipherRekey<K>: ErrorType {
/// Rekeys the cipher context with a new key.
///
/// # Parameters
///
/// - `new_key`: A reference to the new key.
///
/// # Returns
///
/// A result indicating success or failure.
fn rekey(&mut self, new_key: &K) -> Result<(), Self::Error>;
}
/// Error type for block-aligned container operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockAlignedError {
/// The container has reached its maximum capacity.
CapacityExceeded,
/// The input data is too large for the container.
DataTooLarge,
}
impl core::fmt::Display for BlockAlignedError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::CapacityExceeded => write!(f, "block container has reached maximum capacity"),
Self::DataTooLarge => write!(f, "input data exceeds container capacity"),
}
}
}
/// Block-aligned data container that guarantees correct block sizing at compile time.
///
/// This type wrapper ensures that data is always properly aligned to block boundaries,
/// preventing runtime errors from incorrectly sized cipher inputs. It uses fixed-size
/// arrays suitable for embedded systems without heap allocation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockAligned<const BLOCK_SIZE: usize, const MAX_BLOCKS: usize> {
blocks: [[u8; BLOCK_SIZE]; MAX_BLOCKS],
block_count: usize,
}
impl<const BLOCK_SIZE: usize, const MAX_BLOCKS: usize> Default
for BlockAligned<BLOCK_SIZE, MAX_BLOCKS>
{
fn default() -> Self {
Self::new()
}
}
impl<const BLOCK_SIZE: usize, const MAX_BLOCKS: usize> BlockAligned<BLOCK_SIZE, MAX_BLOCKS> {
/// Create a new empty block-aligned container
pub const fn new() -> Self {
Self {
blocks: [[0u8; BLOCK_SIZE]; MAX_BLOCKS],
block_count: 0,
}
}
/// Create block-aligned data from a byte slice, padding if necessary.
///
/// # Parameters
/// - `data`: Input data that will be padded to block boundaries
/// - `padding_byte`: Byte value to use for padding (typically 0)
///
/// # Returns
/// - `Ok(BlockAligned)`: Successfully created block-aligned data
/// - `Err(BlockAlignedError)`: Input data exceeds maximum capacity
///
/// # Errors
/// Returns an error if the input data would require more than `MAX_BLOCKS` blocks.
pub fn from_slice_padded(data: &[u8], padding_byte: u8) -> Result<Self, BlockAlignedError> {
let required_blocks = data.len().div_ceil(BLOCK_SIZE);
if required_blocks > MAX_BLOCKS {
return Err(BlockAlignedError::DataTooLarge);
}
let mut result = Self::new();
result.block_count = required_blocks;
// Fill complete blocks
for (i, chunk) in data.chunks(BLOCK_SIZE).enumerate() {
let block = result
.blocks
.get_mut(i)
.ok_or(BlockAlignedError::DataTooLarge)?;
block.fill(padding_byte);
let slice = block
.get_mut(..chunk.len())
.ok_or(BlockAlignedError::DataTooLarge)?;
slice.copy_from_slice(chunk);
}
Ok(result)
}
/// Add a complete block to the container.
///
/// # Parameters
/// - `block`: Block data to add
///
/// # Returns
/// - `Ok(())`: Block successfully added
/// - `Err(BlockAlignedError)`: Container is at maximum capacity
pub fn push_block(&mut self, block: [u8; BLOCK_SIZE]) -> Result<(), BlockAlignedError> {
if self.block_count >= MAX_BLOCKS {
return Err(BlockAlignedError::CapacityExceeded);
}
let block_slot = self
.blocks
.get_mut(self.block_count)
.ok_or(BlockAlignedError::CapacityExceeded)?;
*block_slot = block;
self.block_count = self.block_count.saturating_add(1);
Ok(())
}
/// Get the blocks as a slice containing only the valid blocks.
pub fn blocks(&self) -> &[[u8; BLOCK_SIZE]] {
&self.blocks[..self.block_count]
}
/// Get the total number of bytes in valid blocks.
pub const fn len(&self) -> usize {
self.block_count.saturating_mul(BLOCK_SIZE)
}
/// Check if the container is empty.
pub const fn is_empty(&self) -> bool {
self.block_count == 0
}
/// Get the number of blocks currently stored.
pub const fn block_count(&self) -> usize {
self.block_count
}
/// Get the maximum number of blocks this container can hold.
pub const fn capacity(&self) -> usize {
MAX_BLOCKS
}
/// Get a specific block by index.
pub fn get_block(&self, index: usize) -> Option<&[u8; BLOCK_SIZE]> {
if index < self.block_count {
self.blocks.get(index)
} else {
None
}
}
/// Iterate over all valid blocks.
pub fn iter_blocks(&self) -> impl Iterator<Item = &[u8; BLOCK_SIZE]> {
self.blocks[..self.block_count].iter()
}
}
/// Trait for secure cipher operations and cleanup.
///
/// This trait provides security-focused operations that are orthogonal to basic
/// cipher functionality. It enables secure state management, cleanup, and
/// zeroization without requiring full cipher operation capabilities.
///
/// # Security Operations
///
/// - Secure state clearing and zeroization
/// - Emergency cleanup procedures
/// - Security policy enforcement
/// - Sensitive data lifecycle management
///
/// # Independence from CipherOp
///
/// This trait is deliberately independent of `CipherOp` to allow:
/// - Security managers that don't perform encryption/decryption
/// - Key stores and vaults with secure cleanup
/// - Flexible composition with other cipher traits
pub trait SecureCipherOp: ErrorType {
/// Securely clear internal state and zeroize sensitive data.
///
/// This method performs a secure cleanup of all internal state, including:
/// - Zeroization of key material in memory
/// - Clearing of intermediate computation values
/// - Resetting hardware registers (for hardware implementations)
/// - Invalidating cached data or contexts
///
/// # Security Guarantees
///
/// - All sensitive data must be cryptographically erased
/// - Memory containing keys or intermediate values must be zeroized
/// - Hardware registers must be cleared if applicable
/// - The operation should be resistant to compiler optimizations
///
/// # Returns
///
/// A result indicating whether the secure cleanup was successful.
///
/// # Errors
///
/// - `HardwareFailure`: Hardware cleanup operations failed
/// - `PermissionDenied`: Insufficient privileges for secure operations
/// - `InvalidState`: Cipher is in a state that prevents secure cleanup
///
/// # Example
///
/// ```ignore
/// let mut cipher = SecureAesCipher::new();
/// // ... perform cipher operations ...
/// cipher.clear_state()?; // Secure cleanup before dropping
/// ```
fn clear_state(&mut self) -> Result<(), Self::Error>;
}
/// Trait for querying cipher status and hardware state.
///
/// This trait provides status monitoring capabilities that are useful for
/// hardware implementations, performance optimization, and error detection.
/// It's independent of cipher operations to allow status monitoring without
/// requiring operation capabilities.
///
/// # Status Monitoring
///
/// - Hardware readiness and availability
/// - Output data availability
/// - Error and alert conditions
/// - Performance and state information
///
/// # Hardware Integration
///
/// - Allows polling-based operation models
/// - Supports interrupt-driven architectures
/// - Enables efficient resource utilization
/// - Provides visibility into hardware state
pub trait CipherStatus: ErrorType {
/// Check if the cipher is ready to accept new input data.
///
/// This method indicates whether the cipher implementation can accept
/// new input for processing. For hardware implementations, this typically
/// corresponds to input buffer availability.
///
/// # Returns
///
/// - `Ok(true)`: Cipher is ready for new input
/// - `Ok(false)`: Cipher is busy and cannot accept input
/// - `Err(_)`: Error occurred while checking status
///
/// # Use Cases
///
/// - Polling loops waiting for hardware readiness
/// - Flow control in streaming operations
/// - Performance optimization by avoiding blocking calls
fn is_ready(&self) -> Result<bool, Self::Error>;
/// Check if processed output data is available for reading.
///
/// This method indicates whether the cipher has completed processing
/// and has output data available. For hardware implementations, this
/// typically corresponds to output buffer status.
///
/// # Returns
///
/// - `Ok(true)`: Output data is available
/// - `Ok(false)`: No output data is currently available
/// - `Err(_)`: Error occurred while checking status
///
/// # Use Cases
///
/// - Polling for completion of asynchronous operations
/// - Avoiding blocking reads when no data is available
/// - Implementing efficient producer-consumer patterns
fn has_output(&self) -> Result<bool, Self::Error>;
/// Check if the cipher is idle and available for new operations.
///
/// This method indicates whether the cipher is in an idle state and
/// can be used for new operations. This is useful for determining
/// when to start new transactions or perform maintenance operations.
///
/// # Returns
///
/// - `Ok(true)`: Cipher is idle and available
/// - `Ok(false)`: Cipher is busy with ongoing operations
/// - `Err(_)`: Error occurred while checking status
///
/// # Use Cases
///
/// - Determining when to begin new cipher transactions
/// - Resource management and scheduling
/// - Power management decisions
/// - Maintenance and diagnostic operations
fn is_idle(&self) -> Result<bool, Self::Error>;
}
/// Trait for AEAD (Authenticated Encryption with Associated Data) operations.
///
/// This trait extends symmetric cipher operations to provide authenticated encryption,
/// which combines confidentiality (encryption) with authenticity and integrity
/// (authentication). AEAD modes like AES-GCM and ChaCha20-Poly1305 are the
/// recommended approach for modern cryptographic applications.
///
/// # AEAD Benefits
///
/// - **Confidentiality**: Data is encrypted and unreadable without the key
/// - **Authenticity**: Verifies the data came from the expected sender
/// - **Integrity**: Detects any tampering or corruption of the data
/// - **Associated Data**: Can authenticate additional data without encrypting it
///
/// # Security Guarantees
///
/// - Prevents chosen-ciphertext attacks
/// - Provides semantic security
/// - Detects message tampering
/// - Supports additional authenticated data (AAD) that remains in plaintext
///
/// # Common Algorithms
///
/// - **AES-GCM**: High performance, hardware acceleration available
/// - **ChaCha20-Poly1305**: Software-friendly, constant-time implementation
/// - **AES-CCM**: Suited for resource-constrained environments
///
/// # Example Usage
///
/// ```ignore
/// // Encrypt with associated data
/// let plaintext = b"secret message";
/// let aad = b"public header info";
/// let (ciphertext, tag) = cipher.encrypt_aead(plaintext, aad)?;
///
/// // Decrypt and verify
/// let decrypted = cipher.decrypt_aead(ciphertext, aad, tag)?;
/// ```
pub trait AeadCipherOp: SymmetricCipher + ErrorType {
/// The associated data type for AEAD operations.
///
/// Associated data (AAD) is additional information that is authenticated
/// but not encrypted. It provides integrity protection for data that must
/// remain in plaintext, such as packet headers or metadata.
///
/// # Security Properties
///
/// - **Authenticated but not encrypted**: AAD is included in authentication tag calculation
/// - **Integrity protected**: Any modification to AAD will cause decryption to fail
/// - **No confidentiality**: AAD remains visible in plaintext
///
/// # Use Cases
///
/// - Network packet headers that must be readable by intermediary devices
/// - File metadata that must remain accessible
/// - Protocol version information
/// - Sequence numbers or timestamps
///
/// # Common Types
///
/// - `&[u8]` for read-only associated data
/// - `[u8; N]` for fixed-size owned associated data
/// - `()` or empty slice if no associated data is needed
type AssociatedData: FromBytes + IntoBytes;
/// The authentication tag type for AEAD operations.
///
/// The authentication tag is a cryptographic checksum that provides
/// integrity and authenticity verification for both the ciphertext
/// and associated data.
///
/// # Security Properties
///
/// - **Unforgeable**: Cannot be created without the secret key
/// - **Tamper-evident**: Any modification to protected data changes the tag
/// - **Algorithm-specific size**: Fixed size determined by the AEAD mode
///
/// # Tag Sizes
///
/// - **AES-GCM**: 16 bytes (128 bits) recommended, can be truncated
/// - **ChaCha20-Poly1305**: 16 bytes (128 bits) fixed
/// - **AES-CCM**: Variable (4, 6, 8, 10, 12, 14, or 16 bytes)
///
/// # Security Warning
///
/// Tags must be compared in constant time to prevent timing attacks.
/// Use cryptographic comparison functions, not standard equality operators.
///
/// # Common Types
///
/// - `[u8; 16]` for most AEAD modes
/// - `[u8; N]` for variable-length tags
/// - Custom types that include metadata
type Tag: FromBytes + IntoBytes;
/// Encrypts the given plaintext with associated data.
///
/// # Parameters
///
/// - `plaintext`: The data to encrypt.
/// - `associated_data`: The associated data to authenticate.
///
/// # Returns
///
/// A result containing the ciphertext and authentication tag or an error.
fn encrypt_aead(
&mut self,
plaintext: Self::PlainText,
associated_data: Self::AssociatedData,
) -> Result<(Self::CipherText, Self::Tag), Self::Error>;
/// Decrypts the given ciphertext with associated data and authentication tag.
///
/// # Parameters
///
/// - `ciphertext`: The data to decrypt.
/// - `associated_data`: The associated data to authenticate.
/// - `tag`: The authentication tag.
///
/// # Returns
///
/// A result containing the plaintext or an error.
fn decrypt_aead(
&mut self,
ciphertext: Self::CipherText,
associated_data: Self::AssociatedData,
tag: Self::Tag,
) -> Result<Self::PlainText, Self::Error>;
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] // Allow unwrap in tests for cleaner test code
mod tests {
use super::*;
#[test]
fn test_block_aligned_creation() {
let container = BlockAligned::<16, 4>::new();
assert_eq!(container.block_count(), 0);
assert_eq!(container.capacity(), 4);
assert_eq!(container.len(), 0);
assert!(container.is_empty());
}
#[test]
fn test_block_aligned_default() {
let container: BlockAligned<16, 4> = Default::default();
assert_eq!(container.block_count(), 0);
assert_eq!(container.capacity(), 4);
assert!(container.is_empty());
}
#[test]
fn test_push_block_success() {
let mut container = BlockAligned::<16, 4>::new();
let block1 = [0x42u8; 16];
let result = container.push_block(block1);
assert!(result.is_ok());
assert_eq!(container.block_count(), 1);
assert_eq!(container.len(), 16);
assert!(!container.is_empty());
let block2 = [0x33u8; 16];
let result = container.push_block(block2);
assert!(result.is_ok());
assert_eq!(container.block_count(), 2);
assert_eq!(container.len(), 32);
}
#[test]
fn test_push_block_capacity_exceeded() {
let mut container = BlockAligned::<16, 2>::new();
// Fill to capacity
container.push_block([0x01u8; 16]).unwrap();
container.push_block([0x02u8; 16]).unwrap();
assert_eq!(container.block_count(), 2);
// Try to exceed capacity
let result = container.push_block([0x03u8; 16]);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), BlockAlignedError::CapacityExceeded);
assert_eq!(container.block_count(), 2); // Should remain unchanged
}
#[test]
fn test_get_block() {
let mut container = BlockAligned::<16, 4>::new();
let block1 = [0x42u8; 16];
let block2 = [0x33u8; 16];
container.push_block(block1).unwrap();
container.push_block(block2).unwrap();
// Test valid indices
assert_eq!(container.get_block(0), Some(&block1));
assert_eq!(container.get_block(1), Some(&block2));
// Test invalid indices
assert_eq!(container.get_block(2), None);
assert_eq!(container.get_block(100), None);
}
#[test]
fn test_blocks_slice() {
let mut container = BlockAligned::<16, 4>::new();
let block1 = [0x42u8; 16];
let block2 = [0x33u8; 16];
container.push_block(block1).unwrap();
container.push_block(block2).unwrap();
let blocks = container.blocks();
assert_eq!(blocks.len(), 2);
assert_eq!(blocks.first().unwrap(), &block1);
assert_eq!(blocks.get(1).unwrap(), &block2);
}
#[test]
fn test_iter_blocks() {
let mut container = BlockAligned::<16, 4>::new();
let block1 = [0x42u8; 16];
let block2 = [0x33u8; 16];
let block3 = [0x11u8; 16];
container.push_block(block1).unwrap();
container.push_block(block2).unwrap();
container.push_block(block3).unwrap();
// Test iterator manually
let mut iter = container.iter_blocks();
assert_eq!(iter.next(), Some(&block1));
assert_eq!(iter.next(), Some(&block2));
assert_eq!(iter.next(), Some(&block3));
assert_eq!(iter.next(), None);
// Test that iterator only includes valid blocks
let empty_container = BlockAligned::<16, 4>::new();
let mut empty_iter = empty_container.iter_blocks();
assert_eq!(empty_iter.next(), None);
}
#[test]
fn test_from_slice_padded_exact_fit() {
// Test data that exactly fits one block
let data = [0x42u8; 16];
let container = BlockAligned::<16, 4>::from_slice_padded(&data, 0x00).unwrap();
assert_eq!(container.block_count(), 1);
assert_eq!(container.get_block(0), Some(&data));
}
#[test]
fn test_from_slice_padded_partial_block() {
// Test data that requires padding
let data = b"Hello, World!"; // 13 bytes
let container = BlockAligned::<16, 4>::from_slice_padded(data, 0x00).unwrap();
assert_eq!(container.block_count(), 1);
let block = container.get_block(0).unwrap();
// First 13 bytes should match input
assert_eq!(&block[..13], data);
// Last 3 bytes should be padding
assert_eq!(&block[13..], &[0x00; 3]);
}
#[test]
fn test_from_slice_padded_multiple_blocks() {
// Test data that spans multiple blocks
let data = [0x42u8; 33]; // 33 bytes = 3 blocks (16 + 16 + 1)
let container = BlockAligned::<16, 4>::from_slice_padded(&data, 0xFF).unwrap();
assert_eq!(container.block_count(), 3);
// First two blocks should be all 0x42
assert_eq!(container.get_block(0), Some(&[0x42u8; 16]));
assert_eq!(container.get_block(1), Some(&[0x42u8; 16]));
// Third block should have one byte of data and 15 bytes of padding
let third_block = container.get_block(2).unwrap();
assert_eq!(third_block.first().unwrap(), &0x42);
assert_eq!(&third_block[1..], &[0xFF; 15]);
}
#[test]
fn test_from_slice_padded_empty_data() {
let data = &[];
let container = BlockAligned::<16, 4>::from_slice_padded(data, 0x00).unwrap();
assert_eq!(container.block_count(), 0);
assert!(container.is_empty());
}
#[test]
fn test_from_slice_padded_data_too_large() {
// Test data that exceeds capacity
let data = [0x42u8; 100]; // 100 bytes = 7 blocks (16*6 + 4), but capacity is only 4
let result = BlockAligned::<16, 4>::from_slice_padded(&data, 0x00);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), BlockAlignedError::DataTooLarge);
}
#[test]
fn test_from_slice_padded_different_padding() {
let data = b"test"; // 4 bytes
let container = BlockAligned::<8, 2>::from_slice_padded(data, 0xAA).unwrap();
assert_eq!(container.block_count(), 1);
let block = container.get_block(0).unwrap();
assert_eq!(&block[..4], data);
assert_eq!(&block[4..], &[0xAA; 4]);
}
#[test]
fn test_different_block_sizes() {
// Test with 8-byte blocks
let mut container8 = BlockAligned::<8, 4>::new();
container8.push_block([0x11u8; 8]).unwrap();
assert_eq!(container8.len(), 8);
// Test with 32-byte blocks
let mut container32 = BlockAligned::<32, 2>::new();
container32.push_block([0x22u8; 32]).unwrap();
assert_eq!(container32.len(), 32);
// Test with 1-byte blocks
let mut container1 = BlockAligned::<1, 16>::new();
container1.push_block([0x33]).unwrap();
assert_eq!(container1.len(), 1);
}
#[test]
fn test_clone_and_equality() {
let mut container1 = BlockAligned::<16, 4>::new();
let block = [0x42u8; 16];
container1.push_block(block).unwrap();
let container2 = container1.clone();
assert_eq!(container1, container2);
assert_eq!(container1.block_count(), container2.block_count());
assert_eq!(container1.get_block(0), container2.get_block(0));
// Test inequality
let mut container3 = BlockAligned::<16, 4>::new();
container3.push_block([0x33u8; 16]).unwrap();
assert_ne!(container1, container3);
}
#[test]
fn test_edge_cases() {
// Test with maximum capacity
let mut container = BlockAligned::<1, 8>::new();
for i in 0..8 {
container.push_block([i as u8]).unwrap();
}
assert_eq!(container.block_count(), 8);
assert_eq!(container.len(), 8);
// Verify all blocks are correct
for i in 0..8 {
assert_eq!(container.get_block(i), Some(&[i as u8]));
}
}
#[test]
fn test_error_display() {
let capacity_error = BlockAlignedError::CapacityExceeded;
let data_error = BlockAlignedError::DataTooLarge;
// Test that the errors are created correctly
assert_eq!(capacity_error, BlockAlignedError::CapacityExceeded);
assert_eq!(data_error, BlockAlignedError::DataTooLarge);
// Test that they are not equal to each other
assert_ne!(capacity_error, data_error);
}
#[test]
fn test_debug_formatting() {
let mut container = BlockAligned::<4, 2>::new();
container.push_block([1, 2, 3, 4]).unwrap();
// Test that the container was created successfully
assert_eq!(container.block_count(), 1);
assert_eq!(container.get_block(0), Some(&[1, 2, 3, 4]));
}
}