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}