redoubt_alloc/
redoubt_vec.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::vec::Vec;
6use core::ops::{Deref, DerefMut};
7
8use redoubt_zero::{
9    FastZeroizable, RedoubtZero, ZeroizationProbe, ZeroizeMetadata, ZeroizeOnDropSentinel,
10};
11
12/// A Vec wrapper with automatic zeroization and safe reallocation.
13///
14/// When capacity is exceeded, `RedoubtVec` performs a safe reallocation:
15/// 1. Allocates temporary storage with current data
16/// 2. Zeroizes old allocation
17/// 3. Re-allocates with 2x capacity
18/// 4. Drains from temp (zeroizing temp)
19///
20/// This ensures no sensitive data is left in old allocations, at the cost
21/// of performance (double allocation during growth).
22///
23/// # Example
24///
25/// ```rust
26/// use redoubt_alloc::RedoubtVec;
27/// use redoubt_zero::ZeroizationProbe;
28///
29/// let mut vec = RedoubtVec::new();
30/// let mut data = [42u8, 43];
31/// vec.extend_from_mut_slice(&mut data);
32///
33/// // Source is guaranteed to be zeroized
34/// assert!(data.is_zeroized());
35/// ```
36#[derive(RedoubtZero)]
37pub struct RedoubtVec<T>
38where
39    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
40{
41    inner: Vec<T>,
42    __sentinel: ZeroizeOnDropSentinel,
43}
44
45#[cfg(any(test, feature = "test-utils"))]
46impl<T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe + PartialEq> PartialEq
47    for RedoubtVec<T>
48{
49    fn eq(&self, other: &Self) -> bool {
50        // Skip __sentinel (metadata that changes during zeroization)
51        self.inner == other.inner
52    }
53}
54
55#[cfg(any(test, feature = "test-utils"))]
56impl<T: FastZeroizable + ZeroizeMetadata + Eq + ZeroizationProbe> Eq for RedoubtVec<T> {}
57
58impl<T> core::fmt::Debug for RedoubtVec<T>
59where
60    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
61{
62    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63        f.debug_struct("RedoubtVec")
64            .field("data", &"REDACTED")
65            .field("len", &self.len())
66            .field("capacity", &self.capacity())
67            .finish()
68    }
69}
70
71impl<T> RedoubtVec<T>
72where
73    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
74{
75    /// Creates a new empty `RedoubtVec`.
76    pub fn new() -> Self {
77        Self {
78            inner: Vec::new(),
79            __sentinel: ZeroizeOnDropSentinel::default(),
80        }
81    }
82
83    /// Creates a new `RedoubtVec` with the specified capacity.
84    pub fn with_capacity(capacity: usize) -> Self {
85        Self {
86            inner: Vec::with_capacity(capacity),
87            __sentinel: ZeroizeOnDropSentinel::default(),
88        }
89    }
90
91    /// Creates a new `RedoubtVec` from a mutable slice, zeroizing the source.
92    pub fn from_mut_slice(src: &mut [T]) -> Self
93    where
94        T: Default,
95    {
96        let mut vec = Self::new();
97        vec.extend_from_mut_slice(src);
98        vec
99    }
100
101    /// Returns the number of elements in the vector.
102    #[inline]
103    pub fn len(&self) -> usize {
104        self.inner.len()
105    }
106
107    /// Returns `true` if the vector contains no elements.
108    #[inline]
109    pub fn is_empty(&self) -> bool {
110        self.inner.is_empty()
111    }
112
113    /// Returns the capacity of the vector.
114    #[inline]
115    pub fn capacity(&self) -> usize {
116        self.inner.capacity()
117    }
118
119    /// Grows to at least `min_capacity` if needed.
120    ///
121    /// Rounds up to the next power of 2 to maintain efficient growth pattern
122    /// (1 → 2 → 4 → 8 → 16...). Does nothing if current capacity is sufficient.
123    ///
124    /// # Safety Strategy
125    ///
126    /// 1. Allocate temp Vec with current data (memcpy for performance)
127    /// 2. Zeroize old allocation
128    /// 3. Re-allocate with new capacity (next power of 2)
129    /// 4. Move data from temp back (memcpy + zeroize temp)
130    ///
131    /// # Performance Note
132    ///
133    /// Uses `ptr::copy_nonoverlapping` instead of `iter_mut() + mem::take()`
134    /// because every nanosecond counts when handling sensitive data. The
135    /// unsafe memcpy is significantly faster and avoids requiring `T: Clone`
136    /// or `T: Default` bounds.
137    ///
138    /// By accepting `min_capacity` and doing a single grow, this is O(n) instead
139    /// of O(n log n) when growing by large amounts.
140    #[cold]
141    #[inline(never)]
142    fn grow_to(&mut self, min_capacity: usize) {
143        let current_len = self.len();
144        let new_capacity = min_capacity.next_power_of_two();
145
146        // 1. Allocate temp and copy current data
147        let mut tmp = Vec::with_capacity(current_len);
148        unsafe {
149            // SAFETY (PRECONDITIONS ARE MET): copying exactly len() elements from valid Vec
150            core::ptr::copy_nonoverlapping(self.inner.as_ptr(), tmp.as_mut_ptr(), current_len);
151            tmp.set_len(current_len);
152        }
153
154        // 2. Zeroize old allocation
155        self.inner.fast_zeroize();
156        self.inner.clear();
157        self.inner.shrink_to_fit();
158
159        // 3. Re-allocate with new capacity
160        self.inner.reserve_exact(new_capacity);
161
162        // 4. Copy data back from tmp
163        unsafe {
164            // SAFETY (PRECONDITIONS ARE MET): tmp has exactly current_len elements, self has sufficient capacity from reserve_exact
165            core::ptr::copy_nonoverlapping(tmp.as_ptr(), self.inner.as_mut_ptr(), current_len);
166            self.inner.set_len(current_len);
167        }
168
169        // 5. Zeroize and drop tmp
170        tmp.fast_zeroize();
171    }
172
173    #[inline(always)]
174    fn maybe_grow_to(&mut self, min_capacity: usize) {
175        if self.capacity() >= min_capacity {
176            return;
177        }
178
179        self.grow_to(min_capacity);
180    }
181
182    /// Extends from a mutable slice, zeroizing the source.
183    ///
184    /// Grows the vector if necessary to accommodate the slice.
185    ///
186    /// # Performance Note
187    ///
188    /// Uses `ptr::copy_nonoverlapping` for bulk copy instead of individual
189    /// operations. This is significantly faster for large slices.
190    pub fn extend_from_mut_slice(&mut self, src: &mut [T])
191    where
192        T: Default,
193    {
194        self.maybe_grow_to(self.len() + src.len());
195
196        unsafe {
197            // SAFETY (PRECONDITIONS ARE MET): src has exactly src.len() elements, self has sufficient capacity from maybe_grow_to
198            let src_ptr = src.as_ptr();
199            let dst_ptr = self.inner.as_mut_ptr().add(self.len());
200            core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, src.len());
201            self.inner.set_len(self.len() + src.len());
202        }
203
204        // Zeroize source
205        src.fast_zeroize();
206    }
207
208    /// Replaces the vector contents with data from a mutable slice, zeroizing both
209    /// the old contents and the source.
210    pub fn replace_from_mut_slice(&mut self, src: &mut [T])
211    where
212        T: Default,
213    {
214        self.clear();
215        self.extend_from_mut_slice(src);
216    }
217
218    /// Drains a single value into the vector, zeroizing the source.
219    pub fn drain_value(&mut self, src: &mut T)
220    where
221        T: Default,
222    {
223        self.maybe_grow_to(self.len() + 1);
224
225        let item = core::mem::take(src);
226        self.inner.push(item);
227        src.fast_zeroize();
228    }
229
230    /// Clears the vector, removing all values.
231    pub fn clear(&mut self) {
232        self.inner.fast_zeroize();
233        self.inner.clear();
234    }
235
236    /// Returns a slice containing the entire vector.
237    pub fn as_slice(&self) -> &[T] {
238        &self.inner
239    }
240
241    /// Returns a mutable slice containing the entire vector.
242    pub fn as_mut_slice(&mut self) -> &mut [T] {
243        &mut self.inner
244    }
245
246    /// Returns a reference to the inner Vec.
247    ///
248    /// This allows direct access to the underlying Vec for operations
249    /// that require Vec-specific APIs, such as codec implementations.
250    pub fn as_vec(&self) -> &Vec<T> {
251        &self.inner
252    }
253
254    /// Returns a mutable reference to the inner Vec.
255    ///
256    /// This allows direct manipulation of the underlying Vec for operations
257    /// that require Vec-specific APIs, such as codec implementations.
258    pub fn as_mut_vec(&mut self) -> &mut Vec<T> {
259        &mut self.inner
260    }
261
262    /// Initializes the vector to the specified size using the most efficient method.
263    ///
264    /// For types that can be bulk zeroized (primitives), this uses zero initialization
265    /// which is extremely fast. For complex types, it uses `T::default()`.
266    ///
267    /// # Performance
268    ///
269    /// - If `T::CAN_BE_BULK_ZEROIZED == true`: O(1) memset operation
270    /// - Otherwise: O(n) pushing defaults
271    ///
272    /// # Safety
273    ///
274    /// After calling this method, the vector will have exactly `size` elements,
275    /// all properly initialized either to zero (if bulk zeroizable) or to their
276    /// default value.
277    #[cfg(feature = "default_init")]
278    pub fn default_init_to_size(&mut self, size: usize)
279    where
280        T: Default,
281    {
282        self.clear();
283        self.maybe_grow_to(size);
284
285        if T::CAN_BE_BULK_ZEROIZED {
286            // Zero init path (SUPER FAST for primitives like u8, u32, etc.)
287            self.inner.fast_zeroize();
288            // SAFETY: T can be bulk zeroized, so all-zeros is a valid state.
289            // The inner vec has sufficient capacity from maybe_grow_to.
290            unsafe {
291                self.inner.set_len(size);
292            }
293        } else {
294            // Default path for complex types
295            for _ in 0..size {
296                self.inner.push(T::default());
297            }
298            self.inner.fast_zeroize();
299        }
300
301        debug_assert_eq!(self.len(), size);
302    }
303}
304
305impl<T> Default for RedoubtVec<T>
306where
307    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
308{
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314impl<T> Deref for RedoubtVec<T>
315where
316    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
317{
318    type Target = [T];
319
320    fn deref(&self) -> &Self::Target {
321        &self.inner
322    }
323}
324
325impl<T> DerefMut for RedoubtVec<T>
326where
327    T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
328{
329    fn deref_mut(&mut self) -> &mut Self::Target {
330        &mut self.inner
331    }
332}