redoubt_alloc/
redoubt_array.rs

1// Copyright (c) 2025-2026 Federico Hoerth <memparanoid@gmail.com>
2// SPDX-License-Identifier: GPL-3.0-only
3// See LICENSE in the repository root for full license text.
4
5use alloc::boxed::Box;
6use core::ops::{Deref, DerefMut};
7
8use redoubt_zero::{
9    FastZeroizable, RedoubtZero, ZeroizationProbe, ZeroizeMetadata, ZeroizeOnDropSentinel,
10};
11
12/// A fixed-size array wrapper with automatic zeroization.
13///
14/// Unlike `RedoubtVec`, this type has a fixed size known at compile time.
15/// It provides safe replacement of the entire array with zeroization of the source.
16///
17/// # Example
18///
19/// ```rust
20/// use redoubt_alloc::RedoubtArray;
21/// use redoubt_zero::ZeroizationProbe;
22///
23/// let mut arr = RedoubtArray::<u8, 32>::new();
24/// let mut data = [42u8; 32];
25/// arr.replace_from_mut_array(&mut data);
26///
27/// // Source is guaranteed to be zeroized
28/// assert!(data.is_zeroized());
29/// ```
30#[derive(RedoubtZero)]
31pub struct RedoubtArray<T, const N: usize>
32where
33    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
34{
35    inner: Box<[T; N]>,
36    __sentinel: ZeroizeOnDropSentinel,
37}
38
39#[cfg(any(test, feature = "test-utils"))]
40impl<T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe + PartialEq, const N: usize> PartialEq
41    for RedoubtArray<T, N>
42{
43    fn eq(&self, other: &Self) -> bool {
44        // Skip __sentinel (metadata that changes during zeroization)
45        self.inner == other.inner
46    }
47}
48
49#[cfg(any(test, feature = "test-utils"))]
50impl<T: FastZeroizable + ZeroizeMetadata + Eq + ZeroizationProbe, const N: usize> Eq
51    for RedoubtArray<T, N>
52{
53}
54
55impl<T, const N: usize> core::fmt::Debug for RedoubtArray<T, N>
56where
57    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
58{
59    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60        f.debug_struct("RedoubtArray")
61            .field("data", &"REDACTED")
62            .field("len", &N)
63            .finish()
64    }
65}
66
67impl<T, const N: usize> RedoubtArray<T, N>
68where
69    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
70{
71    /// Creates a new `RedoubtArray` with default-initialized elements.
72    pub fn new() -> Self
73    where
74        T: Default,
75    {
76        Self {
77            inner: Box::new(core::array::from_fn(|_| T::default())),
78            __sentinel: ZeroizeOnDropSentinel::default(),
79        }
80    }
81
82    /// Creates a new `RedoubtArray` from a mutable array, zeroizing the source.
83    pub fn from_mut_array(src: &mut [T; N]) -> Self
84    where
85        T: Default,
86    {
87        let mut arr = Self::new();
88        arr.replace_from_mut_array(src);
89        arr
90    }
91
92    /// Returns the number of elements in the array (always N).
93    #[inline]
94    pub const fn len(&self) -> usize {
95        N
96    }
97
98    /// Returns `true` if the array contains no elements (always false unless N=0).
99    #[inline]
100    pub const fn is_empty(&self) -> bool {
101        N == 0
102    }
103
104    /// Replaces the entire array from a mutable source, zeroizing the source.
105    ///
106    /// # Performance Note
107    ///
108    /// Uses `ptr::swap_nonoverlapping` to exchange contents with the source
109    /// without creating intermediate copies that could spill to stack.
110    pub fn replace_from_mut_array(&mut self, src: &mut [T; N]) {
111        self.fast_zeroize();
112
113        unsafe {
114            // SAFETY: Both arrays have exactly N elements and are properly aligned
115            // Swap exchanges contents without intermediate copies
116            core::ptr::swap_nonoverlapping(src.as_mut_ptr(), self.inner.as_mut_ptr(), N);
117        }
118
119        // Zeroize source (which now contains the old self.inner values, all zeros)
120        src.fast_zeroize();
121    }
122
123    /// Returns a slice containing the entire array.
124    #[inline]
125    pub fn as_slice(&self) -> &[T] {
126        self.inner.as_ref()
127    }
128
129    /// Returns a mutable slice containing the entire array.
130    #[inline]
131    pub fn as_mut_slice(&mut self) -> &mut [T] {
132        self.inner.as_mut()
133    }
134
135    /// Returns a reference to the underlying array.
136    #[inline]
137    pub fn as_array(&self) -> &[T; N] {
138        &self.inner
139    }
140
141    /// Returns a mutable reference to the underlying array.
142    #[inline]
143    pub fn as_mut_array(&mut self) -> &mut [T; N] {
144        &mut self.inner
145    }
146}
147
148impl<T, const N: usize> Default for RedoubtArray<T, N>
149where
150    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe + Default,
151{
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157impl<T, const N: usize> Deref for RedoubtArray<T, N>
158where
159    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
160{
161    type Target = [T];
162
163    fn deref(&self) -> &Self::Target {
164        &*self.inner
165    }
166}
167
168impl<T, const N: usize> DerefMut for RedoubtArray<T, N>
169where
170    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
171{
172    fn deref_mut(&mut self) -> &mut Self::Target {
173        &mut *self.inner
174    }
175}