Skip to main content

nu_plugin_secret/
memory_optimizations.rs

1//! Memory optimization utilities for the secret plugin
2//!
3//! This module contains optimizations to reduce memory usage and improve
4//! performance while maintaining security guarantees.
5
6// Removed unnecessary wrapper functions that only added indirection
7// Use crate::redaction functions directly instead
8
9/// Memory pool for small allocations
10/// This reduces allocation overhead for small secret values
11pub 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    /// Get a pre-allocated string from the pool or create a new one
31    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    /// Return a string to the pool for reuse
48    pub fn return_string(&mut self, mut s: String) {
49        // Clear content but keep capacity
50        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        // If pools are full, just drop the string
63    }
64}
65
66impl Default for SecretMemoryPool {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72/// Optimize binary data storage for common patterns
73pub mod binary_optimization {
74    use std::borrow::Cow;
75    use zeroize::Zeroize;
76
77    /// Common binary patterns that can be stored more efficiently
78    #[derive(Clone)]
79    pub enum OptimizedBinary {
80        /// All zeros pattern - store just the length
81        Zeros(usize),
82        /// All ones pattern - store just the length  
83        Ones(usize),
84        /// Repeating byte pattern
85        Repeated(u8, usize),
86        /// Small binary data (inline storage)
87        Small([u8; 32], usize),
88        /// Large binary data (heap allocated)
89        Large(Vec<u8>),
90    }
91
92    impl OptimizedBinary {
93        /// Create optimized binary from a slice
94        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            // Check for common patterns
102            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        /// Get the data as a Cow (clone-on-write)
120        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        /// Get the length without reconstructing the data
131        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        /// Check if empty
140        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                    // These patterns don't contain actual secret data
150                }
151                Self::Small(data, len) => {
152                    // Zero the actual used portion
153                    data[..*len].zeroize();
154                    *len = 0;
155                }
156                Self::Large(data) => {
157                    data.zeroize();
158                }
159            }
160        }
161    }
162}
163
164/// Memory usage statistics for monitoring
165#[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        // Round up to ensure non-zero for small allocations
191        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        // Round up to ensure non-zero for small allocations
198        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        // Simple heuristic: secrets should be efficient compared to plain storage
206        let baseline_kb = self.total_secrets * 64 / 1024; // Assume 64 bytes per secret baseline
207        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        // Should return correct Tera-templated format
231        assert_eq!(s1, "<redacted:string>");
232        assert_eq!(s2, "<redacted:string>");
233
234        // Test other types
235        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        // Test zeros optimization
254        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        // Test ones optimization
260        let ones = vec![0xFF; 500];
261        let opt_ones = OptimizedBinary::from_slice(&ones);
262        assert!(matches!(opt_ones, OptimizedBinary::Ones(500)));
263
264        // Test repeated pattern optimization
265        let repeated = vec![0xAA; 200];
266        let opt_repeated = OptimizedBinary::from_slice(&repeated);
267        assert!(matches!(opt_repeated, OptimizedBinary::Repeated(0xAA, 200)));
268
269        // Test small data optimization
270        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        // Get some strings
280        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        // Return them to the pool
289        pool.return_string(s1);
290        pool.return_string(s2);
291        pool.return_string(s3);
292
293        // Get them back (should reuse)
294        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        // Test without configuration - should fallback to static strings
317        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        // Test with None value - should still return redacted text
325        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        // Test with integer
338        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        // Test with boolean
346        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        // Test with float
354        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        // Test the new length-aware function
365        let result = crate::redaction::get_redacted_string_with_length("password", Some(12));
366        assert_eq!(result, "<redacted:password>");
367
368        // Test without length
369        let result = crate::redaction::get_redacted_string_with_length("token", None);
370        assert_eq!(result, "<redacted:token>");
371    }
372}