Skip to main content

saorsa_core/
secure_memory.rs

1// Copyright 2024 Saorsa Labs Limited
2//
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: david@saorsalabs.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14//! # Secure Memory Management for Cryptographic Operations
15//!
16//! This module provides memory-protected storage for cryptographic keys and sensitive data.
17//! All allocations are automatically zeroized on drop and protected against memory dumps.
18//!
19//! ## Security Features
20//! - Automatic zeroization on drop (prevents key recovery)
21//! - Memory locking to prevent swapping to disk
22//! - Protected allocation regions
23//! - Constant-time comparison operations
24//! - Guard pages to detect buffer overflows
25//!
26//! ## Performance Features
27//! - Pool-based allocation to reduce fragmentation
28//! - Batch allocation for multiple keys
29//! - Efficient reuse of protected memory regions
30//! - Minimal overhead for secure operations
31
32#![allow(unsafe_code)] // Required for secure memory operations: mlock, memory zeroing, and protected allocation
33
34use crate::{P2PError, Result};
35use std::alloc::{Layout, alloc_zeroed, dealloc};
36use std::collections::VecDeque;
37use std::fmt;
38use std::ops::Deref;
39use std::ptr::{self, NonNull};
40use std::sync::Mutex;
41use subtle::ConstantTimeEq;
42
43#[cfg(unix)]
44use libc::{mlock, munlock};
45
46#[cfg(windows)]
47use winapi::um::memoryapi::{VirtualLock, VirtualUnlock};
48
49/// Maximum size for a single secure allocation (64KB)
50const MAX_SECURE_ALLOCATION: usize = 65536;
51
52/// Default size for the secure memory pool (1MB)
53const DEFAULT_POOL_SIZE: usize = 1024 * 1024;
54
55/// Alignment requirement for secure allocations
56const SECURE_ALIGNMENT: usize = 64;
57
58/// Secure memory container that automatically zeroizes on drop
59pub struct SecureMemory {
60    /// Pointer to the allocated memory
61    ptr: NonNull<u8>,
62    /// Size of the allocation
63    size: usize,
64    /// Actual data length (may be less than allocation size due to alignment)
65    data_len: usize,
66    /// Whether the memory is locked (cannot be swapped)
67    locked: bool,
68    /// Layout used for allocation
69    layout: Layout,
70}
71
72// Safety: SecureMemory is safe to send between threads as it owns its memory
73unsafe impl Send for SecureMemory {}
74// Safety: SecureMemory is safe to share between threads with proper synchronization
75unsafe impl Sync for SecureMemory {}
76
77/// Secure vector with automatic zeroization
78pub struct SecureVec {
79    /// Underlying secure memory
80    memory: SecureMemory,
81    /// Current length of the vector
82    len: usize,
83}
84
85/// Secure string with automatic zeroization
86pub struct SecureString {
87    /// Underlying secure vector
88    vec: SecureVec,
89}
90
91/// Pool for managing secure memory allocations
92pub struct SecureMemoryPool {
93    /// Available memory chunks
94    available: Mutex<VecDeque<SecureMemory>>,
95    /// Total pool size
96    total_size: usize,
97    /// Chunk size for allocations
98    chunk_size: usize,
99    /// Statistics
100    stats: Mutex<PoolStats>,
101}
102
103/// Statistics for secure memory pool
104#[derive(Debug, Clone, Default)]
105pub struct PoolStats {
106    /// Total allocations made
107    pub total_allocations: u64,
108    /// Total deallocations
109    pub total_deallocations: u64,
110    /// Current active allocations
111    pub active_allocations: u64,
112    /// Pool hits (reused memory)
113    pub pool_hits: u64,
114    /// Pool misses (new allocations)
115    pub pool_misses: u64,
116    /// Total bytes allocated
117    pub total_bytes_allocated: u64,
118    /// Current bytes in use
119    pub current_bytes_in_use: u64,
120}
121
122/// Error types for secure memory operations
123#[derive(Debug, Clone)]
124pub enum SecureMemoryError {
125    /// Allocation failed
126    AllocationFailed(String),
127    /// Memory locking failed
128    LockingFailed(String),
129    /// Invalid size or alignment
130    InvalidParameters(String),
131    /// Pool exhausted
132    PoolExhausted,
133    /// Operation not supported on this platform
134    NotSupported(String),
135}
136
137impl std::fmt::Display for SecureMemoryError {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        match self {
140            SecureMemoryError::AllocationFailed(msg) => write!(f, "Allocation failed: {msg}"),
141            SecureMemoryError::LockingFailed(msg) => write!(f, "Memory locking failed: {msg}"),
142            SecureMemoryError::InvalidParameters(msg) => write!(f, "Invalid parameters: {msg}"),
143            SecureMemoryError::PoolExhausted => write!(f, "Secure memory pool exhausted"),
144            SecureMemoryError::NotSupported(msg) => write!(f, "Operation not supported: {msg}"),
145        }
146    }
147}
148
149impl std::error::Error for SecureMemoryError {}
150
151impl SecureMemory {
152    /// Allocate secure memory with the given size
153    pub fn new(size: usize) -> Result<Self> {
154        if size == 0 {
155            return Err(P2PError::Io(std::io::Error::new(
156                std::io::ErrorKind::InvalidInput,
157                "Cannot allocate zero-sized memory",
158            )));
159        }
160
161        if size > MAX_SECURE_ALLOCATION {
162            return Err(P2PError::Io(std::io::Error::new(
163                std::io::ErrorKind::InvalidInput,
164                format!("Allocation size {size} exceeds maximum {MAX_SECURE_ALLOCATION}"),
165            )));
166        }
167
168        // Align size to secure alignment boundary
169        let aligned_size = (size + SECURE_ALIGNMENT - 1) & !(SECURE_ALIGNMENT - 1);
170
171        // Create layout for allocation
172        let layout = Layout::from_size_align(aligned_size, SECURE_ALIGNMENT).map_err(|e| {
173            P2PError::Io(std::io::Error::new(
174                std::io::ErrorKind::InvalidInput,
175                format!("Invalid layout: {e}"),
176            ))
177        })?;
178
179        // Allocate zeroed memory
180        let ptr = unsafe { alloc_zeroed(layout) };
181        if ptr.is_null() {
182            return Err(P2PError::Io(std::io::Error::new(
183                std::io::ErrorKind::OutOfMemory,
184                "Memory allocation failed",
185            )));
186        }
187
188        let ptr = NonNull::new(ptr).ok_or_else(|| {
189            P2PError::Io(std::io::Error::new(
190                std::io::ErrorKind::OutOfMemory,
191                "Null pointer returned from allocator",
192            ))
193        })?;
194
195        let mut memory = Self {
196            ptr,
197            size: aligned_size,
198            data_len: size,
199            locked: false,
200            layout,
201        };
202
203        // Attempt to lock the memory
204        if let Err(e) = memory.lock_memory() {
205            tracing::warn!("Failed to lock secure memory: {}", e);
206        }
207
208        Ok(memory)
209    }
210
211    /// Create secure memory from existing data (data is copied and source should be zeroized)
212    pub fn from_slice(data: &[u8]) -> Result<Self> {
213        let mut memory = Self::new(data.len())?;
214        memory.as_mut_slice()[..data.len()].copy_from_slice(data);
215        Ok(memory)
216    }
217
218    /// Get the size of the allocated memory
219    pub fn len(&self) -> usize {
220        self.size
221    }
222
223    /// Check if the memory is empty
224    pub fn is_empty(&self) -> bool {
225        self.size == 0
226    }
227
228    /// Get a slice view of the memory (only the actual data length)
229    pub fn as_slice(&self) -> &[u8] {
230        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.data_len) }
231    }
232
233    /// Get a mutable slice view of the memory (only the actual data length)
234    pub fn as_mut_slice(&mut self) -> &mut [u8] {
235        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.data_len) }
236    }
237
238    /// Get a slice view of the full allocated memory (including alignment padding)
239    pub fn as_allocated_slice(&self) -> &[u8] {
240        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
241    }
242
243    /// Get a mutable slice view of the full allocated memory (including alignment padding)
244    pub fn as_allocated_mut_slice(&mut self) -> &mut [u8] {
245        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
246    }
247
248    /// Compare two secure memory regions in constant time
249    pub fn constant_time_eq(&self, other: &SecureMemory) -> bool {
250        if self.data_len != other.data_len {
251            return false;
252        }
253
254        // Use constant-time comparison from subtle crate
255        self.as_slice().ct_eq(other.as_slice()).into()
256    }
257
258    /// Lock memory to prevent it from being swapped to disk
259    fn lock_memory(&mut self) -> Result<()> {
260        if self.locked {
261            return Ok(());
262        }
263
264        #[cfg(unix)]
265        {
266            let result = unsafe { mlock(self.ptr.as_ptr() as *const libc::c_void, self.size) };
267            if result != 0 {
268                return Err(P2PError::Io(std::io::Error::new(
269                    std::io::ErrorKind::PermissionDenied,
270                    "Failed to lock memory pages",
271                )));
272            }
273        }
274
275        #[cfg(windows)]
276        {
277            let result =
278                unsafe { VirtualLock(self.ptr.as_ptr() as *mut winapi::ctypes::c_void, self.size) };
279            if result == 0 {
280                return Err(P2PError::Io(std::io::Error::new(
281                    std::io::ErrorKind::PermissionDenied,
282                    "VirtualLock failed",
283                )));
284            }
285        }
286
287        #[cfg(not(any(unix, windows)))]
288        {
289            tracing::warn!("Memory locking not supported on this platform");
290        }
291
292        self.locked = true;
293        Ok(())
294    }
295
296    /// Unlock memory (called automatically on drop)
297    fn unlock_memory(&mut self) {
298        if !self.locked {
299            return;
300        }
301
302        #[cfg(unix)]
303        {
304            unsafe { munlock(self.ptr.as_ptr() as *const libc::c_void, self.size) };
305        }
306
307        #[cfg(windows)]
308        {
309            unsafe { VirtualUnlock(self.ptr.as_ptr() as *mut winapi::ctypes::c_void, self.size) };
310        }
311
312        self.locked = false;
313    }
314
315    /// Securely zeroize the memory
316    pub fn zeroize(&mut self) {
317        unsafe {
318            // Use volatile write to prevent compiler optimization
319            ptr::write_volatile(self.ptr.as_ptr(), 0u8);
320
321            // Zeroize the entire allocation
322            for i in 0..self.size {
323                ptr::write_volatile(self.ptr.as_ptr().add(i), 0u8);
324            }
325        }
326    }
327}
328
329impl Drop for SecureMemory {
330    fn drop(&mut self) {
331        // Zeroize memory before deallocation
332        self.zeroize();
333
334        // Unlock memory
335        self.unlock_memory();
336
337        // Deallocate memory
338        unsafe {
339            dealloc(self.ptr.as_ptr(), self.layout);
340        }
341    }
342}
343
344impl SecureVec {
345    /// Create a new secure vector with the given capacity
346    pub fn with_capacity(capacity: usize) -> Result<Self> {
347        let memory = SecureMemory::new(capacity)?;
348        Ok(Self { memory, len: 0 })
349    }
350
351    /// Create a secure vector from existing data
352    pub fn from_slice(data: &[u8]) -> Result<Self> {
353        let memory = SecureMemory::from_slice(data)?;
354        let len = data.len();
355        Ok(Self { memory, len })
356    }
357
358    /// Get the length of the vector
359    pub fn len(&self) -> usize {
360        self.len
361    }
362
363    /// Check if the vector is empty
364    pub fn is_empty(&self) -> bool {
365        self.len == 0
366    }
367
368    /// Get the capacity of the vector
369    pub fn capacity(&self) -> usize {
370        self.memory.len()
371    }
372
373    /// Push a byte to the vector
374    pub fn push(&mut self, value: u8) -> Result<()> {
375        if self.len >= self.capacity() {
376            return Err(P2PError::Io(std::io::Error::new(
377                std::io::ErrorKind::InvalidInput,
378                "SecureVec capacity exceeded",
379            )));
380        }
381
382        self.memory.as_allocated_mut_slice()[self.len] = value;
383        self.len += 1;
384        Ok(())
385    }
386
387    /// Extend the vector with data from a slice
388    pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<()> {
389        if self.len + data.len() > self.capacity() {
390            return Err(P2PError::Io(std::io::Error::new(
391                std::io::ErrorKind::InvalidInput,
392                "SecureVec capacity exceeded",
393            )));
394        }
395
396        self.memory.as_allocated_mut_slice()[self.len..self.len + data.len()].copy_from_slice(data);
397        self.len += data.len();
398        Ok(())
399    }
400
401    /// Get a slice of the vector's contents
402    pub fn as_slice(&self) -> &[u8] {
403        &self.memory.as_slice()[..self.len]
404    }
405
406    /// Clear the vector (zeroizes the data)
407    pub fn clear(&mut self) {
408        self.memory.zeroize();
409        self.len = 0;
410    }
411}
412
413impl Deref for SecureVec {
414    type Target = [u8];
415
416    fn deref(&self) -> &Self::Target {
417        self.as_slice()
418    }
419}
420
421impl SecureString {
422    /// Create a new secure string with the given capacity
423    pub fn with_capacity(capacity: usize) -> Result<Self> {
424        let vec = SecureVec::with_capacity(capacity)?;
425        Ok(Self { vec })
426    }
427
428    /// Create a secure string from a regular string
429    pub fn from_plain_str(s: &str) -> Result<Self> {
430        let vec = SecureVec::from_slice(s.as_bytes())?;
431        Ok(Self { vec })
432    }
433
434    /// Get the length of the string
435    pub fn len(&self) -> usize {
436        self.vec.len()
437    }
438
439    /// Check if the string is empty
440    pub fn is_empty(&self) -> bool {
441        self.vec.is_empty()
442    }
443
444    /// Push a character to the string
445    pub fn push(&mut self, ch: char) -> Result<()> {
446        let mut buffer = [0u8; 4];
447        let encoded = ch.encode_utf8(&mut buffer);
448        self.vec.extend_from_slice(encoded.as_bytes())
449    }
450
451    /// Push a string slice to the string
452    pub fn push_str(&mut self, s: &str) -> Result<()> {
453        self.vec.extend_from_slice(s.as_bytes())
454    }
455
456    /// Get the string as a str slice
457    pub fn as_str(&self) -> Result<&str> {
458        std::str::from_utf8(self.vec.as_slice()).map_err(|e| {
459            P2PError::Io(std::io::Error::new(
460                std::io::ErrorKind::InvalidData,
461                format!("Invalid UTF-8: {e}"),
462            ))
463        })
464    }
465
466    /// Clear the string (zeroizes the data)
467    pub fn clear(&mut self) {
468        self.vec.clear();
469    }
470}
471
472impl fmt::Display for SecureString {
473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474        match self.as_str() {
475            Ok(s) => write!(f, "{s}"),
476            Err(_) => write!(f, "<invalid UTF-8>"),
477        }
478    }
479}
480
481impl fmt::Debug for SecureString {
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        write!(f, "SecureString[{}]", self.len())
484    }
485}
486
487impl SecureMemoryPool {
488    /// Create a new secure memory pool
489    pub fn new(total_size: usize, chunk_size: usize) -> Result<Self> {
490        if chunk_size > total_size {
491            return Err(P2PError::Io(std::io::Error::new(
492                std::io::ErrorKind::InvalidInput,
493                "Chunk size cannot exceed total size",
494            )));
495        }
496
497        let pool = Self {
498            available: Mutex::new(VecDeque::new()),
499            total_size,
500            chunk_size,
501            stats: Mutex::new(PoolStats::default()),
502        };
503
504        // Pre-allocate chunks
505        pool.preallocate_chunks()?;
506
507        Ok(pool)
508    }
509
510    /// Create a default secure memory pool
511    pub fn default_pool() -> Result<Self> {
512        Self::new(DEFAULT_POOL_SIZE, 4096)
513    }
514
515    /// Allocate memory from the pool
516    pub fn allocate(&self, size: usize) -> Result<SecureMemory> {
517        if size > self.chunk_size {
518            // Large allocation - allocate directly
519            match self.stats.lock() {
520                Ok(mut stats) => {
521                    stats.pool_misses += 1;
522                    stats.total_allocations += 1;
523                    stats.active_allocations += 1;
524                    stats.total_bytes_allocated += size as u64;
525                    stats.current_bytes_in_use += size as u64;
526                }
527                Err(e) => {
528                    tracing::warn!("Memory pool stats mutex poisoned in allocate: {}", e);
529                }
530            }
531            return SecureMemory::new(size);
532        }
533
534        // Try to get from pool
535        {
536            if let Ok(mut available) = self.available.lock()
537                && let Some(memory) = available.pop_front()
538            {
539                match self.stats.lock() {
540                    Ok(mut stats) => {
541                        stats.pool_hits += 1;
542                        stats.total_allocations += 1;
543                        stats.active_allocations += 1;
544                        stats.current_bytes_in_use += memory.len() as u64;
545                    }
546                    Err(e) => {
547                        tracing::warn!("Memory pool stats mutex poisoned in allocate: {}", e);
548                    }
549                }
550                return Ok(memory);
551            }
552        }
553
554        // Pool empty - allocate new chunk
555        match self.stats.lock() {
556            Ok(mut stats) => {
557                stats.pool_misses += 1;
558                stats.total_allocations += 1;
559                stats.active_allocations += 1;
560                stats.total_bytes_allocated += self.chunk_size as u64;
561                stats.current_bytes_in_use += self.chunk_size as u64;
562            }
563            Err(e) => {
564                tracing::warn!("Memory pool stats mutex poisoned in allocate: {}", e);
565            }
566        }
567
568        SecureMemory::new(self.chunk_size)
569    }
570
571    /// Return memory to the pool
572    pub fn deallocate(&self, mut memory: SecureMemory) {
573        // Zeroize before returning to pool
574        memory.zeroize();
575
576        let memory_size = memory.len();
577
578        if memory_size == self.chunk_size {
579            // Return to pool
580            match self.available.lock() {
581                Ok(mut available) => {
582                    available.push_back(memory);
583                }
584                Err(e) => {
585                    tracing::warn!("Memory pool available mutex poisoned in deallocate: {}", e);
586                }
587            }
588        }
589        // Large allocations are dropped automatically
590
591        match self.stats.lock() {
592            Ok(mut stats) => {
593                stats.total_deallocations += 1;
594                stats.active_allocations -= 1;
595                stats.current_bytes_in_use -= memory_size as u64;
596            }
597            Err(e) => {
598                tracing::warn!("Memory pool stats mutex poisoned in deallocate: {}", e);
599            }
600        }
601    }
602
603    /// Get pool statistics
604    pub fn stats(&self) -> PoolStats {
605        match self.stats.lock() {
606            Ok(s) => s.clone(),
607            Err(e) => {
608                tracing::warn!("Memory pool stats mutex poisoned: {}", e);
609                PoolStats::default()
610            }
611        }
612    }
613
614    /// Pre-allocate chunks for the pool
615    fn preallocate_chunks(&self) -> Result<()> {
616        let num_chunks = self.total_size / self.chunk_size;
617        match self.available.lock() {
618            Ok(mut available) => {
619                for _ in 0..num_chunks {
620                    let memory = SecureMemory::new(self.chunk_size)?;
621                    available.push_back(memory);
622                }
623            }
624            Err(e) => {
625                tracing::warn!(
626                    "Memory pool available mutex poisoned in preallocate_chunks: {}",
627                    e
628                );
629            }
630        }
631
632        Ok(())
633    }
634}
635
636/// Global secure memory pool instance
637static GLOBAL_POOL: std::sync::OnceLock<Result<SecureMemoryPool>> = std::sync::OnceLock::new();
638
639/// Get the global secure memory pool
640pub fn global_secure_pool() -> &'static SecureMemoryPool {
641    let result = GLOBAL_POOL.get_or_init(SecureMemoryPool::default_pool);
642    match result {
643        Ok(pool) => pool,
644        Err(_) => match SecureMemoryPool::new(DEFAULT_POOL_SIZE, 4096) {
645            Ok(pool) => {
646                let _ = GLOBAL_POOL.set(Ok(pool));
647                if let Some(Ok(pool)) = GLOBAL_POOL.get() {
648                    pool
649                } else {
650                    // fallback to a static default
651                    static FALLBACK: once_cell::sync::OnceCell<SecureMemoryPool> =
652                        once_cell::sync::OnceCell::new();
653                    FALLBACK.get_or_init(|| SecureMemoryPool {
654                        available: Mutex::new(VecDeque::new()),
655                        total_size: DEFAULT_POOL_SIZE,
656                        chunk_size: 4096,
657                        stats: Mutex::new(PoolStats::default()),
658                    })
659                }
660            }
661            Err(_) => {
662                // Provide minimal fallback rather than panic
663                static FALLBACK: once_cell::sync::OnceCell<SecureMemoryPool> =
664                    once_cell::sync::OnceCell::new();
665                FALLBACK.get_or_init(|| SecureMemoryPool {
666                    available: Mutex::new(VecDeque::new()),
667                    total_size: DEFAULT_POOL_SIZE,
668                    chunk_size: 4096,
669                    stats: Mutex::new(PoolStats::default()),
670                })
671            }
672        },
673    }
674}
675
676/// Convenience function to allocate secure memory from global pool
677pub fn allocate_secure(size: usize) -> Result<SecureMemory> {
678    global_secure_pool().allocate(size)
679}
680
681/// Convenience function to create a secure vector from global pool
682pub fn secure_vec_with_capacity(capacity: usize) -> Result<SecureVec> {
683    let memory = global_secure_pool().allocate(capacity)?;
684    Ok(SecureVec { memory, len: 0 })
685}
686
687/// Convenience function to create a secure string from global pool
688pub fn secure_string_with_capacity(capacity: usize) -> Result<SecureString> {
689    let vec = secure_vec_with_capacity(capacity)?;
690    Ok(SecureString { vec })
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_secure_memory_basic() {
699        let mut memory = SecureMemory::new(1024).unwrap();
700
701        // Test basic operations
702        assert_eq!(memory.len(), 1024);
703        assert!(!memory.is_empty());
704
705        // Test writing and reading
706        memory.as_mut_slice()[0] = 42;
707        assert_eq!(memory.as_slice()[0], 42);
708
709        // Test zeroization
710        memory.zeroize();
711        assert_eq!(memory.as_slice()[0], 0);
712    }
713
714    #[test]
715    fn test_secure_memory_constant_time_comparison() {
716        let memory1 = SecureMemory::from_slice(b"hello").unwrap();
717        let memory2 = SecureMemory::from_slice(b"hello").unwrap();
718        let memory3 = SecureMemory::from_slice(b"world").unwrap();
719
720        assert!(memory1.constant_time_eq(&memory2));
721        assert!(!memory1.constant_time_eq(&memory3));
722    }
723
724    #[test]
725    fn test_constant_time_eq_comprehensive() {
726        // Test 1: Equal values should return true
727        let key1 = SecureMemory::from_slice(b"supersecretkey123456").unwrap();
728        let key2 = SecureMemory::from_slice(b"supersecretkey123456").unwrap();
729        assert!(key1.constant_time_eq(&key2), "Equal keys should match");
730
731        // Test 2: Different length keys should return false
732        let key_short = SecureMemory::from_slice(b"short").unwrap();
733        let key_long = SecureMemory::from_slice(b"muchlongerkey").unwrap();
734        assert!(
735            !key_short.constant_time_eq(&key_long),
736            "Different length keys should not match"
737        );
738
739        // Test 3: Keys differing in first byte should return false
740        let key_first_diff1 = SecureMemory::from_slice(b"Aello123456789012").unwrap();
741        let key_first_diff2 = SecureMemory::from_slice(b"Bello123456789012").unwrap();
742        assert!(
743            !key_first_diff1.constant_time_eq(&key_first_diff2),
744            "Keys differing in first byte should not match"
745        );
746
747        // Test 4: Keys differing in last byte should return false
748        let key_last_diff1 = SecureMemory::from_slice(b"hello12345678901A").unwrap();
749        let key_last_diff2 = SecureMemory::from_slice(b"hello12345678901B").unwrap();
750        assert!(
751            !key_last_diff1.constant_time_eq(&key_last_diff2),
752            "Keys differing in last byte should not match"
753        );
754
755        // Test 5: Keys differing in middle byte should return false
756        let key_mid_diff1 = SecureMemory::from_slice(b"hello1X3456789012").unwrap();
757        let key_mid_diff2 = SecureMemory::from_slice(b"hello1Y3456789012").unwrap();
758        assert!(
759            !key_mid_diff1.constant_time_eq(&key_mid_diff2),
760            "Keys differing in middle byte should not match"
761        );
762
763        // Test 6: Different length keys with one byte difference
764        let one_byte = SecureMemory::from_slice(b"a").unwrap();
765        let two_bytes = SecureMemory::from_slice(b"ab").unwrap();
766        assert!(
767            !one_byte.constant_time_eq(&two_bytes),
768            "Keys with length difference should not match"
769        );
770
771        // Test 7: Single byte keys
772        let single1 = SecureMemory::from_slice(b"A").unwrap();
773        let single2 = SecureMemory::from_slice(b"A").unwrap();
774        let single3 = SecureMemory::from_slice(b"B").unwrap();
775        assert!(
776            single1.constant_time_eq(&single2),
777            "Equal single byte keys should match"
778        );
779        assert!(
780            !single1.constant_time_eq(&single3),
781            "Different single byte keys should not match"
782        );
783
784        // Test 8: Large keys (32 bytes - typical cryptographic key size)
785        let large1 = SecureMemory::from_slice(b"12345678901234567890123456789012").unwrap();
786        let large2 = SecureMemory::from_slice(b"12345678901234567890123456789012").unwrap();
787        let large3 = SecureMemory::from_slice(b"12345678901234567890123456789013").unwrap();
788        assert!(
789            large1.constant_time_eq(&large2),
790            "Equal large keys should match"
791        );
792        assert!(
793            !large1.constant_time_eq(&large3),
794            "Different large keys should not match"
795        );
796    }
797
798    #[test]
799    fn test_secure_vec() {
800        let mut vec = SecureVec::with_capacity(100).unwrap();
801
802        // Test basic operations
803        vec.push(1).unwrap();
804        vec.push(2).unwrap();
805        vec.extend_from_slice(&[3, 4, 5]).unwrap();
806
807        assert_eq!(vec.len(), 5);
808        assert_eq!(vec.as_slice(), &[1, 2, 3, 4, 5]);
809
810        // Test clear
811        vec.clear();
812        assert_eq!(vec.len(), 0);
813        assert!(vec.is_empty());
814    }
815
816    #[test]
817    fn test_secure_string() {
818        let mut string = SecureString::with_capacity(100).unwrap();
819
820        // Test basic operations
821        string.push('H').unwrap();
822        string.push_str("ello").unwrap();
823
824        assert_eq!(string.as_str().unwrap(), "Hello");
825        assert_eq!(string.len(), 5);
826
827        // Test clear
828        string.clear();
829        assert_eq!(string.len(), 0);
830        assert!(string.is_empty());
831    }
832
833    #[test]
834    fn test_secure_memory_pool() {
835        let pool = SecureMemoryPool::new(8192, 1024).unwrap();
836
837        // Test allocation
838        let memory1 = pool.allocate(512).unwrap();
839        let memory2 = pool.allocate(1024).unwrap();
840
841        // Check stats
842        let stats = pool.stats();
843        assert_eq!(stats.total_allocations, 2);
844        assert_eq!(stats.active_allocations, 2);
845
846        // Test deallocation
847        pool.deallocate(memory1);
848        pool.deallocate(memory2);
849
850        let stats = pool.stats();
851        assert_eq!(stats.total_deallocations, 2);
852        assert_eq!(stats.active_allocations, 0);
853    }
854
855    #[test]
856    fn test_global_pool() {
857        let memory = allocate_secure(256).unwrap();
858        println!(
859            "allocate_secure(256) returned memory.len() = {}",
860            memory.len()
861        );
862        assert_eq!(memory.len(), 4096); // Pool allocates in chunks
863
864        // Pool allocates in chunks of 4096 bytes, so capacity will be the chunk size
865        let vec = secure_vec_with_capacity(128).unwrap();
866        println!(
867            "secure_vec_with_capacity(128) returned vec.capacity() = {}",
868            vec.capacity()
869        );
870        assert_eq!(vec.capacity(), 4096); // Pool chunk size
871
872        let string = secure_string_with_capacity(64).unwrap();
873        println!(
874            "secure_string_with_capacity(64) returned string.vec.capacity() = {}",
875            string.vec.capacity()
876        );
877        assert_eq!(string.vec.capacity(), 4096); // Pool chunk size
878    }
879}