use alloc::vec::Vec;
use crate::error::AllockedVecError;
use redoubt_zero::{
FastZeroizable, RedoubtZero, ZeroizationProbe, ZeroizeMetadata, ZeroizeOnDropSentinel,
};
#[cfg(any(test, feature = "test-utils"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AllockedVecBehaviour {
#[default]
None,
FailAtPush,
FailAtDrainFrom,
}
#[cfg(any(test, feature = "test-utils"))]
impl ZeroizationProbe for AllockedVecBehaviour {
fn is_zeroized(&self) -> bool {
matches!(self, Self::None)
}
}
#[cfg(any(test, feature = "test-utils"))]
impl ZeroizeMetadata for AllockedVecBehaviour {
const CAN_BE_BULK_ZEROIZED: bool = false;
}
#[cfg(any(test, feature = "test-utils"))]
impl FastZeroizable for AllockedVecBehaviour {
fn fast_zeroize(&mut self) {
*self = AllockedVecBehaviour::None;
}
}
#[derive(RedoubtZero)]
#[fast_zeroize(drop)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Clone))]
pub struct AllockedVec<T>
where
T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
{
inner: Vec<T>,
has_been_sealed: bool,
#[cfg(any(test, feature = "test-utils"))]
behaviour: AllockedVecBehaviour,
__sentinel: ZeroizeOnDropSentinel,
}
impl<T> core::fmt::Debug for AllockedVec<T>
where
T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AllockedVec")
.field("data", &"REDACTED")
.field("len", &self.len())
.field("capacity", &self.capacity())
.finish()
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe + PartialEq> PartialEq
for AllockedVec<T>
{
fn eq(&self, other: &Self) -> bool {
(self.inner == other.inner)
& (self.has_been_sealed == other.has_been_sealed)
& (self.behaviour == other.behaviour)
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<T: FastZeroizable + ZeroizeMetadata + Eq + ZeroizationProbe> Eq for AllockedVec<T> {}
impl<T> AllockedVec<T>
where
T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
{
pub(crate) fn realloc_with<F>(&mut self, capacity: usize, #[allow(unused)] mut hook: F)
where
T: Default,
F: FnMut(&mut Self),
{
if capacity == self.capacity() {
return;
}
if capacity < self.capacity() {
self.truncate(capacity);
}
let new_allocked_vec = {
let mut allocked_vec = AllockedVec::<T>::with_capacity(capacity);
allocked_vec
.drain_from(self.as_mut_slice())
.expect("realloc_with: drain_from cannot fail - new vec has sufficient capacity");
allocked_vec
};
#[cfg(test)]
hook(self);
self.fast_zeroize();
#[cfg(test)]
hook(self);
*self = new_allocked_vec;
}
pub fn new() -> Self {
Self {
inner: Vec::new(),
has_been_sealed: false,
#[cfg(any(test, feature = "test-utils"))]
behaviour: AllockedVecBehaviour::default(),
__sentinel: ZeroizeOnDropSentinel::default(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
let mut vec = Self::new();
vec.reserve_exact(capacity)
.expect("Infallible: Vec capacity is 0 (has_been_sealed is false)");
vec
}
pub fn reserve_exact(&mut self, capacity: usize) -> Result<(), AllockedVecError> {
if self.has_been_sealed {
return Err(AllockedVecError::AlreadySealed);
}
self.inner.reserve_exact(capacity);
self.has_been_sealed = true;
#[cfg(any(test, feature = "unsafe"))]
if capacity > 0 {
redoubt_util::fast_zeroize_slice(unsafe { self.as_capacity_mut_slice() });
}
Ok(())
}
pub fn push(&mut self, value: T) -> Result<(), AllockedVecError> {
#[cfg(any(test, feature = "test-utils"))]
if matches!(self.behaviour, AllockedVecBehaviour::FailAtPush) {
return Err(AllockedVecError::CapacityExceeded);
}
if self.len() >= self.capacity() {
return Err(AllockedVecError::CapacityExceeded);
}
self.inner.push(value);
Ok(())
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn as_slice(&self) -> &[T] {
&self.inner
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.inner
}
pub fn truncate(&mut self, new_len: usize) {
if new_len < self.len() {
self.inner[new_len..].fast_zeroize();
debug_assert!(
self.inner[new_len..].iter().all(|v| v.is_zeroized()),
"AllockedVec::truncate: zeroization failed"
);
self.inner.truncate(new_len);
}
}
pub fn drain_from(&mut self, slice: &mut [T]) -> Result<(), AllockedVecError>
where
T: Default,
{
#[cfg(any(test, feature = "test-utils"))]
if matches!(self.behaviour, AllockedVecBehaviour::FailAtDrainFrom) {
return Err(AllockedVecError::CapacityExceeded);
}
let new_len = self
.len()
.checked_add(slice.len())
.ok_or(AllockedVecError::Overflow)?;
if new_len > self.capacity() {
return Err(AllockedVecError::CapacityExceeded);
}
for item in slice.iter_mut() {
let value = core::mem::take(item);
self.inner.push(value);
}
Ok(())
}
pub fn realloc_with_capacity(&mut self, capacity: usize)
where
T: Default,
{
self.realloc_with(capacity, |_| {});
}
pub fn fill_with_default(&mut self)
where
T: Default,
{
let remaining = self.capacity() - self.len();
let mut source: Vec<T> = (0..remaining).map(|_| T::default()).collect();
self.drain_from(&mut source)
.expect("infallible: remaining = capacity - len");
}
#[cfg(any(test, feature = "test-utils"))]
pub fn change_behaviour(&mut self, behaviour: AllockedVecBehaviour) {
self.behaviour = behaviour;
}
#[cfg(test)]
pub(crate) fn __unsafe_expose_inner_for_tests<F>(&mut self, f: F)
where
F: FnOnce(&mut Vec<T>),
{
f(&mut self.inner);
}
#[cfg(any(test, feature = "unsafe"))]
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.inner.as_mut_ptr()
}
#[cfg(any(test, feature = "unsafe"))]
#[inline(always)]
pub unsafe fn as_capacity_slice(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.inner.as_ptr(), self.inner.capacity()) }
}
#[cfg(any(test, feature = "unsafe"))]
#[inline(always)]
pub unsafe fn as_capacity_mut_slice(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.inner.as_mut_ptr(), self.inner.capacity()) }
}
#[cfg(any(test, feature = "unsafe"))]
#[inline(always)]
pub unsafe fn set_len(&mut self, new_len: usize) {
debug_assert!(new_len <= self.capacity());
unsafe { self.inner.set_len(new_len) };
}
}
impl<T> Default for AllockedVec<T>
where
T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
{
fn default() -> Self {
Self::new()
}
}
impl<T> core::ops::Deref for AllockedVec<T>
where
T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
{
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.inner
}
}