redoubt_alloc/allocked_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;
6
7use crate::error::AllockedVecError;
8use redoubt_zero::{
9 FastZeroizable, RedoubtZero, ZeroizationProbe, ZeroizeMetadata, ZeroizeOnDropSentinel,
10};
11
12/// Test behaviour for injecting failures in `AllockedVec` operations.
13///
14/// This is only available with the `test-utils` feature and allows users
15/// to test error handling paths in their code by injecting failures.
16///
17/// The behaviour is sticky - once set, it remains active until changed.
18///
19/// # Example
20///
21/// ```rust
22/// // test-utils feature required in dev-dependencies
23/// use redoubt_alloc::{AllockedVec, AllockedVecBehaviour, AllockedVecError};
24///
25/// #[cfg(test)]
26/// mod tests {
27/// use super::*;
28///
29/// #[test]
30/// fn test_handles_capacity_exceeded() -> Result<(), AllockedVecError> {
31/// let mut vec = AllockedVec::with_capacity(10);
32///
33/// // Inject failure
34/// vec.change_behaviour(AllockedVecBehaviour::FailAtPush);
35///
36/// // This will fail even though capacity allows it
37/// let result = vec.push(1u8);
38/// assert!(result.is_err());
39///
40/// // Reset to normal behaviour
41/// vec.change_behaviour(AllockedVecBehaviour::None);
42///
43/// // Now it works
44/// vec.push(1u8)?;
45/// Ok(())
46/// }
47/// }
48/// ```
49#[cfg(any(test, feature = "test-utils"))]
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum AllockedVecBehaviour {
52 /// Normal behaviour - no injected failures.
53 #[default]
54 None,
55 /// Next `push()` call will fail with `CapacityExceeded`.
56 FailAtPush,
57 /// Next `drain_from()` call will fail with `CapacityExceeded`.
58 FailAtDrainFrom,
59}
60
61#[cfg(any(test, feature = "test-utils"))]
62impl ZeroizationProbe for AllockedVecBehaviour {
63 fn is_zeroized(&self) -> bool {
64 matches!(self, Self::None)
65 }
66}
67
68#[cfg(any(test, feature = "test-utils"))]
69impl ZeroizeMetadata for AllockedVecBehaviour {
70 const CAN_BE_BULK_ZEROIZED: bool = false;
71}
72
73#[cfg(any(test, feature = "test-utils"))]
74impl FastZeroizable for AllockedVecBehaviour {
75 fn fast_zeroize(&mut self) {
76 *self = AllockedVecBehaviour::None;
77 }
78}
79
80/// Allocation-locked Vec that prevents reallocation after sealing.
81///
82/// Once `reserve_exact()` is called, the vector is sealed and cannot grow beyond
83/// that capacity. All operations that would cause reallocation fail and zeroize data.
84///
85/// # Type Parameters
86///
87/// - `T`: The element type. Must implement `FastZeroizable`, `ZeroizeMetadata`, and `ZeroizationProbe` for automatic cleanup.
88///
89/// # Example
90///
91/// ```rust
92/// use redoubt_alloc::{AllockedVec, AllockedVecError};
93///
94/// fn example() -> Result<(), AllockedVecError> {
95/// let mut vec = AllockedVec::new();
96/// vec.reserve_exact(5)?;
97///
98/// vec.push(1u8)?;
99/// vec.push(2u8)?;
100///
101/// assert_eq!(vec.len(), 2);
102/// assert_eq!(vec.capacity(), 5);
103/// Ok(())
104/// }
105/// # example().unwrap();
106/// ```
107#[derive(RedoubtZero)]
108#[fast_zeroize(drop)]
109#[cfg_attr(any(test, feature = "test-utils"), derive(Clone))]
110pub struct AllockedVec<T>
111where
112 T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
113{
114 inner: Vec<T>,
115 has_been_sealed: bool,
116 #[cfg(any(test, feature = "test-utils"))]
117 behaviour: AllockedVecBehaviour,
118 __sentinel: ZeroizeOnDropSentinel,
119}
120
121impl<T> core::fmt::Debug for AllockedVec<T>
122where
123 T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
124{
125 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126 f.debug_struct("AllockedVec")
127 .field("data", &"REDACTED")
128 .field("len", &self.len())
129 .field("capacity", &self.capacity())
130 .finish()
131 }
132}
133
134#[cfg(any(test, feature = "test-utils"))]
135impl<T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe + PartialEq> PartialEq
136 for AllockedVec<T>
137{
138 fn eq(&self, other: &Self) -> bool {
139 // Skip __sentinel (metadata that changes during zeroization)
140 // Use `&` instead of `&&` to avoid branches and easier testing.
141 (self.inner == other.inner)
142 & (self.has_been_sealed == other.has_been_sealed)
143 & (self.behaviour == other.behaviour)
144 }
145}
146
147#[cfg(any(test, feature = "test-utils"))]
148impl<T: FastZeroizable + ZeroizeMetadata + Eq + ZeroizationProbe> Eq for AllockedVec<T> {}
149
150impl<T> AllockedVec<T>
151where
152 T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
153{
154 pub(crate) fn realloc_with<F>(&mut self, capacity: usize, #[allow(unused)] mut hook: F)
155 where
156 T: Default,
157 F: FnMut(&mut Self),
158 {
159 // No-op if capacity is the same
160 if capacity == self.capacity() {
161 return;
162 }
163
164 // When shrinking, truncate to fit in new capacity
165 if capacity < self.capacity() {
166 self.truncate(capacity);
167 }
168
169 let new_allocked_vec = {
170 let mut allocked_vec = AllockedVec::<T>::with_capacity(capacity);
171 allocked_vec
172 .drain_from(self.as_mut_slice())
173 .expect("realloc_with: drain_from cannot fail - new vec has sufficient capacity");
174 allocked_vec
175 };
176
177 #[cfg(test)]
178 hook(self);
179
180 self.fast_zeroize();
181
182 #[cfg(test)]
183 hook(self);
184
185 *self = new_allocked_vec;
186 }
187
188 /// Creates a new empty `AllockedVec` with zero capacity.
189 ///
190 /// The vector is not sealed until `reserve_exact()` is called.
191 ///
192 /// # Example
193 ///
194 /// ```rust
195 /// use redoubt_alloc::AllockedVec;
196 ///
197 /// let vec: AllockedVec<u8> = AllockedVec::new();
198 /// assert_eq!(vec.len(), 0);
199 /// assert_eq!(vec.capacity(), 0);
200 /// ```
201 pub fn new() -> Self {
202 Self {
203 inner: Vec::new(),
204 has_been_sealed: false,
205 #[cfg(any(test, feature = "test-utils"))]
206 behaviour: AllockedVecBehaviour::default(),
207 __sentinel: ZeroizeOnDropSentinel::default(),
208 }
209 }
210
211 /// Creates a new `AllockedVec` with the specified capacity and seals it immediately.
212 ///
213 /// This is equivalent to calling `new()` followed by `reserve_exact(capacity)`.
214 ///
215 /// # Example
216 ///
217 /// ```rust
218 /// use redoubt_alloc::AllockedVec;
219 ///
220 /// let mut vec = AllockedVec::<u8>::with_capacity(10);
221 /// assert_eq!(vec.len(), 0);
222 /// assert_eq!(vec.capacity(), 10);
223 /// // Already sealed - cannot reserve again
224 /// assert!(vec.reserve_exact(20).is_err());
225 /// ```
226 pub fn with_capacity(capacity: usize) -> Self {
227 let mut vec = Self::new();
228
229 vec.reserve_exact(capacity)
230 .expect("Infallible: Vec capacity is 0 (has_been_sealed is false)");
231
232 vec
233 }
234
235 /// Reserves exact capacity and seals the vector.
236 ///
237 /// After calling this method, the vector is sealed and cannot be resized.
238 /// Subsequent calls to `reserve_exact()` will fail.
239 ///
240 /// # Errors
241 ///
242 /// Returns [`AllockedVecError::AlreadySealed`] if the vector is already sealed.
243 ///
244 /// # Example
245 ///
246 /// ```rust
247 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
248 ///
249 /// fn example() -> Result<(), AllockedVecError> {
250 /// let mut vec: AllockedVec<u8> = AllockedVec::new();
251 /// vec.reserve_exact(10)?;
252 ///
253 /// // Second reserve fails
254 /// assert!(vec.reserve_exact(20).is_err());
255 /// Ok(())
256 /// }
257 /// # example().unwrap();
258 /// ```
259 pub fn reserve_exact(&mut self, capacity: usize) -> Result<(), AllockedVecError> {
260 if self.has_been_sealed {
261 return Err(AllockedVecError::AlreadySealed);
262 }
263
264 self.inner.reserve_exact(capacity);
265 self.has_been_sealed = true;
266
267 // When unsafe feature is enabled, zero the entire capacity to prevent
268 // reading garbage via as_capacity_slice() / as_capacity_mut_slice()
269 #[cfg(any(test, feature = "unsafe"))]
270 if capacity > 0 {
271 redoubt_util::fast_zeroize_slice(unsafe { self.as_capacity_mut_slice() });
272 }
273
274 Ok(())
275 }
276
277 /// Pushes a value onto the end of the vector.
278 ///
279 /// # Errors
280 ///
281 /// Returns [`AllockedVecError::CapacityExceeded`] if the vector is at capacity.
282 ///
283 /// # Example
284 ///
285 /// ```rust
286 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
287 ///
288 /// fn example() -> Result<(), AllockedVecError> {
289 /// let mut vec = AllockedVec::with_capacity(2);
290 /// vec.push(1u8)?;
291 /// vec.push(2u8)?;
292 ///
293 /// // Exceeds capacity
294 /// assert!(vec.push(3u8).is_err());
295 /// Ok(())
296 /// }
297 /// # example().unwrap();
298 /// ```
299 pub fn push(&mut self, value: T) -> Result<(), AllockedVecError> {
300 #[cfg(any(test, feature = "test-utils"))]
301 if matches!(self.behaviour, AllockedVecBehaviour::FailAtPush) {
302 return Err(AllockedVecError::CapacityExceeded);
303 }
304
305 if self.len() >= self.capacity() {
306 return Err(AllockedVecError::CapacityExceeded);
307 }
308
309 self.inner.push(value);
310 Ok(())
311 }
312
313 /// Returns the number of elements in the vector.
314 ///
315 /// # Example
316 ///
317 /// ```rust
318 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
319 ///
320 /// fn example() -> Result<(), AllockedVecError> {
321 /// let mut vec = AllockedVec::with_capacity(10);
322 /// vec.push(1u8)?;
323 /// assert_eq!(vec.len(), 1);
324 /// Ok(())
325 /// }
326 /// # example().unwrap();
327 /// ```
328 pub fn len(&self) -> usize {
329 self.inner.len()
330 }
331
332 /// Returns the total capacity of the vector.
333 ///
334 /// # Example
335 ///
336 /// ```rust
337 /// use redoubt_alloc::AllockedVec;
338 ///
339 /// let vec: AllockedVec<u8> = AllockedVec::with_capacity(10);
340 /// assert_eq!(vec.capacity(), 10);
341 /// ```
342 pub fn capacity(&self) -> usize {
343 self.inner.capacity()
344 }
345
346 /// Returns `true` if the vector contains no elements.
347 ///
348 /// # Example
349 ///
350 /// ```rust
351 /// use redoubt_alloc::AllockedVec;
352 ///
353 /// let vec: AllockedVec<u8> = AllockedVec::new();
354 /// assert!(vec.is_empty());
355 /// ```
356 pub fn is_empty(&self) -> bool {
357 self.inner.is_empty()
358 }
359
360 /// Returns an immutable slice view of the vector.
361 ///
362 /// # Example
363 ///
364 /// ```rust
365 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
366 ///
367 /// fn example() -> Result<(), AllockedVecError> {
368 /// let mut vec = AllockedVec::with_capacity(3);
369 /// vec.push(1u8)?;
370 /// vec.push(2u8)?;
371 ///
372 /// assert_eq!(vec.as_slice(), &[1, 2]);
373 /// Ok(())
374 /// }
375 /// # example().unwrap();
376 /// ```
377 pub fn as_slice(&self) -> &[T] {
378 &self.inner
379 }
380
381 /// Returns a mutable slice view of the vector.
382 ///
383 /// # Example
384 ///
385 /// ```rust
386 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
387 ///
388 /// fn example() -> Result<(), AllockedVecError> {
389 /// let mut vec = AllockedVec::with_capacity(3);
390 /// vec.push(1u8)?;
391 /// vec.push(2u8)?;
392 ///
393 /// vec.as_mut_slice()[0] = 42;
394 /// assert_eq!(vec.as_slice(), &[42, 2]);
395 /// Ok(())
396 /// }
397 /// # example().unwrap();
398 /// ```
399 pub fn as_mut_slice(&mut self) -> &mut [T] {
400 &mut self.inner
401 }
402
403 /// Truncates the vector to the specified length, zeroizing removed elements.
404 ///
405 /// If `new_len` is greater than or equal to the current length, this is a no-op.
406 /// Otherwise, the elements beyond `new_len` are zeroized before truncation.
407 ///
408 /// # Security
409 ///
410 /// This method zeroizes removed elements before truncating to prevent sensitive
411 /// data from remaining in spare capacity. A debug assertion verifies zeroization.
412 ///
413 /// # Note
414 ///
415 /// This method guarantees no data remains in spare capacity after the operation.
416 ///
417 /// # Example
418 ///
419 /// ```rust
420 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
421 ///
422 /// fn example() -> Result<(), AllockedVecError> {
423 /// let mut vec = AllockedVec::with_capacity(5);
424 /// vec.push(1u8)?;
425 /// vec.push(2u8)?;
426 /// vec.push(3u8)?;
427 ///
428 /// vec.truncate(1);
429 ///
430 /// assert_eq!(vec.len(), 1);
431 /// assert_eq!(vec.as_slice(), &[1]);
432 /// // Elements at indices 1 and 2 have been zeroized in spare capacity
433 /// Ok(())
434 /// }
435 /// # example().unwrap();
436 /// ```
437 pub fn truncate(&mut self, new_len: usize) {
438 if new_len < self.len() {
439 self.inner[new_len..].fast_zeroize();
440
441 debug_assert!(
442 self.inner[new_len..].iter().all(|v| v.is_zeroized()),
443 "AllockedVec::truncate: zeroization failed"
444 );
445
446 self.inner.truncate(new_len);
447 }
448 }
449
450 /// Drains values from a mutable slice into the vector.
451 ///
452 /// The source slice is zeroized after draining (each element replaced with `T::default()`).
453 ///
454 /// # Errors
455 ///
456 /// Returns [`AllockedVecError::CapacityExceeded`] if adding all elements would exceed capacity.
457 ///
458 /// # Example
459 ///
460 /// ```rust
461 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
462 ///
463 /// fn example() -> Result<(), AllockedVecError> {
464 /// let mut vec = AllockedVec::with_capacity(5);
465 /// let mut data = vec![1u8, 2, 3, 4, 5];
466 ///
467 /// vec.drain_from(&mut data)?;
468 ///
469 /// assert_eq!(vec.len(), 5);
470 /// assert!(data.iter().all(|&x| x == 0)); // Source zeroized
471 /// Ok(())
472 /// }
473 /// # example().unwrap();
474 /// ```
475 pub fn drain_from(&mut self, slice: &mut [T]) -> Result<(), AllockedVecError>
476 where
477 T: Default,
478 {
479 #[cfg(any(test, feature = "test-utils"))]
480 if matches!(self.behaviour, AllockedVecBehaviour::FailAtDrainFrom) {
481 return Err(AllockedVecError::CapacityExceeded);
482 }
483
484 // Note: checked_add overflow is practically impossible (requires len > isize::MAX),
485 // but we keep this defensive check for integer overflow safety.
486 let new_len = self
487 .len()
488 .checked_add(slice.len())
489 .ok_or(AllockedVecError::Overflow)?;
490
491 if new_len > self.capacity() {
492 return Err(AllockedVecError::CapacityExceeded);
493 }
494
495 for item in slice.iter_mut() {
496 let value = core::mem::take(item);
497 self.inner.push(value);
498 }
499
500 Ok(())
501 }
502
503 /// Re-seals the vector with a new capacity, safely zeroizing the old allocation.
504 ///
505 /// This method allows expanding a sealed `AllockedVec` by:
506 /// 1. Creating a new vector with the requested capacity
507 /// 2. Draining data from the old vector to the new one (zeroizes source via `mem::take`)
508 /// 3. Zeroizing the old vector (including spare capacity)
509 /// 4. Replacing self with the new vector
510 ///
511 /// If `new_capacity <= current capacity`, this is a no-op.
512 ///
513 /// # Safety Guarantees
514 ///
515 /// - Old allocation is fully zeroized before being dropped
516 /// - No unzeroized copies of data remain in memory
517 /// - New allocation is sealed with the specified capacity
518 ///
519 /// # Example
520 ///
521 /// ```rust
522 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
523 ///
524 /// fn example() -> Result<(), AllockedVecError> {
525 /// let mut vec = AllockedVec::with_capacity(5);
526 /// vec.push(1u8)?;
527 /// vec.push(2u8)?;
528 ///
529 /// // Expand capacity safely
530 /// vec.realloc_with_capacity(10);
531 ///
532 /// // Now can push more elements
533 /// vec.push(3u8)?;
534 /// assert_eq!(vec.capacity(), 10);
535 /// Ok(())
536 /// }
537 /// # example().unwrap();
538 /// ```
539 pub fn realloc_with_capacity(&mut self, capacity: usize)
540 where
541 T: Default,
542 {
543 self.realloc_with(capacity, |_| {});
544 }
545
546 /// Fills the remaining capacity with `T::default()` values.
547 ///
548 /// This method creates default values for the unused capacity and appends them
549 /// to the vector, preserving any existing elements. After this call,
550 /// `len() == capacity()`.
551 ///
552 /// # Example
553 ///
554 /// ```rust
555 /// use redoubt_alloc::{AllockedVec, AllockedVecError};
556 ///
557 /// fn example() -> Result<(), AllockedVecError> {
558 /// let mut vec = AllockedVec::<u8>::with_capacity(5);
559 /// vec.push(1)?;
560 /// vec.push(2)?;
561 ///
562 /// vec.fill_with_default();
563 ///
564 /// assert_eq!(vec.len(), 5);
565 /// assert_eq!(vec.as_slice(), &[1, 2, 0, 0, 0]);
566 /// Ok(())
567 /// }
568 /// # example().unwrap();
569 /// ```
570 pub fn fill_with_default(&mut self)
571 where
572 T: Default,
573 {
574 let remaining = self.capacity() - self.len();
575 let mut source: Vec<T> = (0..remaining).map(|_| T::default()).collect();
576 self.drain_from(&mut source)
577 .expect("infallible: remaining = capacity - len");
578 }
579
580 /// Changes the test behaviour for this vector.
581 ///
582 /// This is only available with the `test-utils` feature and allows injecting
583 /// failures for testing error handling paths.
584 ///
585 /// # Example
586 ///
587 /// ```rust
588 /// // test-utils feature required in dev-dependencies
589 /// #[cfg(test)]
590 /// mod tests {
591 /// use redoubt_alloc::{AllockedVec, AllockedVecBehaviour};
592 ///
593 /// #[test]
594 /// fn test_error_handling() {
595 /// let mut vec = AllockedVec::with_capacity(10);
596 /// vec.change_behaviour(AllockedVecBehaviour::FailAtPush);
597 ///
598 /// // Next push will fail
599 /// assert!(vec.push(1u8).is_err());
600 /// }
601 /// }
602 /// ```
603 #[cfg(any(test, feature = "test-utils"))]
604 pub fn change_behaviour(&mut self, behaviour: AllockedVecBehaviour) {
605 self.behaviour = behaviour;
606 }
607
608 #[cfg(test)]
609 pub(crate) fn __unsafe_expose_inner_for_tests<F>(&mut self, f: F)
610 where
611 F: FnOnce(&mut Vec<T>),
612 {
613 f(&mut self.inner);
614 }
615
616 /// Returns a raw mutable pointer to the vector's buffer.
617 ///
618 /// # Safety
619 ///
620 /// This method is only available with the `unsafe` feature.
621 #[cfg(any(test, feature = "unsafe"))]
622 #[inline(always)]
623 pub fn as_mut_ptr(&mut self) -> *mut T {
624 self.inner.as_mut_ptr()
625 }
626
627 /// Returns a slice of the full capacity, regardless of len.
628 ///
629 /// # Safety
630 ///
631 /// This method is only available with the `unsafe` feature.
632 /// Elements beyond `len()` may not be properly initialized.
633 /// The caller must ensure that accessing elements beyond `len()` is valid for type `T`.
634 #[cfg(any(test, feature = "unsafe"))]
635 #[inline(always)]
636 pub unsafe fn as_capacity_slice(&self) -> &[T] {
637 unsafe { core::slice::from_raw_parts(self.inner.as_ptr(), self.inner.capacity()) }
638 }
639
640 /// Returns a mutable slice of the full capacity, regardless of len.
641 ///
642 /// # Safety
643 ///
644 /// This method is only available with the `unsafe` feature.
645 /// Elements beyond `len()` may not be properly initialized.
646 /// The caller must ensure that accessing elements beyond `len()` is valid for type `T`.
647 #[cfg(any(test, feature = "unsafe"))]
648 #[inline(always)]
649 pub unsafe fn as_capacity_mut_slice(&mut self) -> &mut [T] {
650 unsafe { core::slice::from_raw_parts_mut(self.inner.as_mut_ptr(), self.inner.capacity()) }
651 }
652
653 /// Sets the length of the vector without any checks.
654 ///
655 /// This is useful after operations like `zeroize()` that clear the vector's
656 /// length but preserve the underlying data, allowing the length to be restored.
657 ///
658 /// # Safety
659 ///
660 /// - `new_len` must be less than or equal to `capacity()`.
661 /// - Elements at indices `0..new_len` must be properly initialized.
662 /// - This method is only available with the `unsafe` feature.
663 ///
664 /// # Example
665 ///
666 /// ```rust
667 /// use redoubt_alloc::AllockedVec;
668 /// use redoubt_zero::FastZeroizable;
669 ///
670 /// let mut vec = AllockedVec::with_capacity(5);
671 /// vec.push(1u8).unwrap();
672 /// vec.push(2u8).unwrap();
673 /// assert_eq!(vec.len(), 2);
674 ///
675 /// let old_len = vec.len();
676 /// vec.fast_zeroize();
677 ///
678 /// assert_eq!(vec.len(), 2);
679 /// assert_eq!(vec.as_slice(), &[0, 0]); // Data was zeroized
680 /// ```
681 #[cfg(any(test, feature = "unsafe"))]
682 #[inline(always)]
683 pub unsafe fn set_len(&mut self, new_len: usize) {
684 debug_assert!(new_len <= self.capacity());
685 unsafe { self.inner.set_len(new_len) };
686 }
687}
688
689impl<T> Default for AllockedVec<T>
690where
691 T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
692{
693 fn default() -> Self {
694 Self::new()
695 }
696}
697
698impl<T> core::ops::Deref for AllockedVec<T>
699where
700 T: FastZeroizable + ZeroizeMetadata + ZeroizationProbe,
701{
702 type Target = [T];
703
704 fn deref(&self) -> &Self::Target {
705 &self.inner
706 }
707}