1use chrono::{DateTime, Duration, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::sync::Mutex;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TraceContext {
16 pub trace_id: String,
17 pub span_id: String,
18 pub parent_span_id: Option<String>,
19 pub baggage: HashMap<String, String>,
20}
21
22impl Default for TraceContext {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl TraceContext {
29 pub fn new() -> Self {
30 use rand_core::{OsRng, RngCore};
31 let mut trace_bytes = [0u8; 16];
32 let mut span_bytes = [0u8; 8];
33 OsRng.fill_bytes(&mut trace_bytes);
34 OsRng.fill_bytes(&mut span_bytes);
35 Self {
36 trace_id: hex::encode(trace_bytes),
37 span_id: hex::encode(span_bytes),
38 parent_span_id: None,
39 baggage: HashMap::new(),
40 }
41 }
42
43 pub fn child(&self) -> Self {
44 use rand_core::{OsRng, RngCore};
45 let mut span_bytes = [0u8; 8];
46 OsRng.fill_bytes(&mut span_bytes);
47 Self {
48 trace_id: self.trace_id.clone(),
49 span_id: hex::encode(span_bytes),
50 parent_span_id: Some(self.span_id.clone()),
51 baggage: self.baggage.clone(),
52 }
53 }
54
55 pub fn to_w3c_header(&self) -> String {
56 format!("00-{}-{}-01", self.trace_id, self.span_id)
57 }
58
59 pub fn from_w3c_header(header: &str) -> Option<Self> {
60 let parts: Vec<&str> = header.split('-').collect();
61 if parts.len() != 4 {
62 return None;
63 }
64 Some(Self {
65 trace_id: parts[1].into(),
66 span_id: parts[2].into(),
67 parent_span_id: None,
68 baggage: HashMap::new(),
69 })
70 }
71
72 pub fn add_baggage(&mut self, key: &str, value: &str) {
73 self.baggage.insert(key.into(), value.into());
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct LogEntry {
81 pub timestamp: DateTime<Utc>,
82 pub level: String,
83 pub message: String,
84 pub fields: HashMap<String, String>,
85 pub trace_id: Option<String>,
86}
87
88impl LogEntry {
89 pub fn info(msg: &str) -> Self {
90 Self {
91 timestamp: Utc::now(),
92 level: "info".into(),
93 message: msg.into(),
94 fields: HashMap::new(),
95 trace_id: None,
96 }
97 }
98
99 pub fn error(msg: &str) -> Self {
100 Self {
101 timestamp: Utc::now(),
102 level: "error".into(),
103 message: msg.into(),
104 fields: HashMap::new(),
105 trace_id: None,
106 }
107 }
108
109 pub fn warn(msg: &str) -> Self {
110 Self {
111 timestamp: Utc::now(),
112 level: "warn".into(),
113 message: msg.into(),
114 fields: HashMap::new(),
115 trace_id: None,
116 }
117 }
118
119 pub fn field(mut self, key: &str, value: &str) -> Self {
120 self.fields.insert(key.into(), value.into());
121 self
122 }
123
124 pub fn with_trace(mut self, trace_id: &str) -> Self {
125 self.trace_id = Some(trace_id.into());
126 self
127 }
128
129 pub fn to_json(&self) -> String {
130 serde_json::to_string(self).unwrap_or_else(|_| "{}".into())
131 }
132
133 pub fn to_jsonl(&self) -> String {
134 self.to_json() + "\n"
135 }
136}
137
138#[derive(Default)]
139pub struct StructuredLogger {
140 entries: Mutex<Vec<LogEntry>>,
141 min_level: Mutex<String>,
142}
143
144impl StructuredLogger {
145 pub fn new(min_level: &str) -> Self {
146 Self {
147 entries: Mutex::new(Vec::new()),
148 min_level: Mutex::new(min_level.into()),
149 }
150 }
151
152 pub fn log(&self, entry: LogEntry) {
153 let levels = ["trace", "debug", "info", "warn", "error"];
154 let min_idx = levels
155 .iter()
156 .position(|&l| l == *self.min_level.lock().unwrap())
157 .unwrap_or(2);
158 let entry_idx = levels
159 .iter()
160 .position(|&l| l == entry.level.as_str())
161 .unwrap_or(2);
162 if entry_idx >= min_idx {
163 self.entries.lock().unwrap().push(entry);
164 }
165 }
166
167 pub fn entries(&self) -> Vec<LogEntry> {
168 self.entries.lock().unwrap().clone()
169 }
170 pub fn count(&self) -> usize {
171 self.entries.lock().unwrap().len()
172 }
173 pub fn to_jsonl(&self) -> String {
174 self.entries
175 .lock()
176 .unwrap()
177 .iter()
178 .map(|e| e.to_jsonl())
179 .collect()
180 }
181 pub fn flush(&self) -> Vec<LogEntry> {
182 let mut entries = self.entries.lock().unwrap();
183 std::mem::take(&mut *entries)
184 }
185}
186
187pub struct CardinalityLimiter {
190 label_counts: Mutex<HashMap<String, HashSet<String>>>,
191 max_values_per_label: usize,
192 total_series: AtomicU64,
193 max_total_series: u64,
194}
195
196impl CardinalityLimiter {
197 pub fn new(max_values_per_label: usize, max_total_series: u64) -> Self {
198 Self {
199 label_counts: Mutex::new(HashMap::new()),
200 max_values_per_label,
201 total_series: AtomicU64::new(0),
202 max_total_series,
203 }
204 }
205
206 pub fn allow_label(&self, label_name: &str, label_value: &str) -> bool {
207 if self.total_series.load(Ordering::SeqCst) >= self.max_total_series {
208 return false;
209 }
210 let mut counts = self.label_counts.lock().unwrap();
211 let values = counts.entry(label_name.into()).or_default();
212 if values.contains(label_value) {
213 return true;
214 }
215 if values.len() >= self.max_values_per_label {
216 return false;
217 }
218 values.insert(label_value.into());
219 self.total_series.fetch_add(1, Ordering::SeqCst);
220 true
221 }
222
223 pub fn label_value_count(&self, label: &str) -> usize {
224 self.label_counts
225 .lock()
226 .unwrap()
227 .get(label)
228 .map(|s| s.len())
229 .unwrap_or(0)
230 }
231
232 pub fn total_series(&self) -> u64 {
233 self.total_series.load(Ordering::SeqCst)
234 }
235 pub fn reset(&self) {
236 self.label_counts.lock().unwrap().clear();
237 self.total_series.store(0, Ordering::SeqCst);
238 }
239}
240
241pub fn frequency_test(data: &[u8]) -> f64 {
244 let n = data.len() as f64 * 8.0;
245 let mut s: f64 = 0.0;
246 for byte in data {
247 for bit in 0..8 {
248 s += if (byte >> bit) & 1 == 1 { 1.0 } else { -1.0 };
249 }
250 }
251 let s_obs = s.abs() / n.sqrt();
252 erfc(s_obs / 2f64.sqrt())
253}
254
255pub fn runs_test(data: &[u8]) -> f64 {
256 let bits: Vec<i8> = data
257 .iter()
258 .flat_map(|b| (0..8).map(move |i| ((b >> i) & 1) as i8))
259 .collect();
260 let n = bits.len() as f64;
261 let pi: f64 = bits.iter().map(|&b| b as f64).sum::<f64>() / n;
262 if (pi - 0.5).abs() >= 2.0 / n.sqrt() {
263 return 0.0;
264 }
265 let mut v_obs = 1.0;
266 for i in 1..bits.len() {
267 if bits[i] != bits[i - 1] {
268 v_obs += 1.0;
269 }
270 }
271 let denom = 2.0 * pi * (1.0 - pi);
272 let s = (2.0 * (n.sqrt())) * denom;
273 erfc((v_obs - s) / (2.0 * s * denom.sqrt()))
274}
275
276pub fn entropy_estimate(data: &[u8]) -> f64 {
277 let mut counts = [0u32; 256];
278 for &b in data {
279 counts[b as usize] += 1;
280 }
281 let n = data.len() as f64;
282 let mut entropy = 0.0;
283 for &count in &counts {
284 if count > 0 {
285 let p = count as f64 / n;
286 entropy -= p * p.log2();
287 }
288 }
289 entropy
290}
291
292fn erfc(x: f64) -> f64 {
293 let z = x.abs();
295 let t = 1.0 / (1.0 + 0.5 * z);
296 let r = t
297 * (-z * z - 1.26551223
298 + t * (1.00002368
299 + t * (-0.37609177
300 + t * (0.36496351
301 + t * (-0.16108111 + t * (0.08095456 + t * (-0.02907307)))))))
302 .exp();
303 if x >= 0.0 { r } else { 2.0 - r }
304}
305
306pub struct ZeroizationAuditor {
309 tracked_secrets: Mutex<Vec<SecretRecord>>,
310}
311
312#[derive(Debug, Clone)]
313pub struct SecretRecord {
314 pub id: String,
315 pub allocated_at: DateTime<Utc>,
316 pub zeroized: bool,
317 pub size_bytes: usize,
318}
319
320impl ZeroizationAuditor {
321 pub fn new() -> Self {
322 Self {
323 tracked_secrets: Mutex::new(Vec::new()),
324 }
325 }
326
327 pub fn track(&self, id: &str, size: usize) {
328 self.tracked_secrets.lock().unwrap().push(SecretRecord {
329 id: id.into(),
330 allocated_at: Utc::now(),
331 zeroized: false,
332 size_bytes: size,
333 });
334 }
335
336 pub fn mark_zeroized(&self, id: &str) {
337 if let Some(s) = self
338 .tracked_secrets
339 .lock()
340 .unwrap()
341 .iter_mut()
342 .find(|s| s.id == id)
343 {
344 s.zeroized = true;
345 }
346 }
347
348 pub fn unzeroized_count(&self) -> usize {
349 self.tracked_secrets
350 .lock()
351 .unwrap()
352 .iter()
353 .filter(|s| !s.zeroized)
354 .count()
355 }
356
357 pub fn audit_report(&self) -> ZeroizationReport {
358 let secrets = self.tracked_secrets.lock().unwrap();
359 let total = secrets.len();
360 let zeroized = secrets.iter().filter(|s| s.zeroized).count();
361 ZeroizationReport {
362 total_secrets: total,
363 zeroized,
364 unzeroized: total - zeroized,
365 }
366 }
367}
368
369impl Default for ZeroizationAuditor {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct ZeroizationReport {
377 pub total_secrets: usize,
378 pub zeroized: usize,
379 pub unzeroized: usize,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct EncryptedBackup {
386 pub backup_id: String,
387 pub encrypted_shares_hex: Vec<String>,
388 pub backup_key_threshold: u32,
389 pub created_at: DateTime<Utc>,
390}
391
392pub struct BackupManager {
393 backups: Mutex<HashMap<String, EncryptedBackup>>,
394}
395
396impl BackupManager {
397 pub fn new() -> Self {
398 Self {
399 backups: Mutex::new(HashMap::new()),
400 }
401 }
402
403 pub fn create_backup(
404 &self,
405 backup_id: &str,
406 shares: &[Vec<u8>],
407 threshold: u32,
408 ) -> EncryptedBackup {
409 use rand_core::{OsRng, RngCore};
410 let encrypted: Vec<String> = shares
411 .iter()
412 .map(|s| {
413 let mut key = [0u8; 32];
414 OsRng.fill_bytes(&mut key);
415 let encrypted: Vec<u8> = s
416 .iter()
417 .enumerate()
418 .map(|(i, &b)| b ^ key[i % key.len()])
419 .collect();
420 hex::encode(encrypted)
421 })
422 .collect();
423 let backup = EncryptedBackup {
424 backup_id: backup_id.into(),
425 encrypted_shares_hex: encrypted,
426 backup_key_threshold: threshold,
427 created_at: Utc::now(),
428 };
429 self.backups
430 .lock()
431 .unwrap()
432 .insert(backup_id.into(), backup.clone());
433 backup
434 }
435
436 pub fn restore_backup(&self, backup_id: &str, keys: &[[u8; 32]; 32]) -> Option<Vec<Vec<u8>>> {
437 let backups = self.backups.lock().unwrap();
438 let backup = backups.get(backup_id)?;
439 if keys.len() < backup.backup_key_threshold as usize {
440 return None;
441 }
442 let restored: Vec<Vec<u8>> = backup
444 .encrypted_shares_hex
445 .iter()
446 .map(|hex_str| {
447 let encrypted = hex::decode(hex_str).unwrap_or_default();
448 let key = &keys[0];
449 encrypted
450 .iter()
451 .enumerate()
452 .map(|(i, &b)| b ^ key[i % key.len()])
453 .collect()
454 })
455 .collect();
456 Some(restored)
457 }
458
459 pub fn backup_count(&self) -> usize {
460 self.backups.lock().unwrap().len()
461 }
462 pub fn list_backups(&self) -> Vec<String> {
463 self.backups.lock().unwrap().keys().cloned().collect()
464 }
465}
466
467impl Default for BackupManager {
468 fn default() -> Self {
469 Self::new()
470 }
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct RetentionPolicy {
477 pub session_retention_days: u32,
478 pub audit_retention_days: u32,
479 pub wal_retention_entries: u64,
480 pub purge_interval_hours: u32,
481}
482
483impl Default for RetentionPolicy {
484 fn default() -> Self {
485 Self {
486 session_retention_days: 7,
487 audit_retention_days: 365,
488 wal_retention_entries: 100_000,
489 purge_interval_hours: 1,
490 }
491 }
492}
493
494pub struct RetentionEngine {
495 policy: RetentionPolicy,
496 purged_sessions: AtomicU64,
497 purged_audit_entries: AtomicU64,
498 last_purge: Mutex<Option<DateTime<Utc>>>,
499}
500
501impl RetentionEngine {
502 pub fn new(policy: RetentionPolicy) -> Self {
503 Self {
504 policy,
505 purged_sessions: AtomicU64::new(0),
506 purged_audit_entries: AtomicU64::new(0),
507 last_purge: Mutex::new(None),
508 }
509 }
510
511 pub fn should_purge(&self) -> bool {
512 if let Some(last) = *self.last_purge.lock().unwrap() {
513 Utc::now() - last >= Duration::hours(self.policy.purge_interval_hours as i64)
514 } else {
515 true
516 }
517 }
518
519 pub fn purge_sessions(&self, sessions: &mut Vec<(String, DateTime<Utc>)>) -> usize {
520 let cutoff = Utc::now() - Duration::days(self.policy.session_retention_days as i64);
521 let before = sessions.len();
522 sessions.retain(|(_, ts)| *ts > cutoff);
523 let purged = before - sessions.len();
524 self.purged_sessions
525 .fetch_add(purged as u64, Ordering::SeqCst);
526 *self.last_purge.lock().unwrap() = Some(Utc::now());
527 purged
528 }
529
530 pub fn truncate_wal(&self, wal: &mut Vec<String>) -> usize {
531 let max = self.policy.wal_retention_entries as usize;
532 if wal.len() <= max {
533 return 0;
534 }
535 let excess = wal.len() - max;
536 wal.drain(..excess);
537 self.purged_audit_entries
538 .fetch_add(excess as u64, Ordering::SeqCst);
539 excess
540 }
541
542 pub fn total_purged_sessions(&self) -> u64 {
543 self.purged_sessions.load(Ordering::SeqCst)
544 }
545 pub fn total_purged_entries(&self) -> u64 {
546 self.purged_audit_entries.load(Ordering::SeqCst)
547 }
548}
549
550#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct ReplicaState {
554 pub region: String,
555 pub last_seq: u64,
556 pub last_sync: DateTime<Utc>,
557 pub lag_seconds: i64,
558}
559
560pub struct ReplicationManager {
561 replicas: Mutex<HashMap<String, ReplicaState>>,
562 local_seq: AtomicU64,
563}
564
565impl ReplicationManager {
566 pub fn new() -> Self {
567 Self {
568 replicas: Mutex::new(HashMap::new()),
569 local_seq: AtomicU64::new(0),
570 }
571 }
572
573 pub fn register_replica(&self, region: &str) {
574 self.replicas.lock().unwrap().insert(
575 region.into(),
576 ReplicaState {
577 region: region.into(),
578 last_seq: 0,
579 last_sync: Utc::now(),
580 lag_seconds: 0,
581 },
582 );
583 }
584
585 pub fn record_replication(&self, region: &str, seq: u64) {
586 let mut replicas = self.replicas.lock().unwrap();
587 if let Some(r) = replicas.get_mut(region) {
588 r.last_seq = seq;
589 r.last_sync = Utc::now();
590 let _local = self.local_seq.load(Ordering::SeqCst);
591 r.lag_seconds = (Utc::now() - r.last_sync).num_seconds();
592 }
593 }
594
595 pub fn advance_local(&self) -> u64 {
596 self.local_seq.fetch_add(1, Ordering::SeqCst) + 1
597 }
598 pub fn local_seq(&self) -> u64 {
599 self.local_seq.load(Ordering::SeqCst)
600 }
601
602 pub fn replica_lag(&self, region: &str) -> Option<u64> {
603 let replicas = self.replicas.lock().unwrap();
604 let r = replicas.get(region)?;
605 Some(self.local_seq.load(Ordering::SeqCst) - r.last_seq)
606 }
607
608 pub fn replica_count(&self) -> usize {
609 self.replicas.lock().unwrap().len()
610 }
611 pub fn all_replicas(&self) -> Vec<ReplicaState> {
612 self.replicas.lock().unwrap().values().cloned().collect()
613 }
614}
615
616impl Default for ReplicationManager {
617 fn default() -> Self {
618 Self::new()
619 }
620}
621
622pub trait CloudKms: Send + Sync {
625 fn encrypt(&self, plaintext: &[u8], key_id: &str) -> Result<Vec<u8>, String>;
626 fn decrypt(&self, ciphertext: &[u8], key_id: &str) -> Result<Vec<u8>, String>;
627 fn create_key(&self, key_id: &str) -> Result<(), String>;
628 fn delete_key(&self, key_id: &str) -> Result<(), String>;
629 fn key_exists(&self, key_id: &str) -> bool;
630 fn name(&self) -> &str;
631}
632
633pub struct MockKms {
634 keys: Mutex<HashMap<String, [u8; 32]>>,
635}
636
637impl MockKms {
638 pub fn new() -> Self {
639 Self {
640 keys: Mutex::new(HashMap::new()),
641 }
642 }
643}
644
645impl CloudKms for MockKms {
646 fn encrypt(&self, plaintext: &[u8], key_id: &str) -> Result<Vec<u8>, String> {
647 let keys = self.keys.lock().unwrap();
648 let key = keys.get(key_id).ok_or("key not found")?;
649 Ok(plaintext
650 .iter()
651 .enumerate()
652 .map(|(i, &b)| b ^ key[i % 32])
653 .collect())
654 }
655 fn decrypt(&self, ciphertext: &[u8], key_id: &str) -> Result<Vec<u8>, String> {
656 self.encrypt(ciphertext, key_id) }
658 fn create_key(&self, key_id: &str) -> Result<(), String> {
659 use rand_core::{OsRng, RngCore};
660 let mut key = [0u8; 32];
661 OsRng.fill_bytes(&mut key);
662 self.keys.lock().unwrap().insert(key_id.into(), key);
663 Ok(())
664 }
665 fn delete_key(&self, key_id: &str) -> Result<(), String> {
666 self.keys.lock().unwrap().remove(key_id);
667 Ok(())
668 }
669 fn key_exists(&self, key_id: &str) -> bool {
670 self.keys.lock().unwrap().contains_key(key_id)
671 }
672 fn name(&self) -> &str {
673 "mock-kms"
674 }
675}
676
677impl Default for MockKms {
678 fn default() -> Self {
679 Self::new()
680 }
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize)]
686pub struct SyslogMessage {
687 pub facility: u8,
688 pub severity: u8,
689 pub timestamp: DateTime<Utc>,
690 pub hostname: String,
691 pub app_name: String,
692 pub proc_id: u32,
693 pub msg_id: String,
694 pub message: String,
695}
696
697impl SyslogMessage {
698 pub fn new(severity: u8, message: &str) -> Self {
699 Self {
700 facility: 4,
701 severity,
702 timestamp: Utc::now(),
703 hostname: "confium".into(),
704 app_name: "coordinator".into(),
705 proc_id: 1,
706 msg_id: "audit".into(),
707 message: message.into(),
708 }
709 }
710
711 pub fn priority(&self) -> u8 {
712 self.facility * 8 + self.severity
713 }
714
715 pub fn to_rfc5424(&self) -> String {
716 format!(
717 "<{}>1 {} {} {} {} {} {}",
718 self.priority(),
719 self.timestamp.format("%Y-%m-%dT%H:%M:%SZ"),
720 self.hostname,
721 self.app_name,
722 self.proc_id,
723 self.msg_id,
724 self.message
725 )
726 }
727}
728
729#[derive(Default)]
730pub struct SyslogForwarder {
731 sent: Mutex<Vec<SyslogMessage>>,
732 enabled: Mutex<bool>,
733}
734
735impl SyslogForwarder {
736 pub fn new() -> Self {
737 Self {
738 sent: Mutex::new(Vec::new()),
739 enabled: Mutex::new(true),
740 }
741 }
742
743 pub fn forward(&self, msg: SyslogMessage) -> bool {
744 if !*self.enabled.lock().unwrap() {
745 return false;
746 }
747 self.sent.lock().unwrap().push(msg);
748 true
749 }
750
751 pub fn forward_audit(&self, event: &str) -> bool {
752 self.forward(SyslogMessage::new(5, event)) }
754
755 pub fn forward_error(&self, error: &str) -> bool {
756 self.forward(SyslogMessage::new(3, error)) }
758
759 pub fn sent_count(&self) -> usize {
760 self.sent.lock().unwrap().len()
761 }
762 pub fn set_enabled(&self, enabled: bool) {
763 *self.enabled.lock().unwrap() = enabled;
764 }
765 pub fn messages(&self) -> Vec<SyslogMessage> {
766 self.sent.lock().unwrap().clone()
767 }
768 pub fn flush(&self) -> Vec<SyslogMessage> {
769 let mut sent = self.sent.lock().unwrap();
770 std::mem::take(&mut *sent)
771 }
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777
778 #[test]
780 fn trace_context_child_inherits_trace_id() {
781 let parent = TraceContext::new();
782 let child = parent.child();
783 assert_eq!(parent.trace_id, child.trace_id);
784 assert_eq!(child.parent_span_id, Some(parent.span_id.clone()));
785 }
786
787 #[test]
788 fn trace_w3c_header_round_trips() {
789 let ctx = TraceContext::new();
790 let header = ctx.to_w3c_header();
791 let parsed = TraceContext::from_w3c_header(&header).unwrap();
792 assert_eq!(ctx.trace_id, parsed.trace_id);
793 }
794
795 #[test]
796 fn trace_baggage() {
797 let mut ctx = TraceContext::new();
798 ctx.add_baggage("user", "alice");
799 let child = ctx.child();
800 assert_eq!(child.baggage.get("user"), Some(&"alice".to_string()));
801 }
802
803 #[test]
805 fn log_entry_to_json() {
806 let entry = LogEntry::info("test message").field("key", "value");
807 let json = entry.to_json();
808 assert!(json.contains("test message"));
809 assert!(json.contains("\"key\":\"value\""));
810 }
811
812 #[test]
813 fn logger_filters_by_level() {
814 let logger = StructuredLogger::new("warn");
815 logger.log(LogEntry::info("info msg"));
816 logger.log(LogEntry::warn("warn msg"));
817 logger.log(LogEntry::error("error msg"));
818 assert_eq!(logger.count(), 2); }
820
821 #[test]
822 fn logger_jsonl_output() {
823 let logger = StructuredLogger::new("info");
824 logger.log(LogEntry::info("msg1"));
825 logger.log(LogEntry::info("msg2"));
826 let jsonl = logger.to_jsonl();
827 assert!(jsonl.contains("msg1"));
828 assert!(jsonl.contains("msg2"));
829 }
830
831 #[test]
833 fn cardinality_allows_new_values() {
834 let limiter = CardinalityLimiter::new(5, 100);
835 assert!(limiter.allow_label("user", "alice"));
836 assert!(limiter.allow_label("user", "bob"));
837 assert_eq!(limiter.label_value_count("user"), 2);
838 }
839
840 #[test]
841 fn cardinality_rejects_excess() {
842 let limiter = CardinalityLimiter::new(2, 100);
843 assert!(limiter.allow_label("ip", "1.1.1.1"));
844 assert!(limiter.allow_label("ip", "2.2.2.2"));
845 assert!(!limiter.allow_label("ip", "3.3.3.3"));
846 }
847
848 #[test]
849 fn cardinality_total_series_limit() {
850 let limiter = CardinalityLimiter::new(100, 3);
851 limiter.allow_label("a", "1");
852 limiter.allow_label("a", "2");
853 limiter.allow_label("a", "3");
854 assert_eq!(limiter.total_series(), 3);
855 assert!(!limiter.allow_label("a", "4"));
856 }
857
858 #[test]
860 fn frequency_test_random_data() {
861 use rand_core::RngCore;
862 let mut data = vec![0u8; 1000];
863 rand_core::OsRng.fill_bytes(&mut data);
864 let p_value = frequency_test(&data);
865 if p_value <= 0.001 {
870 panic!("p-value {p_value} is far below the random-data expectation");
871 }
872 }
873
874 #[test]
875 fn frequency_test_all_zeros() {
876 let data = vec![0u8; 100];
877 let p_value = frequency_test(&data);
878 assert!(p_value < 0.01, "p-value should be < 0.01 for all-zeros");
879 }
880
881 #[test]
882 fn entropy_random_is_high() {
883 use rand_core::RngCore;
884 let mut data = vec![0u8; 1000];
885 rand_core::OsRng.fill_bytes(&mut data);
886 let entropy = entropy_estimate(&data);
887 assert!(
888 entropy > 7.0,
889 "entropy should be > 7.0 bits/byte for random data"
890 );
891 }
892
893 #[test]
894 fn entropy_constant_is_low() {
895 let data = vec![0x42u8; 1000];
896 let entropy = entropy_estimate(&data);
897 assert!(entropy < 1.0);
898 }
899
900 #[test]
902 fn audit_tracks_secrets() {
903 let auditor = ZeroizationAuditor::new();
904 auditor.track("s1", 32);
905 auditor.track("s2", 64);
906 assert_eq!(auditor.unzeroized_count(), 2);
907 auditor.mark_zeroized("s1");
908 assert_eq!(auditor.unzeroized_count(), 1);
909 }
910
911 #[test]
912 fn audit_report() {
913 let auditor = ZeroizationAuditor::new();
914 auditor.track("s1", 32);
915 auditor.track("s2", 64);
916 auditor.mark_zeroized("s1");
917 let report = auditor.audit_report();
918 assert_eq!(report.total_secrets, 2);
919 assert_eq!(report.zeroized, 1);
920 assert_eq!(report.unzeroized, 1);
921 }
922
923 #[test]
925 fn backup_create_and_list() {
926 let mgr = BackupManager::new();
927 mgr.create_backup("b1", &[vec![0xAA; 32], vec![0xBB; 32]], 2);
928 assert_eq!(mgr.backup_count(), 1);
929 assert!(mgr.list_backups().contains(&"b1".to_string()));
930 }
931
932 #[test]
934 fn retention_purges_old_sessions() {
935 let engine = RetentionEngine::new(RetentionPolicy {
936 session_retention_days: 1,
937 ..Default::default()
938 });
939 let mut sessions = vec![
940 ("s1".into(), Utc::now() - Duration::days(5)),
941 ("s2".into(), Utc::now()),
942 ];
943 let purged = engine.purge_sessions(&mut sessions);
944 assert_eq!(purged, 1);
945 assert_eq!(sessions.len(), 1);
946 }
947
948 #[test]
949 fn retention_truncates_wal() {
950 let engine = RetentionEngine::new(RetentionPolicy {
951 wal_retention_entries: 5,
952 ..Default::default()
953 });
954 let mut wal: Vec<String> = (0..10).map(|i| format!("entry-{i}")).collect();
955 let purged = engine.truncate_wal(&mut wal);
956 assert_eq!(purged, 5);
957 assert_eq!(wal.len(), 5);
958 }
959
960 #[test]
962 fn replication_tracks_lag() {
963 let mgr = ReplicationManager::new();
964 mgr.register_replica("us-east");
965 mgr.advance_local();
966 mgr.advance_local();
967 mgr.advance_local();
968 mgr.record_replication("us-east", 1);
969 assert_eq!(mgr.replica_lag("us-east"), Some(2));
970 }
971
972 #[test]
973 fn replication_multiple_regions() {
974 let mgr = ReplicationManager::new();
975 mgr.register_replica("us-east");
976 mgr.register_replica("eu-west");
977 assert_eq!(mgr.replica_count(), 2);
978 }
979
980 #[test]
982 fn kms_encrypt_decrypt() {
983 let kms = MockKms::new();
984 kms.create_key("key1").unwrap();
985 let ct = kms.encrypt(b"secret", "key1").unwrap();
986 let pt = kms.decrypt(&ct, "key1").unwrap();
987 assert_eq!(pt, b"secret");
988 }
989
990 #[test]
991 fn kms_key_exists() {
992 let kms = MockKms::new();
993 assert!(!kms.key_exists("k1"));
994 kms.create_key("k1").unwrap();
995 assert!(kms.key_exists("k1"));
996 kms.delete_key("k1").unwrap();
997 assert!(!kms.key_exists("k1"));
998 }
999
1000 #[test]
1002 fn syslog_format_rfc5424() {
1003 let msg = SyslogMessage::new(5, "test event");
1004 let formatted = msg.to_rfc5424();
1005 assert!(formatted.starts_with("<"));
1006 assert!(formatted.contains("test event"));
1007 }
1008
1009 #[test]
1010 fn syslog_forward_audit() {
1011 let fwd = SyslogForwarder::new();
1012 fwd.forward_audit("session created");
1013 fwd.forward_error("signing failed");
1014 assert_eq!(fwd.sent_count(), 2);
1015 }
1016
1017 #[test]
1018 fn syslog_disabled() {
1019 let fwd = SyslogForwarder::new();
1020 fwd.set_enabled(false);
1021 fwd.forward_audit("test");
1022 assert_eq!(fwd.sent_count(), 0);
1023 }
1024
1025 #[test]
1026 fn syslog_flush() {
1027 let fwd = SyslogForwarder::new();
1028 fwd.forward_audit("msg1");
1029 fwd.forward_audit("msg2");
1030 let flushed = fwd.flush();
1031 assert_eq!(flushed.len(), 2);
1032 assert_eq!(fwd.sent_count(), 0);
1033 }
1034}