1pub struct SecretMemoryPool {
12 small_strings: Vec<String>,
13 medium_strings: Vec<String>,
14 large_strings: Vec<String>,
15}
16
17impl SecretMemoryPool {
18 const SMALL_SIZE: usize = 64;
19 const MEDIUM_SIZE: usize = 1024;
20 const POOL_INITIAL_CAPACITY: usize = 16;
21
22 pub fn new() -> Self {
23 Self {
24 small_strings: Vec::with_capacity(Self::POOL_INITIAL_CAPACITY),
25 medium_strings: Vec::with_capacity(Self::POOL_INITIAL_CAPACITY),
26 large_strings: Vec::with_capacity(Self::POOL_INITIAL_CAPACITY),
27 }
28 }
29
30 pub fn get_string(&mut self, size_hint: usize) -> String {
32 if size_hint <= Self::SMALL_SIZE {
33 self.small_strings
34 .pop()
35 .unwrap_or_else(|| String::with_capacity(Self::SMALL_SIZE))
36 } else if size_hint <= Self::MEDIUM_SIZE {
37 self.medium_strings
38 .pop()
39 .unwrap_or_else(|| String::with_capacity(Self::MEDIUM_SIZE))
40 } else {
41 self.large_strings
42 .pop()
43 .unwrap_or_else(|| String::with_capacity(size_hint))
44 }
45 }
46
47 pub fn return_string(&mut self, mut s: String) {
49 s.clear();
51
52 let capacity = s.capacity();
53 if capacity <= Self::SMALL_SIZE && self.small_strings.len() < Self::POOL_INITIAL_CAPACITY {
54 self.small_strings.push(s);
55 } else if capacity <= Self::MEDIUM_SIZE
56 && self.medium_strings.len() < Self::POOL_INITIAL_CAPACITY
57 {
58 self.medium_strings.push(s);
59 } else if self.large_strings.len() < Self::POOL_INITIAL_CAPACITY {
60 self.large_strings.push(s);
61 }
62 }
64}
65
66impl Default for SecretMemoryPool {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72pub mod binary_optimization {
74 use std::borrow::Cow;
75 use zeroize::Zeroize;
76
77 #[derive(Clone)]
79 pub enum OptimizedBinary {
80 Zeros(usize),
82 Ones(usize),
84 Repeated(u8, usize),
86 Small([u8; 32], usize),
88 Large(Vec<u8>),
90 }
91
92 impl OptimizedBinary {
93 pub fn from_slice(data: &[u8]) -> Self {
95 let len = data.len();
96
97 if len == 0 {
98 return Self::Small([0; 32], 0);
99 }
100
101 let first_byte = data[0];
103
104 if data.iter().all(|&b| b == 0) {
105 Self::Zeros(len)
106 } else if data.iter().all(|&b| b == 0xFF) {
107 Self::Ones(len)
108 } else if data.iter().all(|&b| b == first_byte) {
109 Self::Repeated(first_byte, len)
110 } else if len <= 32 {
111 let mut small = [0; 32];
112 small[..len].copy_from_slice(data);
113 Self::Small(small, len)
114 } else {
115 Self::Large(data.to_vec())
116 }
117 }
118
119 pub fn as_bytes(&self) -> Cow<'_, [u8]> {
121 match self {
122 Self::Zeros(len) => Cow::Owned(vec![0; *len]),
123 Self::Ones(len) => Cow::Owned(vec![0xFF; *len]),
124 Self::Repeated(byte, len) => Cow::Owned(vec![*byte; *len]),
125 Self::Small(data, len) => Cow::Borrowed(&data[..*len]),
126 Self::Large(data) => Cow::Borrowed(data),
127 }
128 }
129
130 pub fn len(&self) -> usize {
132 match self {
133 Self::Zeros(len) | Self::Ones(len) | Self::Repeated(_, len) => *len,
134 Self::Small(_, len) => *len,
135 Self::Large(data) => data.len(),
136 }
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.len() == 0
142 }
143 }
144
145 impl Zeroize for OptimizedBinary {
146 fn zeroize(&mut self) {
147 match self {
148 Self::Zeros(_) | Self::Ones(_) | Self::Repeated(_, _) => {
149 }
151 Self::Small(data, len) => {
152 data[..*len].zeroize();
154 *len = 0;
155 }
156 Self::Large(data) => {
157 data.zeroize();
158 }
159 }
160 }
161 }
162}
163
164#[derive(Debug, Clone)]
166pub struct MemoryStats {
167 pub total_secrets: usize,
168 pub string_secrets: usize,
169 pub binary_secrets: usize,
170 pub record_secrets: usize,
171 pub list_secrets: usize,
172 pub estimated_memory_kb: usize,
173}
174
175impl MemoryStats {
176 pub fn new() -> Self {
177 Self {
178 total_secrets: 0,
179 string_secrets: 0,
180 binary_secrets: 0,
181 record_secrets: 0,
182 list_secrets: 0,
183 estimated_memory_kb: 0,
184 }
185 }
186
187 pub fn add_string_secret(&mut self, size: usize) {
188 self.total_secrets += 1;
189 self.string_secrets += 1;
190 self.estimated_memory_kb += (size + std::mem::size_of::<String>()).div_ceil(1024);
192 }
193
194 pub fn add_binary_secret(&mut self, size: usize) {
195 self.total_secrets += 1;
196 self.binary_secrets += 1;
197 self.estimated_memory_kb += (size + std::mem::size_of::<Vec<u8>>()).div_ceil(1024);
199 }
200
201 pub fn memory_efficiency_ratio(&self) -> f64 {
202 if self.total_secrets == 0 {
203 return 1.0;
204 }
205 let baseline_kb = self.total_secrets * 64 / 1024; if baseline_kb == 0 {
208 1.0
209 } else {
210 baseline_kb as f64 / self.estimated_memory_kb.max(1) as f64
211 }
212 }
213}
214
215impl Default for MemoryStats {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_redacted_string_templating() {
227 let s1 = crate::redaction::get_cached_redacted_string(None, "string");
228 let s2 = crate::redaction::get_cached_redacted_string(None, "string");
229
230 assert_eq!(s1, "<redacted:string>");
232 assert_eq!(s2, "<redacted:string>");
233
234 assert_eq!(
236 crate::redaction::get_cached_redacted_string(None, "int"),
237 "<redacted:int>"
238 );
239 assert_eq!(
240 crate::redaction::get_cached_redacted_string(None, "float"),
241 "<redacted:float>"
242 );
243 assert_eq!(
244 crate::redaction::get_cached_redacted_string(None, "bool"),
245 "<redacted:bool>"
246 );
247 }
248
249 #[test]
250 fn test_binary_optimization() {
251 use binary_optimization::OptimizedBinary;
252
253 let zeros = vec![0; 1000];
255 let opt_zeros = OptimizedBinary::from_slice(&zeros);
256 assert!(matches!(opt_zeros, OptimizedBinary::Zeros(1000)));
257 assert_eq!(opt_zeros.len(), 1000);
258
259 let ones = vec![0xFF; 500];
261 let opt_ones = OptimizedBinary::from_slice(&ones);
262 assert!(matches!(opt_ones, OptimizedBinary::Ones(500)));
263
264 let repeated = vec![0xAA; 200];
266 let opt_repeated = OptimizedBinary::from_slice(&repeated);
267 assert!(matches!(opt_repeated, OptimizedBinary::Repeated(0xAA, 200)));
268
269 let small_data = vec![1, 2, 3, 4, 5];
271 let opt_small = OptimizedBinary::from_slice(&small_data);
272 assert!(matches!(opt_small, OptimizedBinary::Small(_, 5)));
273 }
274
275 #[test]
276 fn test_memory_pool() {
277 let mut pool = SecretMemoryPool::new();
278
279 let s1 = pool.get_string(32);
281 let s2 = pool.get_string(100);
282 let s3 = pool.get_string(2000);
283
284 assert!(s1.capacity() >= 32);
285 assert!(s2.capacity() >= 100);
286 assert!(s3.capacity() >= 2000);
287
288 pool.return_string(s1);
290 pool.return_string(s2);
291 pool.return_string(s3);
292
293 let s4 = pool.get_string(30);
295 assert!(s4.capacity() >= 30);
296 }
297
298 #[test]
299 fn test_memory_stats() {
300 let mut stats = MemoryStats::new();
301 assert_eq!(stats.total_secrets, 0);
302
303 stats.add_string_secret(100);
304 stats.add_binary_secret(200);
305
306 assert_eq!(stats.total_secrets, 2);
307 assert_eq!(stats.string_secrets, 1);
308 assert_eq!(stats.binary_secrets, 1);
309 assert!(stats.estimated_memory_kb > 0);
310 }
311
312 #[test]
313 fn test_configurable_redacted_string_with_value() {
314 use crate::config::RedactionContext;
315
316 let result = crate::redaction::get_redacted_string_with_value(
318 "string",
319 RedactionContext::Display,
320 Some("secret_value"),
321 );
322 assert!(result.contains("redacted"));
323
324 let result = crate::redaction::get_redacted_string_with_value::<String>(
326 "string",
327 RedactionContext::Display,
328 None,
329 );
330 assert!(result.contains("redacted"));
331 }
332
333 #[test]
334 fn test_configurable_redacted_string_with_generic_value() {
335 use crate::config::RedactionContext;
336
337 let result = crate::redaction::get_redacted_string_with_value(
339 "int",
340 RedactionContext::Display,
341 Some(&42),
342 );
343 assert!(result.contains("redacted"));
344
345 let result = crate::redaction::get_redacted_string_with_value(
347 "bool",
348 RedactionContext::Display,
349 Some(&true),
350 );
351 assert!(result.contains("redacted"));
352
353 let result = crate::redaction::get_redacted_string_with_value(
355 "float",
356 RedactionContext::Display,
357 Some(&2.5),
358 );
359 assert!(result.contains("redacted"));
360 }
361
362 #[test]
363 fn test_redacted_string_with_length() {
364 let result = crate::redaction::get_redacted_string_with_length("password", Some(12));
366 assert_eq!(result, "<redacted:password>");
367
368 let result = crate::redaction::get_redacted_string_with_length("token", None);
370 assert_eq!(result, "<redacted:token>");
371 }
372}