Skip to main content

confium_observability/
data_structures_and_utils.rs

1//! Probabilistic data structures + efficient primitives + crypto utilities.
2
3use hmac::{Hmac, KeyInit, Mac};
4use sha2::{Digest, Sha256};
5use std::collections::VecDeque;
6use std::sync::Mutex;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9// === Bloom Filter ===
10
11pub struct BloomFilter {
12    bits: Vec<u64>,
13    num_bits: usize,
14    num_hashes: usize,
15    count: AtomicU64,
16}
17
18impl BloomFilter {
19    pub fn new(expected_items: usize, false_positive_rate: f64) -> Self {
20        let num_bits = (-(expected_items as f64 * false_positive_rate.ln())
21            / (std::f64::consts::LN_2.powi(2)))
22        .ceil() as usize;
23        let num_bits = num_bits.max(64);
24        let num_hashes =
25            ((num_bits as f64 / expected_items as f64) * std::f64::consts::LN_2).ceil() as usize;
26        let num_hashes = num_hashes.max(1);
27        let words = num_bits.div_ceil(64);
28        Self {
29            bits: vec![0u64; words],
30            num_bits,
31            num_hashes,
32            count: AtomicU64::new(0),
33        }
34    }
35
36    pub fn insert(&mut self, data: &[u8]) {
37        let (h1, h2) = double_hash(data);
38        for i in 0..self.num_hashes {
39            let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
40            let idx = (combined as usize) % self.num_bits;
41            let word = idx / 64;
42            let bit = idx % 64;
43            self.bits[word] |= 1u64 << bit;
44        }
45        self.count.fetch_add(1, Ordering::SeqCst);
46    }
47
48    pub fn contains(&self, data: &[u8]) -> bool {
49        let (h1, h2) = double_hash(data);
50        for i in 0..self.num_hashes {
51            let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
52            let idx = (combined as usize) % self.num_bits;
53            let word = idx / 64;
54            let bit = idx % 64;
55            if self.bits[word] & (1u64 << bit) == 0 {
56                return false;
57            }
58        }
59        true
60    }
61
62    pub fn estimated_count(&self) -> u64 {
63        self.count.load(Ordering::SeqCst)
64    }
65    pub fn num_bits(&self) -> usize {
66        self.num_bits
67    }
68    pub fn num_hashes(&self) -> usize {
69        self.num_hashes
70    }
71}
72
73fn double_hash(data: &[u8]) -> (u64, u64) {
74    let mut h1 = Sha256::new();
75    h1.update(b"h1");
76    h1.update(data);
77    let r1 = h1.finalize();
78    let mut h2 = Sha256::new();
79    h2.update(b"h2");
80    h2.update(data);
81    let r2 = h2.finalize();
82    (
83        u64::from_be_bytes(r1[..8].try_into().unwrap()),
84        u64::from_be_bytes(r2[..8].try_into().unwrap()),
85    )
86}
87
88// === Count-Min Sketch ===
89
90pub struct CountMinSketch {
91    table: Vec<Vec<AtomicU64>>,
92    width: usize,
93    depth: usize,
94}
95
96impl CountMinSketch {
97    pub fn new(width: usize, depth: usize) -> Self {
98        Self {
99            table: (0..depth)
100                .map(|_| (0..width).map(|_| AtomicU64::new(0)).collect())
101                .collect(),
102            width,
103            depth,
104        }
105    }
106
107    pub fn add(&self, data: &[u8], count: u64) {
108        for (d, row) in self.table.iter().enumerate() {
109            let idx = hash_at_depth(data, d, self.width);
110            row[idx].fetch_add(count, Ordering::SeqCst);
111        }
112    }
113
114    pub fn estimate(&self, data: &[u8]) -> u64 {
115        self.table
116            .iter()
117            .enumerate()
118            .map(|(d, row)| {
119                let idx = hash_at_depth(data, d, self.width);
120                row[idx].load(Ordering::SeqCst)
121            })
122            .min()
123            .unwrap_or(0)
124    }
125}
126
127fn hash_at_depth(data: &[u8], depth: usize, width: usize) -> usize {
128    let mut h = Sha256::new();
129    h.update(depth.to_be_bytes());
130    h.update(data);
131    let result = h.finalize();
132    u64::from_be_bytes(result[..8].try_into().unwrap()) as usize % width
133}
134
135// === Ring Buffer ===
136
137pub struct RingBuffer<T: Clone> {
138    data: VecDeque<T>,
139    capacity: usize,
140}
141
142impl<T: Clone> RingBuffer<T> {
143    pub fn new(capacity: usize) -> Self {
144        Self {
145            data: VecDeque::with_capacity(capacity),
146            capacity,
147        }
148    }
149
150    pub fn push(&mut self, item: T) {
151        if self.data.len() >= self.capacity {
152            self.data.pop_front();
153        }
154        self.data.push_back(item);
155    }
156
157    pub fn latest(&self) -> Option<&T> {
158        self.data.back()
159    }
160    pub fn oldest(&self) -> Option<&T> {
161        self.data.front()
162    }
163    pub fn len(&self) -> usize {
164        self.data.len()
165    }
166    pub fn is_empty(&self) -> bool {
167        self.data.is_empty()
168    }
169    pub fn is_full(&self) -> bool {
170        self.data.len() == self.capacity
171    }
172    pub fn iter(&self) -> impl Iterator<Item = &T> {
173        self.data.iter()
174    }
175    pub fn clear(&mut self) {
176        self.data.clear();
177    }
178    pub fn as_vec(&self) -> Vec<T> {
179        self.data.iter().cloned().collect()
180    }
181}
182
183// === Test Fixture Factory ===
184
185pub struct TestFixtures;
186
187impl TestFixtures {
188    pub fn random_bytes(n: usize) -> Vec<u8> {
189        use rand_core::{OsRng, RngCore};
190        let mut buf = vec![0u8; n];
191        OsRng.fill_bytes(&mut buf);
192        buf
193    }
194
195    pub fn random_hex(n: usize) -> String {
196        hex::encode(Self::random_bytes(n))
197    }
198
199    pub fn random_message() -> Vec<u8> {
200        Self::random_bytes(32)
201    }
202
203    pub fn random_session_id() -> String {
204        format!("session-{}", hex::encode(Self::random_bytes(4)))
205    }
206
207    pub fn random_signer_id() -> String {
208        format!("signer-{}", hex::encode(Self::random_bytes(4)))
209    }
210
211    pub fn random_quorum_id() -> String {
212        format!("quorum-{}", hex::encode(Self::random_bytes(4)))
213    }
214
215    pub fn fake_signature() -> Vec<u8> {
216        Self::random_bytes(64)
217    }
218    pub fn fake_share() -> Vec<u8> {
219        Self::random_bytes(32)
220    }
221    pub fn fake_public_key() -> Vec<u8> {
222        Self::random_bytes(33)
223    }
224
225    // make_session_request lives in confium-tc-core or confium-coordinator, not here.
226
227    pub fn batch_messages(n: usize) -> Vec<Vec<u8>> {
228        (0..n)
229            .map(|i| {
230                let mut msg = vec![0u8; 32];
231                msg[0] = i as u8;
232                msg
233            })
234            .collect()
235    }
236}
237
238// === HKDF (RFC 5869) ===
239
240pub fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; 32] {
241    let salt = if salt.is_empty() {
242        &[0u8; 32][..]
243    } else {
244        salt
245    };
246    let mut mac = Hmac::<Sha256>::new_from_slice(salt).expect("HMAC");
247    mac.update(ikm);
248    let result = mac.finalize().into_bytes();
249    let mut prk = [0u8; 32];
250    prk.copy_from_slice(&result);
251    prk
252}
253
254/// Maximum HKDF-SHA256 output length per RFC 5869 §2.3:
255/// 255 × HashLen = 255 × 32 bytes.
256pub const HKDF_SHA256_MAX_OUTPUT: usize = 255 * 32;
257
258/// Errors from HKDF expansion.
259#[derive(Debug, thiserror::Error)]
260pub enum HkdfError {
261    /// Requested output exceeds the RFC 5869 limit of 255 × HashLen.
262    /// The previous implementation's u8 block counter wrapped here,
263    /// silently producing repeating (non-uniform) key material.
264    #[error("HKDF output length {requested} exceeds RFC 5869 max of {max}")]
265    OutputTooLong { requested: usize, max: usize },
266}
267
268pub fn hkdf_expand(prk: &[u8; 32], info: &[u8], length: usize) -> Result<Vec<u8>, HkdfError> {
269    if length > HKDF_SHA256_MAX_OUTPUT {
270        return Err(HkdfError::OutputTooLong {
271            requested: length,
272            max: HKDF_SHA256_MAX_OUTPUT,
273        });
274    }
275    let mut output = Vec::with_capacity(length);
276    let mut previous: Vec<u8> = Vec::new();
277    // u16 so the trailing increment past the final (255th) block
278    // can't overflow the way a u8 counter would.
279    let mut counter: u16 = 1;
280    while output.len() < length {
281        let mut mac = Hmac::<Sha256>::new_from_slice(prk).expect("HMAC");
282        mac.update(&previous);
283        mac.update(info);
284        mac.update(&[counter as u8]);
285        previous = mac.finalize().into_bytes().to_vec();
286        let take = previous.len().min(length - output.len());
287        output.extend_from_slice(&previous[..take]);
288        counter += 1;
289    }
290    Ok(output)
291}
292
293pub fn hkdf(salt: &[u8], ikm: &[u8], info: &[u8], length: usize) -> Result<Vec<u8>, HkdfError> {
294    let prk = hkdf_extract(salt, ikm);
295    hkdf_expand(&prk, info, length)
296}
297
298// === Key Wrapping (RFC 3394 style simplified) ===
299
300pub fn key_wrap(kek: &[u8; 32], plaintext: &[u8]) -> Vec<u8> {
301    // Simplified: XOR-based wrapping with integrity
302    let mut mac = Hmac::<Sha256>::new_from_slice(kek).expect("HMAC");
303    mac.update(plaintext);
304    let tag = mac.finalize().into_bytes();
305    let mut wrapped = Vec::with_capacity(plaintext.len() + 32);
306    for (i, &b) in plaintext.iter().enumerate() {
307        wrapped.push(b ^ kek[i % 32]);
308    }
309    wrapped.extend_from_slice(&tag[..16]);
310    wrapped
311}
312
313pub fn key_unwrap(kek: &[u8; 32], wrapped: &[u8]) -> Option<Vec<u8>> {
314    if wrapped.len() < 16 {
315        return None;
316    }
317    let body = &wrapped[..wrapped.len() - 16];
318    let tag = &wrapped[wrapped.len() - 16..];
319    let unwrapped: Vec<u8> = body
320        .iter()
321        .enumerate()
322        .map(|(i, &b)| b ^ kek[i % 32])
323        .collect();
324    let mut mac = Hmac::<Sha256>::new_from_slice(kek).expect("HMAC");
325    mac.update(&unwrapped);
326    let expected = &mac.finalize().into_bytes()[..16];
327    if tag == expected {
328        Some(unwrapped)
329    } else {
330        None
331    }
332}
333
334// === Secure File Deletion ===
335
336pub fn secure_overwrite(data: &mut [u8]) {
337    // Multi-pass overwrite: zeros, ones, random
338    for b in data.iter_mut() {
339        *b = 0x00;
340    }
341    for b in data.iter_mut() {
342        *b = 0xFF;
343    }
344    use rand_core::{OsRng, RngCore};
345    for chunk in data.chunks_mut(1) {
346        let mut buf = [0u8; 1];
347        OsRng.fill_bytes(&mut buf);
348        chunk[0] = buf[0];
349    }
350}
351
352pub struct SecureBuffer {
353    data: Vec<u8>,
354    zeroized: bool,
355}
356
357impl SecureBuffer {
358    pub fn new(data: Vec<u8>) -> Self {
359        Self {
360            data,
361            zeroized: false,
362        }
363    }
364    pub fn as_slice(&self) -> &[u8] {
365        &self.data
366    }
367    pub fn len(&self) -> usize {
368        self.data.len()
369    }
370    pub fn is_empty(&self) -> bool {
371        self.data.is_empty()
372    }
373
374    pub fn zeroize(&mut self) {
375        if !self.zeroized {
376            secure_overwrite(&mut self.data);
377            self.zeroized = true;
378        }
379    }
380
381    pub fn is_zeroized(&self) -> bool {
382        self.zeroized
383    }
384}
385
386impl Drop for SecureBuffer {
387    fn drop(&mut self) {
388        if !self.zeroized {
389            secure_overwrite(&mut self.data);
390        }
391    }
392}
393
394// === Entropy Pool Monitor ===
395
396pub struct EntropyMonitor {
397    samples: Mutex<VecDeque<u32>>,
398    max_samples: usize,
399    min_threshold: u32,
400    alerts: AtomicU64,
401}
402
403impl EntropyMonitor {
404    pub fn new(max_samples: usize, min_threshold: u32) -> Self {
405        Self {
406            samples: Mutex::new(VecDeque::with_capacity(max_samples)),
407            max_samples,
408            min_threshold,
409            alerts: AtomicU64::new(0),
410        }
411    }
412
413    pub fn record(&self, entropy_bits: u32) {
414        let mut samples = self.samples.lock().unwrap();
415        if samples.len() >= self.max_samples {
416            samples.pop_front();
417        }
418        samples.push_back(entropy_bits);
419        if entropy_bits < self.min_threshold {
420            self.alerts.fetch_add(1, Ordering::SeqCst);
421        }
422    }
423
424    pub fn latest(&self) -> Option<u32> {
425        self.samples.lock().unwrap().back().copied()
426    }
427    pub fn average(&self) -> f64 {
428        let s = self.samples.lock().unwrap();
429        if s.is_empty() {
430            return 0.0;
431        }
432        s.iter().sum::<u32>() as f64 / s.len() as f64
433    }
434    pub fn min(&self) -> Option<u32> {
435        self.samples.lock().unwrap().iter().copied().min()
436    }
437    pub fn alert_count(&self) -> u64 {
438        self.alerts.load(Ordering::SeqCst)
439    }
440    pub fn is_low(&self) -> bool {
441        self.latest()
442            .map(|e| e < self.min_threshold)
443            .unwrap_or(false)
444    }
445    pub fn sample_count(&self) -> usize {
446        self.samples.lock().unwrap().len()
447    }
448}
449
450// === Burst Token Bucket ===
451
452pub struct BurstTokenBucket {
453    tokens: Mutex<f64>,
454    capacity: f64,
455    refill_rate: f64, // tokens per second
456    burst_capacity: f64,
457    last_refill: Mutex<std::time::Instant>,
458}
459
460impl BurstTokenBucket {
461    pub fn new(sustained_rate: f64, burst_multiplier: f64) -> Self {
462        let capacity = sustained_rate * burst_multiplier;
463        Self {
464            tokens: Mutex::new(capacity),
465            capacity,
466            refill_rate: sustained_rate,
467            burst_capacity: capacity,
468            last_refill: Mutex::new(std::time::Instant::now()),
469        }
470    }
471
472    fn refill(&self) {
473        let mut tokens = self.tokens.lock().unwrap();
474        let mut last = self.last_refill.lock().unwrap();
475        let elapsed = last.elapsed().as_secs_f64();
476        *tokens = (*tokens + elapsed * self.refill_rate).min(self.capacity);
477        *last = std::time::Instant::now();
478    }
479
480    pub fn try_consume(&self, cost: f64) -> bool {
481        self.refill();
482        let mut tokens = self.tokens.lock().unwrap();
483        if *tokens >= cost {
484            *tokens -= cost;
485            true
486        } else {
487            false
488        }
489    }
490
491    pub fn available_tokens(&self) -> f64 {
492        self.refill();
493        *self.tokens.lock().unwrap()
494    }
495
496    pub fn burst_capacity(&self) -> f64 {
497        self.burst_capacity
498    }
499    pub fn sustained_rate(&self) -> f64 {
500        self.refill_rate
501    }
502}
503
504// === Concurrent Test Runner ===
505
506pub struct ConcurrentTestRunner {
507    results: Mutex<Vec<ConcTestResult>>,
508}
509
510#[derive(Debug, Clone)]
511pub struct ConcTestResult {
512    pub name: String,
513    pub passed: bool,
514    pub duration_micros: u64,
515}
516
517impl ConcurrentTestRunner {
518    pub fn new() -> Self {
519        Self {
520            results: Mutex::new(Vec::new()),
521        }
522    }
523
524    pub fn run<F>(&self, name: &str, test_fn: F) -> bool
525    where
526        F: FnOnce() -> bool + Send + 'static,
527    {
528        let start = std::time::Instant::now();
529        let passed = test_fn();
530        let duration = start.elapsed().as_micros() as u64;
531        self.results.lock().unwrap().push(ConcTestResult {
532            name: name.into(),
533            passed,
534            duration_micros: duration,
535        });
536        passed
537    }
538
539    pub fn run_concurrent<F>(&self, tests: Vec<(&str, F)>) -> usize
540    where
541        F: Fn() -> bool + Send + Sync + 'static,
542    {
543        let passed_count = std::sync::Arc::new(AtomicU64::new(0));
544        let mut handles = Vec::new();
545        for (name, test_fn) in tests {
546            let pc = std::sync::Arc::clone(&passed_count);
547            let results = std::sync::Arc::new(Mutex::new(Vec::new()));
548            let r2 = std::sync::Arc::clone(&results);
549            let name = name.to_string();
550            handles.push(std::thread::spawn(move || {
551                let start = std::time::Instant::now();
552                let passed = test_fn();
553                let duration = start.elapsed().as_micros() as u64;
554                r2.lock().unwrap().push(ConcTestResult {
555                    name,
556                    passed,
557                    duration_micros: duration,
558                });
559                if passed {
560                    pc.fetch_add(1, Ordering::SeqCst);
561                }
562            }));
563        }
564        let _total = handles.len();
565        for h in handles {
566            let _ = h.join();
567        }
568        passed_count.load(Ordering::SeqCst) as usize
569    }
570
571    pub fn results(&self) -> Vec<ConcTestResult> {
572        self.results.lock().unwrap().clone()
573    }
574    pub fn total(&self) -> usize {
575        self.results.lock().unwrap().len()
576    }
577    pub fn passed(&self) -> usize {
578        self.results
579            .lock()
580            .unwrap()
581            .iter()
582            .filter(|r| r.passed)
583            .count()
584    }
585    pub fn failed(&self) -> usize {
586        self.total() - self.passed()
587    }
588    pub fn avg_duration_micros(&self) -> f64 {
589        let r = self.results.lock().unwrap();
590        if r.is_empty() {
591            return 0.0;
592        }
593        r.iter().map(|r| r.duration_micros as f64).sum::<f64>() / r.len() as f64
594    }
595}
596
597impl Default for ConcurrentTestRunner {
598    fn default() -> Self {
599        Self::new()
600    }
601}
602
603// === SPSC Channel ===
604
605pub struct SpscSender<T> {
606    shared: std::sync::Arc<SpscShared<T>>,
607}
608pub struct SpscReceiver<T> {
609    shared: std::sync::Arc<SpscShared<T>>,
610}
611
612struct SpscShared<T> {
613    buffer: Mutex<VecDeque<T>>,
614    capacity: usize,
615    total_sent: AtomicU64,
616    total_received: AtomicU64,
617}
618
619pub fn spsc_channel<T>(capacity: usize) -> (SpscSender<T>, SpscReceiver<T>) {
620    let shared = std::sync::Arc::new(SpscShared {
621        buffer: Mutex::new(VecDeque::with_capacity(capacity)),
622        capacity,
623        total_sent: AtomicU64::new(0),
624        total_received: AtomicU64::new(0),
625    });
626    (
627        SpscSender {
628            shared: std::sync::Arc::clone(&shared),
629        },
630        SpscReceiver { shared },
631    )
632}
633
634impl<T> SpscSender<T> {
635    pub fn send(&self, item: T) -> bool {
636        let mut buf = self.shared.buffer.lock().unwrap();
637        if buf.len() >= self.shared.capacity {
638            return false;
639        }
640        buf.push_back(item);
641        self.shared.total_sent.fetch_add(1, Ordering::SeqCst);
642        true
643    }
644    pub fn total_sent(&self) -> u64 {
645        self.shared.total_sent.load(Ordering::SeqCst)
646    }
647}
648
649impl<T> SpscReceiver<T> {
650    pub fn recv(&self) -> Option<T> {
651        let item = self.shared.buffer.lock().unwrap().pop_front();
652        if item.is_some() {
653            self.shared.total_received.fetch_add(1, Ordering::SeqCst);
654        }
655        item
656    }
657    pub fn len(&self) -> usize {
658        self.shared.buffer.lock().unwrap().len()
659    }
660    pub fn is_empty(&self) -> bool {
661        self.len() == 0
662    }
663    pub fn total_received(&self) -> u64 {
664        self.shared.total_received.load(Ordering::SeqCst)
665    }
666}
667
668#[cfg(test)]
669mod tests {
670    use super::*;
671
672    // Bloom filter
673    #[test]
674    fn bloom_insert_and_contains() {
675        let mut bf = BloomFilter::new(1000, 0.01);
676        bf.insert(b"apple");
677        bf.insert(b"banana");
678        assert!(bf.contains(b"apple"));
679        assert!(bf.contains(b"banana"));
680    }
681
682    #[test]
683    fn bloom_absent_not_present() {
684        let mut bf = BloomFilter::new(1000, 0.01);
685        bf.insert(b"present");
686        // With high probability, "absent" is not present
687        let false_positives = (0..100)
688            .filter(|i| bf.contains(format!("absent-{i}").as_bytes()))
689            .count();
690        assert!(false_positives < 10); // < 10% false positive rate
691    }
692
693    #[test]
694    fn bloom_count() {
695        let mut bf = BloomFilter::new(100, 0.05);
696        for i in 0..50 {
697            bf.insert(format!("item-{i}").as_bytes());
698        }
699        assert_eq!(bf.estimated_count(), 50);
700    }
701
702    // Count-Min sketch
703    #[test]
704    fn cms_estimate_frequency() {
705        let cms = CountMinSketch::new(1000, 5);
706        for _ in 0..10 {
707            cms.add(b"popular", 1);
708        }
709        for _ in 0..3 {
710            cms.add(b"rare", 1);
711        }
712        assert!(cms.estimate(b"popular") >= 10);
713        assert!(cms.estimate(b"rare") >= 3);
714    }
715
716    #[test]
717    fn cms_unknown_is_low() {
718        let cms = CountMinSketch::new(1000, 5);
719        cms.add(b"x", 1);
720        assert!(cms.estimate(b"unknown") <= 5);
721    }
722
723    // Ring buffer
724    #[test]
725    fn ring_buffer_evicts_oldest() {
726        let mut rb = RingBuffer::new(3);
727        rb.push(1);
728        rb.push(2);
729        rb.push(3);
730        rb.push(4);
731        assert_eq!(rb.len(), 3);
732        assert_eq!(*rb.oldest().unwrap(), 2);
733        assert_eq!(*rb.latest().unwrap(), 4);
734    }
735
736    #[test]
737    fn ring_buffer_iter() {
738        let mut rb = RingBuffer::new(5);
739        rb.push(1);
740        rb.push(2);
741        rb.push(3);
742        let v: Vec<i32> = rb.iter().copied().collect();
743        assert_eq!(v, vec![1, 2, 3]);
744    }
745
746    // Test fixtures
747    #[test]
748    fn fixtures_random_bytes() {
749        let b1 = TestFixtures::random_bytes(32);
750        let b2 = TestFixtures::random_bytes(32);
751        assert_eq!(b1.len(), 32);
752        assert_ne!(b1, b2);
753    }
754
755    #[test]
756    fn fixtures_session_id() {
757        let id = TestFixtures::random_session_id();
758        assert!(id.starts_with("session-"));
759    }
760
761    #[test]
762    fn fixtures_batch_messages() {
763        let msgs = TestFixtures::batch_messages(5);
764        assert_eq!(msgs.len(), 5);
765        assert_eq!(msgs[0][0], 0);
766        assert_eq!(msgs[4][0], 4);
767    }
768
769    // HKDF
770    #[test]
771    fn hkdf_extract_produces_prk() {
772        let prk = hkdf_extract(b"salt", b"ikm");
773        assert_eq!(prk.len(), 32);
774    }
775
776    #[test]
777    fn hkdf_expand_produces_output() {
778        let prk = [0x42u8; 32];
779        let okm = hkdf_expand(&prk, b"info", 64).unwrap();
780        assert_eq!(okm.len(), 64);
781    }
782
783    #[test]
784    fn hkdf_expand_rejects_over_rfc_limit() {
785        let prk = [0x42u8; 32];
786        // Exactly the RFC 5869 max: must succeed.
787        assert!(hkdf_expand(&prk, b"info", HKDF_SHA256_MAX_OUTPUT).is_ok());
788        // One past: must be a typed error, not counter wraparound.
789        let err = hkdf_expand(&prk, b"info", HKDF_SHA256_MAX_OUTPUT + 1).unwrap_err();
790        assert!(matches!(err, HkdfError::OutputTooLong { .. }));
791    }
792
793    #[test]
794    fn hkdf_expand_max_output_is_uniform() {
795        // The block counter must reach its last valid value (255)
796        // without wrapping; the tail must not repeat the head.
797        let prk = [0x07u8; 32];
798        let okm = hkdf_expand(&prk, b"info", HKDF_SHA256_MAX_OUTPUT).unwrap();
799        assert_eq!(okm.len(), HKDF_SHA256_MAX_OUTPUT);
800        let head = &okm[..32];
801        let tail = &okm[HKDF_SHA256_MAX_OUTPUT - 32..];
802        assert_ne!(head, tail, "tail block must differ from first block");
803    }
804
805    #[test]
806    fn hkdf_full_round_trips() {
807        let okm1 = hkdf(b"salt", b"ikm", b"info", 32).unwrap();
808        let okm2 = hkdf(b"salt", b"ikm", b"info", 32).unwrap();
809        assert_eq!(okm1, okm2); // deterministic
810    }
811
812    #[test]
813    fn hkdf_different_info_different_output() {
814        let okm1 = hkdf(b"s", b"ikm", b"info1", 32).unwrap();
815        let okm2 = hkdf(b"s", b"ikm", b"info2", 32).unwrap();
816        assert_ne!(okm1, okm2);
817    }
818
819    #[test]
820    fn hkdf_matches_rfc5869_test_vector_1() {
821        // RFC 5869 Appendix A.1 (SHA-256) — the canonical first
822        // test vector, verifying extract + expand end-to-end.
823        // OKM (42 octets):
824        //   3cb25f25faacd57a90434f64d0362f2a
825        //   2d2d0a90cf1a5a4c5db02d56ecc4c5bf
826        //   34007208d5b887185865
827        let ikm = [0x0bu8; 22];
828        let salt = [
829            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
830        ];
831        let info = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
832        let expected = [
833            0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
834            0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
835            0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
836        ];
837        let okm = hkdf(&salt, &ikm, &info, 42).unwrap();
838        assert_eq!(okm, expected);
839    }
840
841    // Key wrapping
842    #[test]
843    fn key_wrap_unwrap_round_trips() {
844        let kek = [0x42u8; 32];
845        let plaintext = TestFixtures::random_bytes(32);
846        let wrapped = key_wrap(&kek, &plaintext);
847        let unwrapped = key_unwrap(&kek, &wrapped).unwrap();
848        assert_eq!(unwrapped, plaintext);
849    }
850
851    #[test]
852    fn key_wrap_tampered_rejected() {
853        let kek = [0x42u8; 32];
854        let mut wrapped = key_wrap(&kek, b"secret");
855        wrapped[0] ^= 0xFF;
856        assert!(key_unwrap(&kek, &wrapped).is_none());
857    }
858
859    // Secure deletion
860    #[test]
861    fn secure_overwrite_changes_data() {
862        let mut data = vec![0xAA; 32];
863        secure_overwrite(&mut data);
864        assert!(data.iter().any(|&b| b != 0xAA));
865    }
866
867    #[test]
868    fn secure_buffer_zeroize() {
869        let mut buf = SecureBuffer::new(vec![0x42; 32]);
870        assert!(!buf.is_zeroized());
871        buf.zeroize();
872        assert!(buf.is_zeroized());
873        // The buffer must have changed from the initial all-0x42 state.
874        // (We can't assert "no byte is 0x42" because secure_overwrite's
875        // final pass uses OsRng, which can return any byte value.)
876        let unchanged = buf.as_slice().iter().filter(|&&b| b == 0x42).count();
877        assert!(
878            unchanged < 32,
879            "buffer was not overwritten (still all 0x42)"
880        );
881    }
882
883    // Entropy monitor
884    #[test]
885    fn entropy_monitor_records() {
886        let mon = EntropyMonitor::new(10, 100);
887        mon.record(256);
888        mon.record(128);
889        mon.record(64);
890        assert_eq!(mon.sample_count(), 3);
891        assert_eq!(mon.latest(), Some(64));
892        assert!(mon.average() > 0.0);
893    }
894
895    #[test]
896    fn entropy_monitor_alerts() {
897        let mon = EntropyMonitor::new(10, 100);
898        mon.record(256);
899        mon.record(32);
900        assert_eq!(mon.alert_count(), 1);
901        assert!(mon.is_low());
902    }
903
904    // Burst token bucket
905    #[test]
906    fn burst_bucket_allows_initial_burst() {
907        let bucket = BurstTokenBucket::new(10.0, 5.0); // 10/s sustained, 50 burst
908        assert!(bucket.try_consume(50.0)); // initial burst
909        assert!(!bucket.try_consume(1.0)); // empty
910    }
911
912    #[test]
913    fn burst_bucket_refills_over_time() {
914        let bucket = BurstTokenBucket::new(1000.0, 1.0); // fast refill
915        bucket.try_consume(1000.0);
916        std::thread::sleep(std::time::Duration::from_millis(10));
917        assert!(bucket.try_consume(1.0)); // refilled
918    }
919
920    // Concurrent test runner
921    #[test]
922    fn concurrent_runner_runs_tests() {
923        let runner = ConcurrentTestRunner::new();
924        runner.run("test1", || true);
925        runner.run("test2", || false);
926        assert_eq!(runner.total(), 2);
927        assert_eq!(runner.passed(), 1);
928        assert_eq!(runner.failed(), 1);
929    }
930
931    #[test]
932    fn concurrent_runner_parallel() {
933        type TestFn = (&'static str, fn() -> bool);
934        let runner = ConcurrentTestRunner::new();
935        let tests: Vec<TestFn> = vec![
936            ("t1", || true),
937            ("t2", || true),
938            ("t3", || true),
939            ("t4", || false),
940        ];
941        let passed = runner.run_concurrent(tests);
942        assert_eq!(passed, 3);
943    }
944
945    // SPSC channel
946    #[test]
947    fn spsc_send_recv() {
948        let (tx, rx) = spsc_channel::<i32>(10);
949        assert!(tx.send(42));
950        assert_eq!(rx.recv(), Some(42));
951        assert!(rx.is_empty());
952    }
953
954    #[test]
955    fn spsc_respects_capacity() {
956        let (tx, _rx) = spsc_channel::<i32>(2);
957        assert!(tx.send(1));
958        assert!(tx.send(2));
959        assert!(!tx.send(3)); // full
960    }
961
962    #[test]
963    fn spsc_ordering() {
964        let (tx, rx) = spsc_channel::<i32>(10);
965        tx.send(1);
966        tx.send(2);
967        tx.send(3);
968        assert_eq!(rx.recv(), Some(1));
969        assert_eq!(rx.recv(), Some(2));
970        assert_eq!(rx.recv(), Some(3));
971    }
972
973    #[test]
974    fn spsc_counters() {
975        let (tx, rx) = spsc_channel::<i32>(10);
976        tx.send(1);
977        tx.send(2);
978        rx.recv();
979        assert_eq!(tx.total_sent(), 2);
980        assert_eq!(rx.total_received(), 1);
981    }
982}