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> {}