use crate::error::Result;
use crate::zeroize::{zeroize_slice, Zeroize};
pub struct GuardedRegion<const N: usize> {
data: [u8; N],
locked: bool,
}
impl<const N: usize> GuardedRegion<N> {
#[inline]
pub fn new() -> Result<Self> {
let mut region = Self {
data: [0u8; N],
locked: false,
};
if N > 0 {
let ptr = region.data.as_ptr();
unsafe {
crate::mlock::lock(ptr, N)?;
}
region.locked = true;
}
Ok(region)
}
#[inline]
pub fn new_unlocked() -> Self {
Self {
data: [0u8; N],
locked: false,
}
}
#[inline]
pub fn as_slice(&self) -> &[u8; N] {
&self.data
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8; N] {
&mut self.data
}
#[inline]
pub fn len(&self) -> usize {
N
}
#[inline]
pub fn is_empty(&self) -> bool {
N == 0
}
#[inline]
pub fn is_locked(&self) -> bool {
self.locked
}
#[inline]
pub fn clear(&mut self) {
self.zeroize();
}
}
impl<const N: usize> Zeroize for GuardedRegion<N> {
#[inline]
fn zeroize(&mut self) {
zeroize_slice(&mut self.data);
}
}
impl<const N: usize> Default for GuardedRegion<N> {
fn default() -> Self {
Self::new_unlocked()
}
}
impl<const N: usize> Drop for GuardedRegion<N> {
fn drop(&mut self) {
self.zeroize();
if self.locked {
let ptr = self.data.as_ptr();
let _ = unsafe { crate::mlock::unlock(ptr, N) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn region_new_unlocked() {
let region = GuardedRegion::<16>::new_unlocked();
assert!(!region.is_locked());
assert_eq!(region.len(), 16);
assert!(!region.is_empty());
assert!(region.as_slice().iter().all(|&b| b == 0));
}
#[test]
#[cfg_attr(miri, ignore)]
fn region_new_locked() {
if let Ok(region) = GuardedRegion::<4096>::new() {
assert!(region.is_locked());
}
}
#[test]
fn region_write_read() {
let mut region = GuardedRegion::<8>::new_unlocked();
region.as_mut_slice().copy_from_slice(&[0xAB; 8]);
assert!(region.as_slice().iter().all(|&b| b == 0xAB));
}
#[test]
fn region_clear() {
let mut region = GuardedRegion::<4>::new_unlocked();
region.as_mut_slice().copy_from_slice(&[0xFF; 4]);
region.clear();
assert!(region.as_slice().iter().all(|&b| b == 0));
}
#[test]
fn region_drops_clean() {
{
let mut region = GuardedRegion::<32>::new_unlocked();
region.as_mut_slice().copy_from_slice(&[0x42; 32]);
}
}
#[test]
fn region_zero_size() {
let region = GuardedRegion::<0>::new_unlocked();
assert!(region.is_empty());
assert_eq!(region.len(), 0);
}
#[test]
fn region_default() {
let region = GuardedRegion::<64>::default();
assert!(!region.is_locked());
assert_eq!(region.len(), 64);
}
}