secure_gate/fixed.rs
1// src/fixed.rs
2//! Stack-allocated, zero-cost secure wrappers for fixed-size secrets.
3//!
4//! `Fixed<T>` is a transparent wrapper around any type `T` that lives entirely on the stack.
5//! It provides:
6//! - Zero-cost abstraction via `Deref`/`DerefMut`.
7//! - Automatic redaction in `Debug` output.
8//! - Explicit access via `.expose_secret()` (canonical API).
9//! - Specialized ergonomics for `[u8; N]` arrays (e.g., crypto keys, nonces).
10//!
11//! # Examples
12//!
13//! ```
14//! use secure_gate::{fixed_alias, Fixed};
15//!
16//! fixed_alias!(Aes256Key, 32);
17//!
18//! let raw_key = [42u8; 32]; // In real code: use rand::Rng::gen()
19//! let key: Aes256Key = raw_key.into();
20//!
21//! assert_eq!(key.expose_secret()[0], 42);
22//! ```
23
24use core::convert::From;
25use core::ops::{Deref, DerefMut};
26
27/// A zero-cost, stack-allocated wrapper for sensitive data.
28///
29/// `Fixed<T>` stores its value directly in the struct (no heap allocation).
30/// It behaves exactly like `T` via `Deref`/`DerefMut`, but redacts itself
31/// in debug output and requires explicit access to the inner value.
32///
33/// Use this for fixed-size secrets like encryption keys or nonces.
34///
35/// # Examples
36///
37/// ```
38/// use secure_gate::Fixed;
39///
40/// let secret: Fixed<[u8; 4]> = [1, 2, 3, 4].into();
41/// assert_eq!(secret.expose_secret(), &[1, 2, 3, 4]);
42/// ```
43pub struct Fixed<T>(pub T);
44
45impl<T> Fixed<T> {
46 /// Creates a new `Fixed` wrapper around the given value.
47 ///
48 /// This is zero-cost and usually not called directly—prefer `fixed_alias!` + `.into()`.
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// use secure_gate::Fixed;
54 ///
55 /// let secret = Fixed::new([42u8; 32]);
56 /// assert_eq!(secret.len(), 32);
57 /// ```
58 #[inline(always)]
59 pub const fn new(value: T) -> Self {
60 Fixed(value)
61 }
62}
63
64impl<T> Deref for Fixed<T> {
65 type Target = T;
66
67 /// Dereferences the wrapper to access the inner value immutably.
68 #[inline(always)]
69 fn deref(&self) -> &T {
70 &self.0
71 }
72}
73
74impl<T> DerefMut for Fixed<T> {
75 /// Dereferences the wrapper mutably to access the inner value.
76 #[inline(always)]
77 fn deref_mut(&mut self) -> &mut T {
78 &mut self.0
79 }
80}
81
82/// Converts a byte slice into a fixed-size secret array.
83///
84/// # Panics
85///
86/// Panics if `bytes.len() != N` with the message "slice length mismatch".
87///
88/// # Examples
89///
90/// ```
91/// use secure_gate::Fixed;
92///
93/// let bytes = [42u8; 32];
94/// let secret = Fixed::from_slice(&bytes);
95/// assert_eq!(secret.expose_secret(), &[42u8; 32]);
96/// ```
97impl<const N: usize> Fixed<[u8; N]> {
98 #[inline]
99 pub fn from_slice(bytes: &[u8]) -> Self {
100 assert_eq!(bytes.len(), N, "slice length mismatch");
101 let mut arr = [0u8; N];
102 arr.copy_from_slice(&bytes[..N]);
103 Self::new(arr)
104 }
105}
106
107/// Converts a raw array into a fixed-size secret.
108///
109/// Enables idiomatic construction like `Aes256Key::from(rng.gen())`.
110///
111/// # Examples
112///
113/// ```
114/// use secure_gate::Fixed;
115///
116/// let secret: Fixed<[u8; 4]> = [1, 2, 3, 4].into();
117/// assert_eq!(secret.expose_secret(), &[1, 2, 3, 4]);
118/// ```
119impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
120 #[inline(always)]
121 fn from(arr: [u8; N]) -> Self {
122 Self::new(arr)
123 }
124}
125
126/// Borrows the fixed byte array as a slice.
127///
128/// Useful for passing to crypto APIs expecting `&[u8]`.
129///
130/// # Examples
131///
132/// ```
133/// use secure_gate::Fixed;
134///
135/// let secret: Fixed<[u8; 4]> = [1, 2, 3, 4].into();
136/// let slice: &[u8] = secret.as_ref();
137/// assert_eq!(slice, &[1, 2, 3, 4]);
138/// ```
139impl<const N: usize> AsRef<[u8]> for Fixed<[u8; N]> {
140 #[inline(always)]
141 fn as_ref(&self) -> &[u8] {
142 &self.0
143 }
144}
145
146/// Mutably borrows the fixed byte array as a slice.
147///
148/// Useful for in-place modifications like key scheduling.
149///
150/// # Examples
151///
152/// ```
153/// use secure_gate::Fixed;
154///
155/// let mut secret: Fixed<[u8; 4]> = [1, 2, 3, 4].into();
156/// let slice: &mut [u8] = secret.as_mut();
157/// slice[0] = 42;
158/// assert_eq!(secret.expose_secret(), &[42, 2, 3, 4]);
159/// ```
160impl<const N: usize> AsMut<[u8]> for Fixed<[u8; N]> {
161 #[inline(always)]
162 fn as_mut(&mut self) -> &mut [u8] {
163 &mut self.0
164 }
165}
166
167/// All `Fixed<T>` values print as "[REDACTED]" to prevent accidental leakage.
168impl<T> core::fmt::Debug for Fixed<T> {
169 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
170 f.write_str("[REDACTED]")
171 }
172}
173
174impl<T> Fixed<T> {
175 /// Accesses the secret value immutably.
176 ///
177 /// This is the canonical, explicit way to read the secret.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use secure_gate::Fixed;
183 ///
184 /// let secret: Fixed<i32> = Fixed::new(42);
185 /// assert_eq!(*secret.expose_secret(), 42);
186 /// ```
187 #[inline(always)]
188 pub fn expose_secret(&self) -> &T {
189 &self.0
190 }
191
192 /// Accesses the secret value mutably.
193 ///
194 /// Use for in-place modifications.
195 ///
196 /// # Examples
197 ///
198 /// ```
199 /// use secure_gate::Fixed;
200 ///
201 /// let mut secret: Fixed<i32> = Fixed::new(42);
202 /// *secret.expose_secret_mut() += 1;
203 /// assert_eq!(*secret.expose_secret(), 43);
204 /// ```
205 #[inline(always)]
206 pub fn expose_secret_mut(&mut self) -> &mut T {
207 &mut self.0
208 }
209
210 /// **Deprecated**: Use [`expose_secret`] instead.
211 ///
212 /// This method forwards to [`expose_secret`] for compatibility.
213 #[deprecated(since = "0.5.5", note = "use `expose_secret` instead")]
214 #[doc(hidden)]
215 #[inline(always)]
216 pub fn view(&self) -> &T {
217 self.expose_secret()
218 }
219
220 /// **Deprecated**: Use [`expose_secret_mut`] instead.
221 ///
222 /// This method forwards to [`expose_secret_mut`] for compatibility.
223 #[deprecated(since = "0.5.5", note = "use `expose_secret_mut` instead")]
224 #[doc(hidden)]
225 #[inline(always)]
226 pub fn view_mut(&mut self) -> &mut T {
227 self.expose_secret_mut()
228 }
229
230 /// Consumes the wrapper and returns the inner value.
231 ///
232 /// Useful for passing ownership to functions expecting `T`.
233 ///
234 /// # Examples
235 ///
236 /// ```
237 /// use secure_gate::Fixed;
238 ///
239 /// let secret: Fixed<i32> = Fixed::new(42);
240 /// let value: i32 = secret.into_inner();
241 /// assert_eq!(value, 42);
242 /// ```
243 #[inline(always)]
244 pub fn into_inner(self) -> T {
245 self.0
246 }
247}
248
249/// Implements `Clone` when the inner type is `Clone`.
250impl<T: Clone> Clone for Fixed<T> {
251 #[inline(always)]
252 fn clone(&self) -> Self {
253 Self(self.0.clone())
254 }
255}
256
257/// Implements `Copy` for small fixed-size byte arrays.
258impl<const N: usize> Copy for Fixed<[u8; N]> where [u8; N]: Copy {}