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}