secure_gate/
no_clone.rs

1// ==========================================================================
2// src/no_clone.rs
3// ==========================================================================
4
5extern crate alloc;
6
7use alloc::boxed::Box;
8use core::fmt;
9
10/// Non-cloneable stack-allocated secret wrapper.
11///
12/// This is a zero-cost newtype over `Fixed<T>` that deliberately omits `Clone` and `Copy`.
13/// Use this when you want to enforce single-ownership and prevent accidental duplication of secrets.
14///
15/// Converts from `Fixed<T>` via `.no_clone()`.
16///
17/// # Examples
18///
19/// ```
20/// use secure_gate::{Fixed, FixedNoClone};
21/// let secret = Fixed::new([1u8; 32]);
22/// let no_clone: FixedNoClone<[u8; 32]> = secret.no_clone();
23/// // no_clone cannot be cloned
24/// assert_eq!(no_clone.expose_secret()[0], 1);
25/// ```
26///
27/// With `zeroize`:
28/// ```
29/// # #[cfg(feature = "zeroize")]
30/// # {
31/// use secure_gate::FixedNoClone;
32/// let mut secret = FixedNoClone::new([1u8, 2, 3]);
33/// drop(secret); // wiped on drop
34/// # }
35/// ```
36pub struct FixedNoClone<T>(T);
37
38/// Non-cloneable heap-allocated secret wrapper.
39///
40/// This is a thin newtype over `Dynamic<T>` that deliberately omits `Clone`.
41/// Use this for dynamic secrets where duplication must be prevented.
42///
43/// Converts from `Dynamic<T>` via `.no_clone()`.
44///
45/// # Examples
46///
47/// ```
48/// use secure_gate::{Dynamic, DynamicNoClone};
49/// let secret = Dynamic::new("hunter2".to_string());
50/// let no_clone: DynamicNoClone<String> = secret.no_clone();
51/// // no_clone cannot be cloned
52/// assert_eq!(no_clone.expose_secret(), "hunter2");
53/// ```
54pub struct DynamicNoClone<T: ?Sized>(Box<T>);
55
56impl<T> FixedNoClone<T> {
57    /// Wrap a value in a non-cloneable fixed secret.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// use secure_gate::FixedNoClone;
63    /// let secret = FixedNoClone::new(42u32);
64    /// ```
65    #[inline(always)]
66    pub const fn new(value: T) -> Self {
67        FixedNoClone(value)
68    }
69
70    /// Expose the inner value for read-only access.
71    #[inline(always)]
72    pub const fn expose_secret(&self) -> &T {
73        &self.0
74    }
75
76    /// Expose the inner value for mutable access.
77    #[inline(always)]
78    pub fn expose_secret_mut(&mut self) -> &mut T {
79        &mut self.0
80    }
81
82    /// Consume and return the inner value.
83    #[inline(always)]
84    pub fn into_inner(self) -> T {
85        self.0
86    }
87}
88
89impl<T: ?Sized> DynamicNoClone<T> {
90    /// Wrap a boxed value in a non-cloneable dynamic secret.
91    ///
92    /// # Example
93    ///
94    /// ```
95    /// use secure_gate::DynamicNoClone;
96    /// let boxed = Box::new("secret".to_string());
97    /// let no_clone = DynamicNoClone::new(boxed);
98    /// ```
99    #[inline(always)]
100    pub fn new(value: Box<T>) -> Self {
101        DynamicNoClone(value)
102    }
103
104    /// Expose the inner value for read-only access.
105    #[inline(always)]
106    pub const fn expose_secret(&self) -> &T {
107        &self.0
108    }
109
110    /// Expose the inner value for mutable access.
111    #[inline(always)]
112    pub fn expose_secret_mut(&mut self) -> &mut T {
113        &mut self.0
114    }
115
116    /// Consume and return the inner `Box<T>`.
117    #[inline(always)]
118    pub fn into_inner(self) -> Box<T> {
119        self.0
120    }
121}
122
123impl<T> fmt::Debug for FixedNoClone<T> {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        f.write_str("[REDACTED_NO_CLONE]")
126    }
127}
128
129impl<T: ?Sized> fmt::Debug for DynamicNoClone<T> {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        f.write_str("[REDACTED_NO_CLONE]")
132    }
133}
134
135// === Ergonomic helpers for common heap types ===
136
137impl DynamicNoClone<String> {
138    /// Get a mutable reference and shrink spare capacity.
139    ///
140    /// Similar to `Dynamic::finish_mut()`.
141    pub fn finish_mut(&mut self) -> &mut String {
142        let s = &mut *self.0;
143        s.shrink_to_fit();
144        s
145    }
146
147    /// Returns the length of the secret string in bytes (UTF-8).
148    #[inline(always)]
149    pub const fn len(&self) -> usize {
150        self.0.len()
151    }
152
153    /// Returns true if the secret string is empty.
154    #[inline(always)]
155    pub const fn is_empty(&self) -> bool {
156        self.0.is_empty()
157    }
158}
159
160impl<T> DynamicNoClone<Vec<T>> {
161    /// Get a mutable reference and shrink spare capacity.
162    pub fn finish_mut(&mut self) -> &mut Vec<T> {
163        let v = &mut *self.0;
164        v.shrink_to_fit();
165        v
166    }
167
168    /// Returns the length of the secret vector in elements.
169    #[inline(always)]
170    pub const fn len(&self) -> usize {
171        self.0.len()
172    }
173
174    /// Returns true if the secret vector is empty.
175    #[inline(always)]
176    pub const fn is_empty(&self) -> bool {
177        self.0.is_empty()
178    }
179
180    /// Returns a shared slice of the secret bytes.
181    ///
182    /// Requires explicit intent — consistent with the crate's philosophy.
183    #[inline(always)]
184    pub fn as_slice(&self) -> &[T] {
185        self.expose_secret()
186    }
187}
188
189#[cfg(feature = "zeroize")]
190use zeroize::{Zeroize, ZeroizeOnDrop};
191
192#[cfg(feature = "zeroize")]
193impl<T: Zeroize> Zeroize for FixedNoClone<T> {
194    fn zeroize(&mut self) {
195        self.0.zeroize();
196    }
197}
198
199#[cfg(feature = "zeroize")]
200impl<T: ?Sized + Zeroize> Zeroize for DynamicNoClone<T> {
201    fn zeroize(&mut self) {
202        self.0.zeroize();
203    }
204}
205
206#[cfg(feature = "zeroize")]
207impl<T: Zeroize> ZeroizeOnDrop for FixedNoClone<T> {}
208
209#[cfg(feature = "zeroize")]
210impl<T: ?Sized + Zeroize> ZeroizeOnDrop for DynamicNoClone<T> {}