Skip to main content

peat_protocol/security/
audit.rs

1//! # Audit Logging Module - Security Event Tracking for Peat Protocol
2//!
3//! Implements ADR-006 Layer 7: Audit Logging.
4//!
5//! ## Overview
6//!
7//! Provides comprehensive logging of all security-relevant events for forensics
8//! and compliance with NIST SP 800-53 AU (Audit) controls.
9//!
10//! ## Event Types
11//!
12//! - **Authentication**: Device/user authentication attempts
13//! - **Authorization**: Permission grants and denials
14//! - **DataAccess**: Read/write operations on sensitive data
15//! - **CellFormation**: Cell join/leave/formation events
16//! - **KeyExchange**: Cryptographic key operations
17//! - **SecurityViolation**: Detected security anomalies
18//!
19//! ## Usage
20//!
21//! ```ignore
22//! use peat_protocol::security::{AuditLogger, FileAuditLogger, AuditEventType};
23//!
24//! // Create file-based audit logger
25//! let logger = FileAuditLogger::new("/var/log/peat/audit.log")?;
26//!
27//! // Log authentication event
28//! logger.log_authentication(
29//!     "device:abc123",
30//!     true,
31//!     Some("challenge-response verified"),
32//! );
33//!
34//! // Log authorization denial
35//! logger.log_denial(
36//!     "device:abc123",
37//!     "SetCellLeader",
38//!     "cell:squad-1",
39//!     "role=Member, required=Leader",
40//! );
41//! ```
42
43use std::collections::HashMap;
44use std::fs::{File, OpenOptions};
45use std::io::{BufWriter, Write};
46use std::path::Path;
47use std::sync::{Arc, Mutex};
48use std::time::{SystemTime, UNIX_EPOCH};
49
50use serde::{Deserialize, Serialize};
51
52use super::authorization::Permission;
53use super::error::SecurityError;
54
55/// Audit event types for categorization
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum AuditEventType {
59    /// Device or user authentication attempt
60    Authentication,
61    /// Authorization check (permission grant/denial)
62    Authorization,
63    /// Data access operation (read)
64    DataAccess,
65    /// Data modification operation (write)
66    DataModification,
67    /// Cryptographic key exchange
68    KeyExchange,
69    /// Cell formation or membership change
70    CellFormation,
71    /// Leader election or change
72    LeaderElection,
73    /// Detected security violation
74    SecurityViolation,
75    /// Session management (create/expire/invalidate)
76    SessionManagement,
77    /// Encryption operation
78    Encryption,
79}
80
81impl std::fmt::Display for AuditEventType {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            AuditEventType::Authentication => write!(f, "AUTHENTICATION"),
85            AuditEventType::Authorization => write!(f, "AUTHORIZATION"),
86            AuditEventType::DataAccess => write!(f, "DATA_ACCESS"),
87            AuditEventType::DataModification => write!(f, "DATA_MODIFICATION"),
88            AuditEventType::KeyExchange => write!(f, "KEY_EXCHANGE"),
89            AuditEventType::CellFormation => write!(f, "CELL_FORMATION"),
90            AuditEventType::LeaderElection => write!(f, "LEADER_ELECTION"),
91            AuditEventType::SecurityViolation => write!(f, "SECURITY_VIOLATION"),
92            AuditEventType::SessionManagement => write!(f, "SESSION_MANAGEMENT"),
93            AuditEventType::Encryption => write!(f, "ENCRYPTION"),
94        }
95    }
96}
97
98/// Security violation types
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "snake_case")]
101pub enum SecurityViolation {
102    /// Invalid signature detected
103    InvalidSignature,
104    /// Replay attack detected
105    ReplayAttack,
106    /// Unauthorized access attempt
107    UnauthorizedAccess,
108    /// Certificate validation failed
109    CertificateError,
110    /// Tampered message detected
111    TamperedMessage,
112    /// Rate limit exceeded
113    RateLimitExceeded,
114    /// Unknown device attempted join
115    UnknownDevice,
116    /// Expired credentials used
117    ExpiredCredentials,
118    /// Protocol violation
119    ProtocolViolation,
120}
121
122impl std::fmt::Display for SecurityViolation {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            SecurityViolation::InvalidSignature => write!(f, "INVALID_SIGNATURE"),
126            SecurityViolation::ReplayAttack => write!(f, "REPLAY_ATTACK"),
127            SecurityViolation::UnauthorizedAccess => write!(f, "UNAUTHORIZED_ACCESS"),
128            SecurityViolation::CertificateError => write!(f, "CERTIFICATE_ERROR"),
129            SecurityViolation::TamperedMessage => write!(f, "TAMPERED_MESSAGE"),
130            SecurityViolation::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
131            SecurityViolation::UnknownDevice => write!(f, "UNKNOWN_DEVICE"),
132            SecurityViolation::ExpiredCredentials => write!(f, "EXPIRED_CREDENTIALS"),
133            SecurityViolation::ProtocolViolation => write!(f, "PROTOCOL_VIOLATION"),
134        }
135    }
136}
137
138/// Audit log entry (JSON-serializable)
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct AuditLogEntry {
141    /// Unix timestamp (seconds since epoch)
142    pub timestamp: u64,
143    /// ISO 8601 formatted timestamp for readability
144    pub timestamp_iso: String,
145    /// Event type category
146    pub event_type: AuditEventType,
147    /// Entity performing the action (device ID or user ID)
148    pub entity_id: String,
149    /// Whether the action succeeded
150    pub success: bool,
151    /// Human-readable description
152    pub description: String,
153    /// Additional context (key-value pairs)
154    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
155    pub context: HashMap<String, String>,
156    /// Sequence number for ordering
157    pub sequence: u64,
158}
159
160impl AuditLogEntry {
161    /// Create new audit log entry
162    pub fn new(
163        event_type: AuditEventType,
164        entity_id: impl Into<String>,
165        success: bool,
166        description: impl Into<String>,
167        sequence: u64,
168    ) -> Self {
169        let now = SystemTime::now()
170            .duration_since(UNIX_EPOCH)
171            .map(|d| d.as_secs())
172            .unwrap_or(0);
173
174        Self {
175            timestamp: now,
176            timestamp_iso: format_timestamp(now),
177            event_type,
178            entity_id: entity_id.into(),
179            success,
180            description: description.into(),
181            context: HashMap::new(),
182            sequence,
183        }
184    }
185
186    /// Add context key-value pair
187    pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
188        self.context.insert(key.into(), value.into());
189        self
190    }
191
192    /// Serialize to JSON line
193    pub fn to_json(&self) -> String {
194        serde_json::to_string(self).unwrap_or_else(|_| {
195            format!(
196                "{{\"error\":\"serialization failed for seq {}\"}}",
197                self.sequence
198            )
199        })
200    }
201}
202
203/// Format Unix timestamp as ISO 8601
204fn format_timestamp(secs: u64) -> String {
205    // Simple ISO 8601 format without external dependencies
206    let dt = chrono::DateTime::from_timestamp(secs as i64, 0)
207        .or_else(|| chrono::DateTime::from_timestamp(0, 0))
208        .expect("Unix epoch is always a valid timestamp");
209    dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()
210}
211
212/// Audit logger trait for security event tracking
213pub trait AuditLogger: Send + Sync {
214    /// Log authentication event
215    fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>);
216
217    /// Log authorization grant
218    fn log_grant(&self, entity_id: &str, permission: Permission, target: &str);
219
220    /// Log authorization denial
221    fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str);
222
223    /// Log data operation
224    fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool);
225
226    /// Log security violation
227    fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str);
228
229    /// Log cell formation event
230    fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool);
231
232    /// Log key exchange event
233    fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool);
234
235    /// Log session event (create, expire, invalidate)
236    fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool);
237
238    /// Log encryption event
239    fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool);
240
241    /// Get the number of entries logged
242    fn entry_count(&self) -> u64;
243
244    /// Flush any buffered entries
245    fn flush(&self) -> Result<(), SecurityError>;
246}
247
248/// In-memory audit logger (for testing)
249#[derive(Debug)]
250pub struct MemoryAuditLogger {
251    entries: Arc<Mutex<Vec<AuditLogEntry>>>,
252    sequence: Arc<Mutex<u64>>,
253}
254
255impl MemoryAuditLogger {
256    /// Create new in-memory logger
257    pub fn new() -> Self {
258        Self {
259            entries: Arc::new(Mutex::new(Vec::new())),
260            sequence: Arc::new(Mutex::new(0)),
261        }
262    }
263
264    /// Get all logged entries
265    pub fn entries(&self) -> Vec<AuditLogEntry> {
266        self.entries.lock().expect("entries lock poisoned").clone()
267    }
268
269    /// Get entries filtered by event type
270    pub fn entries_by_type(&self, event_type: AuditEventType) -> Vec<AuditLogEntry> {
271        self.entries
272            .lock()
273            .expect("entries lock poisoned")
274            .iter()
275            .filter(|e| e.event_type == event_type)
276            .cloned()
277            .collect()
278    }
279
280    /// Clear all entries
281    pub fn clear(&self) {
282        self.entries.lock().expect("entries lock poisoned").clear();
283    }
284
285    fn next_sequence(&self) -> u64 {
286        let mut seq = self.sequence.lock().expect("sequence lock poisoned");
287        *seq += 1;
288        *seq
289    }
290
291    fn add_entry(&self, entry: AuditLogEntry) {
292        self.entries
293            .lock()
294            .expect("entries lock poisoned")
295            .push(entry);
296    }
297}
298
299impl Default for MemoryAuditLogger {
300    fn default() -> Self {
301        Self::new()
302    }
303}
304
305impl AuditLogger for MemoryAuditLogger {
306    fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>) {
307        let description = match (success, reason) {
308            (true, Some(r)) => format!("Authentication succeeded: {}", r),
309            (true, None) => "Authentication succeeded".to_string(),
310            (false, Some(r)) => format!("Authentication failed: {}", r),
311            (false, None) => "Authentication failed".to_string(),
312        };
313
314        let entry = AuditLogEntry::new(
315            AuditEventType::Authentication,
316            entity_id,
317            success,
318            description,
319            self.next_sequence(),
320        );
321        self.add_entry(entry);
322    }
323
324    fn log_grant(&self, entity_id: &str, permission: Permission, target: &str) {
325        let entry = AuditLogEntry::new(
326            AuditEventType::Authorization,
327            entity_id,
328            true,
329            format!("Permission granted: {:?}", permission),
330            self.next_sequence(),
331        )
332        .with_context("permission", format!("{:?}", permission))
333        .with_context("target", target);
334        self.add_entry(entry);
335    }
336
337    fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str) {
338        let entry = AuditLogEntry::new(
339            AuditEventType::Authorization,
340            entity_id,
341            false,
342            format!("Permission denied: {} - {}", permission, reason),
343            self.next_sequence(),
344        )
345        .with_context("permission", permission)
346        .with_context("target", target)
347        .with_context("reason", reason);
348        self.add_entry(entry);
349    }
350
351    fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
352        let event_type = if operation.starts_with("read")
353            || operation.starts_with("get")
354            || operation.starts_with("query")
355        {
356            AuditEventType::DataAccess
357        } else {
358            AuditEventType::DataModification
359        };
360
361        let entry = AuditLogEntry::new(
362            event_type,
363            entity_id,
364            success,
365            format!("{} on {}", operation, target),
366            self.next_sequence(),
367        )
368        .with_context("operation", operation)
369        .with_context("target", target);
370        self.add_entry(entry);
371    }
372
373    fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str) {
374        let entry = AuditLogEntry::new(
375            AuditEventType::SecurityViolation,
376            entity_id,
377            false,
378            format!("Security violation: {} - {}", violation, details),
379            self.next_sequence(),
380        )
381        .with_context("violation_type", violation.to_string())
382        .with_context("details", details);
383        self.add_entry(entry);
384    }
385
386    fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool) {
387        let entry = AuditLogEntry::new(
388            AuditEventType::CellFormation,
389            entity_id,
390            success,
391            format!("Cell {}: {}", action, cell_id),
392            self.next_sequence(),
393        )
394        .with_context("cell_id", cell_id)
395        .with_context("action", action);
396        self.add_entry(entry);
397    }
398
399    fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool) {
400        let entry = AuditLogEntry::new(
401            AuditEventType::KeyExchange,
402            entity_id,
403            success,
404            format!("Key exchange with peer: {}", peer_id),
405            self.next_sequence(),
406        )
407        .with_context("peer_id", peer_id);
408        self.add_entry(entry);
409    }
410
411    fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool) {
412        let entry = AuditLogEntry::new(
413            AuditEventType::SessionManagement,
414            entity_id,
415            success,
416            format!("Session {}: {}", action, session_id),
417            self.next_sequence(),
418        )
419        .with_context("session_id", session_id)
420        .with_context("action", action);
421        self.add_entry(entry);
422    }
423
424    fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
425        let entry = AuditLogEntry::new(
426            AuditEventType::Encryption,
427            entity_id,
428            success,
429            format!("Encryption {}: {}", operation, target),
430            self.next_sequence(),
431        )
432        .with_context("operation", operation)
433        .with_context("target", target);
434        self.add_entry(entry);
435    }
436
437    fn entry_count(&self) -> u64 {
438        self.entries.lock().expect("entries lock poisoned").len() as u64
439    }
440
441    fn flush(&self) -> Result<(), SecurityError> {
442        Ok(()) // In-memory, nothing to flush
443    }
444}
445
446/// File-based audit logger (append-only for tamper resistance)
447pub struct FileAuditLogger {
448    writer: Arc<Mutex<BufWriter<File>>>,
449    sequence: Arc<Mutex<u64>>,
450    path: String,
451}
452
453impl FileAuditLogger {
454    /// Create new file-based audit logger
455    ///
456    /// The file is opened in append mode for tamper resistance.
457    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, SecurityError> {
458        let path_str = path.as_ref().to_string_lossy().to_string();
459
460        let file = OpenOptions::new().create(true).append(true).open(&path)?;
461
462        Ok(Self {
463            writer: Arc::new(Mutex::new(BufWriter::new(file))),
464            sequence: Arc::new(Mutex::new(0)),
465            path: path_str,
466        })
467    }
468
469    /// Get the log file path
470    pub fn path(&self) -> &str {
471        &self.path
472    }
473
474    fn next_sequence(&self) -> u64 {
475        let mut seq = self.sequence.lock().expect("sequence lock poisoned");
476        *seq += 1;
477        *seq
478    }
479
480    fn write_entry(&self, entry: &AuditLogEntry) {
481        let json = entry.to_json();
482        if let Ok(mut writer) = self.writer.lock() {
483            let _ = writeln!(writer, "{}", json);
484            // Flush after each entry for durability
485            let _ = writer.flush();
486        }
487    }
488}
489
490impl std::fmt::Debug for FileAuditLogger {
491    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492        f.debug_struct("FileAuditLogger")
493            .field("path", &self.path)
494            .field("sequence", &self.sequence)
495            .finish()
496    }
497}
498
499impl AuditLogger for FileAuditLogger {
500    fn log_authentication(&self, entity_id: &str, success: bool, reason: Option<&str>) {
501        let description = match (success, reason) {
502            (true, Some(r)) => format!("Authentication succeeded: {}", r),
503            (true, None) => "Authentication succeeded".to_string(),
504            (false, Some(r)) => format!("Authentication failed: {}", r),
505            (false, None) => "Authentication failed".to_string(),
506        };
507
508        let entry = AuditLogEntry::new(
509            AuditEventType::Authentication,
510            entity_id,
511            success,
512            description,
513            self.next_sequence(),
514        );
515        self.write_entry(&entry);
516    }
517
518    fn log_grant(&self, entity_id: &str, permission: Permission, target: &str) {
519        let entry = AuditLogEntry::new(
520            AuditEventType::Authorization,
521            entity_id,
522            true,
523            format!("Permission granted: {:?}", permission),
524            self.next_sequence(),
525        )
526        .with_context("permission", format!("{:?}", permission))
527        .with_context("target", target);
528        self.write_entry(&entry);
529    }
530
531    fn log_denial(&self, entity_id: &str, permission: &str, target: &str, reason: &str) {
532        let entry = AuditLogEntry::new(
533            AuditEventType::Authorization,
534            entity_id,
535            false,
536            format!("Permission denied: {} - {}", permission, reason),
537            self.next_sequence(),
538        )
539        .with_context("permission", permission)
540        .with_context("target", target)
541        .with_context("reason", reason);
542        self.write_entry(&entry);
543    }
544
545    fn log_operation(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
546        let event_type = if operation.starts_with("read")
547            || operation.starts_with("get")
548            || operation.starts_with("query")
549        {
550            AuditEventType::DataAccess
551        } else {
552            AuditEventType::DataModification
553        };
554
555        let entry = AuditLogEntry::new(
556            event_type,
557            entity_id,
558            success,
559            format!("{} on {}", operation, target),
560            self.next_sequence(),
561        )
562        .with_context("operation", operation)
563        .with_context("target", target);
564        self.write_entry(&entry);
565    }
566
567    fn log_violation(&self, entity_id: &str, violation: SecurityViolation, details: &str) {
568        let entry = AuditLogEntry::new(
569            AuditEventType::SecurityViolation,
570            entity_id,
571            false,
572            format!("Security violation: {} - {}", violation, details),
573            self.next_sequence(),
574        )
575        .with_context("violation_type", violation.to_string())
576        .with_context("details", details);
577        self.write_entry(&entry);
578    }
579
580    fn log_cell_event(&self, entity_id: &str, cell_id: &str, action: &str, success: bool) {
581        let entry = AuditLogEntry::new(
582            AuditEventType::CellFormation,
583            entity_id,
584            success,
585            format!("Cell {}: {}", action, cell_id),
586            self.next_sequence(),
587        )
588        .with_context("cell_id", cell_id)
589        .with_context("action", action);
590        self.write_entry(&entry);
591    }
592
593    fn log_key_exchange(&self, entity_id: &str, peer_id: &str, success: bool) {
594        let entry = AuditLogEntry::new(
595            AuditEventType::KeyExchange,
596            entity_id,
597            success,
598            format!("Key exchange with peer: {}", peer_id),
599            self.next_sequence(),
600        )
601        .with_context("peer_id", peer_id);
602        self.write_entry(&entry);
603    }
604
605    fn log_session(&self, entity_id: &str, session_id: &str, action: &str, success: bool) {
606        let entry = AuditLogEntry::new(
607            AuditEventType::SessionManagement,
608            entity_id,
609            success,
610            format!("Session {}: {}", action, session_id),
611            self.next_sequence(),
612        )
613        .with_context("session_id", session_id)
614        .with_context("action", action);
615        self.write_entry(&entry);
616    }
617
618    fn log_encryption(&self, entity_id: &str, operation: &str, target: &str, success: bool) {
619        let entry = AuditLogEntry::new(
620            AuditEventType::Encryption,
621            entity_id,
622            success,
623            format!("Encryption {}: {}", operation, target),
624            self.next_sequence(),
625        )
626        .with_context("operation", operation)
627        .with_context("target", target);
628        self.write_entry(&entry);
629    }
630
631    fn entry_count(&self) -> u64 {
632        *self.sequence.lock().expect("sequence lock poisoned")
633    }
634
635    fn flush(&self) -> Result<(), SecurityError> {
636        self.writer
637            .lock()
638            .map_err(|e| SecurityError::Internal(e.to_string()))?
639            .flush()?;
640        Ok(())
641    }
642}
643
644/// No-op audit logger (for testing/disabled logging)
645#[derive(Debug, Default)]
646pub struct NullAuditLogger;
647
648impl NullAuditLogger {
649    pub fn new() -> Self {
650        Self
651    }
652}
653
654impl AuditLogger for NullAuditLogger {
655    fn log_authentication(&self, _entity_id: &str, _success: bool, _reason: Option<&str>) {}
656    fn log_grant(&self, _entity_id: &str, _permission: Permission, _target: &str) {}
657    fn log_denial(&self, _entity_id: &str, _permission: &str, _target: &str, _reason: &str) {}
658    fn log_operation(&self, _entity_id: &str, _operation: &str, _target: &str, _success: bool) {}
659    fn log_violation(&self, _entity_id: &str, _violation: SecurityViolation, _details: &str) {}
660    fn log_cell_event(&self, _entity_id: &str, _cell_id: &str, _action: &str, _success: bool) {}
661    fn log_key_exchange(&self, _entity_id: &str, _peer_id: &str, _success: bool) {}
662    fn log_session(&self, _entity_id: &str, _session_id: &str, _action: &str, _success: bool) {}
663    fn log_encryption(&self, _entity_id: &str, _operation: &str, _target: &str, _success: bool) {}
664    fn entry_count(&self) -> u64 {
665        0
666    }
667    fn flush(&self) -> Result<(), SecurityError> {
668        Ok(())
669    }
670}
671
672#[cfg(test)]
673mod tests {
674    use super::*;
675
676    #[test]
677    fn test_audit_entry_creation() {
678        let entry = AuditLogEntry::new(
679            AuditEventType::Authentication,
680            "device:abc123",
681            true,
682            "Test entry",
683            1,
684        );
685
686        assert_eq!(entry.entity_id, "device:abc123");
687        assert!(entry.success);
688        assert_eq!(entry.event_type, AuditEventType::Authentication);
689        assert_eq!(entry.sequence, 1);
690        assert!(entry.timestamp > 0);
691    }
692
693    #[test]
694    fn test_audit_entry_with_context() {
695        let entry = AuditLogEntry::new(
696            AuditEventType::Authorization,
697            "device:abc123",
698            false,
699            "Access denied",
700            1,
701        )
702        .with_context("permission", "SetCellLeader")
703        .with_context("cell_id", "cell-1");
704
705        assert_eq!(entry.context.get("permission").unwrap(), "SetCellLeader");
706        assert_eq!(entry.context.get("cell_id").unwrap(), "cell-1");
707    }
708
709    #[test]
710    fn test_audit_entry_json_serialization() {
711        let entry = AuditLogEntry::new(
712            AuditEventType::Authentication,
713            "device:abc123",
714            true,
715            "Login successful",
716            42,
717        );
718
719        let json = entry.to_json();
720        assert!(json.contains("\"entity_id\":\"device:abc123\""));
721        assert!(json.contains("\"success\":true"));
722        assert!(json.contains("\"sequence\":42"));
723    }
724
725    #[test]
726    fn test_memory_audit_logger() {
727        let logger = MemoryAuditLogger::new();
728
729        logger.log_authentication("device:abc", true, Some("verified"));
730        logger.log_denial("device:abc", "SetLeader", "cell-1", "not authorized");
731        logger.log_operation("device:abc", "store_cell", "cell-1", true);
732
733        assert_eq!(logger.entry_count(), 3);
734
735        let auth_entries = logger.entries_by_type(AuditEventType::Authentication);
736        assert_eq!(auth_entries.len(), 1);
737        assert!(auth_entries[0].success);
738
739        let authz_entries = logger.entries_by_type(AuditEventType::Authorization);
740        assert_eq!(authz_entries.len(), 1);
741        assert!(!authz_entries[0].success);
742    }
743
744    #[test]
745    fn test_memory_logger_all_event_types() {
746        let logger = MemoryAuditLogger::new();
747
748        logger.log_authentication("dev1", true, None);
749        logger.log_grant("dev1", Permission::JoinCell, "cell-1");
750        logger.log_denial("dev1", "SetLeader", "cell-1", "not leader");
751        logger.log_operation("dev1", "read_cell", "cell-1", true);
752        logger.log_operation("dev1", "store_cell", "cell-1", true);
753        logger.log_violation("dev2", SecurityViolation::InvalidSignature, "bad sig");
754        logger.log_cell_event("dev1", "cell-1", "join", true);
755        logger.log_key_exchange("dev1", "dev2", true);
756        logger.log_session("user1", "sess-1", "create", true);
757        logger.log_encryption("dev1", "encrypt_for_cell", "cell-1", true);
758
759        assert_eq!(logger.entry_count(), 10);
760
761        // Verify each event type is logged
762        assert_eq!(
763            logger.entries_by_type(AuditEventType::Authentication).len(),
764            1
765        );
766        assert_eq!(
767            logger.entries_by_type(AuditEventType::Authorization).len(),
768            2
769        );
770        assert_eq!(logger.entries_by_type(AuditEventType::DataAccess).len(), 1);
771        assert_eq!(
772            logger
773                .entries_by_type(AuditEventType::DataModification)
774                .len(),
775            1
776        );
777        assert_eq!(
778            logger
779                .entries_by_type(AuditEventType::SecurityViolation)
780                .len(),
781            1
782        );
783        assert_eq!(
784            logger.entries_by_type(AuditEventType::CellFormation).len(),
785            1
786        );
787        assert_eq!(logger.entries_by_type(AuditEventType::KeyExchange).len(), 1);
788        assert_eq!(
789            logger
790                .entries_by_type(AuditEventType::SessionManagement)
791                .len(),
792            1
793        );
794        assert_eq!(logger.entries_by_type(AuditEventType::Encryption).len(), 1);
795    }
796
797    #[test]
798    fn test_file_audit_logger() {
799        let temp_dir = tempfile::tempdir().unwrap();
800        let log_path = temp_dir.path().join("audit.log");
801
802        let logger = FileAuditLogger::new(&log_path).unwrap();
803        logger.log_authentication("device:test", true, Some("test auth"));
804        logger.log_denial("device:test", "TestPerm", "target", "testing");
805        logger.flush().unwrap();
806
807        // Read and verify log file
808        let contents = std::fs::read_to_string(&log_path).unwrap();
809        let lines: Vec<&str> = contents.lines().collect();
810        assert_eq!(lines.len(), 2);
811
812        // Parse first line as JSON
813        let entry: AuditLogEntry = serde_json::from_str(lines[0]).unwrap();
814        assert_eq!(entry.entity_id, "device:test");
815        assert!(entry.success);
816    }
817
818    #[test]
819    fn test_null_audit_logger() {
820        let logger = NullAuditLogger::new();
821
822        // All operations should be no-ops
823        logger.log_authentication("test", true, None);
824        logger.log_denial("test", "perm", "target", "reason");
825
826        assert_eq!(logger.entry_count(), 0);
827        assert!(logger.flush().is_ok());
828    }
829
830    #[test]
831    fn test_security_violation_types() {
832        let violations = vec![
833            SecurityViolation::InvalidSignature,
834            SecurityViolation::ReplayAttack,
835            SecurityViolation::UnauthorizedAccess,
836            SecurityViolation::CertificateError,
837            SecurityViolation::TamperedMessage,
838            SecurityViolation::RateLimitExceeded,
839            SecurityViolation::UnknownDevice,
840            SecurityViolation::ExpiredCredentials,
841            SecurityViolation::ProtocolViolation,
842        ];
843
844        let logger = MemoryAuditLogger::new();
845        for violation in violations {
846            logger.log_violation("test", violation, "test details");
847        }
848
849        assert_eq!(logger.entry_count(), 9);
850    }
851
852    #[test]
853    fn test_audit_entry_sequence_ordering() {
854        let logger = MemoryAuditLogger::new();
855
856        for i in 0..10 {
857            logger.log_authentication(&format!("dev{}", i), true, None);
858        }
859
860        let entries = logger.entries();
861        for (i, entry) in entries.iter().enumerate() {
862            assert_eq!(entry.sequence, (i + 1) as u64);
863        }
864    }
865
866    #[test]
867    fn test_event_type_display() {
868        assert_eq!(
869            format!("{}", AuditEventType::Authentication),
870            "AUTHENTICATION"
871        );
872        assert_eq!(
873            format!("{}", AuditEventType::Authorization),
874            "AUTHORIZATION"
875        );
876        assert_eq!(
877            format!("{}", AuditEventType::SecurityViolation),
878            "SECURITY_VIOLATION"
879        );
880    }
881}