use crate::error::{NetwatchError, Result};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq)]
pub enum SecurityEvent {
InvalidInput {
input_type: String,
attempted_value: String,
source: String,
},
SuspiciousFileAccess { path: String, access_type: String },
RateLimitExceeded { source: String, attempt_count: u32 },
ConfigTampering {
config_field: String,
old_value: String,
new_value: String,
},
ResourceExhaustion {
resource_type: String,
usage_amount: u64,
limit: u64,
},
}
pub struct SecurityMonitor {
events: Vec<(Instant, SecurityEvent)>,
event_counts: HashMap<String, u32>,
rate_limits: HashMap<String, (Instant, u32)>,
max_events: usize,
event_cursor: usize, last_cleanup: Instant,
high_performance_mode: bool,
events_this_second: u32,
current_second: u64,
}
impl SecurityMonitor {
pub fn new() -> Self {
let now = Instant::now();
Self {
events: Vec::with_capacity(1000),
event_counts: HashMap::new(),
rate_limits: HashMap::new(),
max_events: 1000, event_cursor: 0,
last_cleanup: now,
high_performance_mode: false,
events_this_second: 0,
current_second: now.elapsed().as_secs(),
}
}
pub fn set_high_performance_mode(&mut self, enabled: bool) {
self.high_performance_mode = enabled;
if enabled {
self.max_events = 500;
if self.events.capacity() > self.max_events {
self.events.truncate(self.max_events);
self.events.shrink_to_fit();
}
}
}
pub fn record_event(&mut self, event: SecurityEvent) {
let now = Instant::now();
let current_second = now.elapsed().as_secs();
if current_second != self.current_second {
self.current_second = current_second;
self.events_this_second = 0;
}
if self.high_performance_mode {
self.events_this_second += 1;
if self.events_this_second > 100 && !self.is_critical_event(&event) {
return;
}
if self.events_this_second > 500 {
return;
}
}
if self.events.len() < self.max_events {
self.events.push((now, event.clone()));
} else {
self.events[self.event_cursor] = (now, event.clone());
self.event_cursor = (self.event_cursor + 1) % self.max_events;
}
if !self.high_performance_mode || self.is_critical_event(&event) {
let event_key = self.event_key(&event);
*self.event_counts.entry(event_key).or_insert(0) += 1;
}
if self.is_critical_event(&event) {
eprintln!("SECURITY ALERT: {event:?}");
}
if now.duration_since(self.last_cleanup) > Duration::from_secs(300) {
self.cleanup_old_data(now);
}
}
fn cleanup_old_data(&mut self, now: Instant) {
self.last_cleanup = now;
if self.high_performance_mode && self.event_counts.len() > 100 {
let keys_to_remove: Vec<String> = self
.event_counts
.iter()
.filter(|(_, &count)| count < 5) .map(|(key, _)| key.clone())
.collect();
for key in keys_to_remove {
self.event_counts.remove(&key);
}
}
let cutoff = now - Duration::from_secs(120); self.rate_limits.retain(|_, (time, _)| *time > cutoff);
}
pub fn check_rate_limit(&mut self, source: &str, max_per_minute: u32) -> Result<()> {
let now = Instant::now();
let key = format!("rate_limit_{source}");
match self.rate_limits.get_mut(&key) {
Some((last_reset, count)) => {
if now.duration_since(*last_reset) > Duration::from_secs(60) {
*last_reset = now;
*count = 1;
} else {
*count += 1;
if *count > max_per_minute {
return Err(NetwatchError::Security(format!(
"Rate limit exceeded for source: {source}"
)));
}
}
}
None => {
self.rate_limits.insert(key, (now, 1));
}
}
Ok(())
}
pub fn get_statistics(&self) -> SecurityStatistics {
if self.high_performance_mode {
return SecurityStatistics {
total_events: self.events.len(),
events_last_hour: self.events.len().min(100), events_last_day: self.events.len(),
critical_events: 0, event_types: HashMap::new(), };
}
let now = Instant::now();
let last_hour = now - Duration::from_secs(3600);
let last_day = now - Duration::from_secs(86400);
let mut events_last_hour = 0;
let mut events_last_day = 0;
let mut critical_events = 0;
for (time, event) in &self.events {
if *time > last_day {
events_last_day += 1;
if *time > last_hour {
events_last_hour += 1;
}
}
if self.is_critical_event(event) {
critical_events += 1;
}
}
SecurityStatistics {
total_events: self.events.len(),
events_last_hour,
events_last_day,
critical_events,
event_types: self.event_counts.clone(),
}
}
pub fn check_anomalies(&self) -> Vec<SecurityAnomaly> {
if self.high_performance_mode {
return Vec::new();
}
let mut anomalies = Vec::new();
let now = Instant::now();
let last_minute = now - Duration::from_secs(60);
let mut recent_events = 0;
let mut invalid_input_sources = HashMap::new();
for (time, event) in &self.events {
if *time > last_minute {
recent_events += 1;
if let SecurityEvent::InvalidInput { source, .. } = event {
*invalid_input_sources.entry(source.clone()).or_insert(0) += 1;
}
}
}
if recent_events > 10 {
anomalies.push(SecurityAnomaly::EventBurst {
event_count: recent_events,
time_window: Duration::from_secs(60),
});
}
for (source, count) in invalid_input_sources {
if count > 3 {
anomalies.push(SecurityAnomaly::RepeatedInvalidInput {
source,
attempt_count: count,
});
}
}
anomalies
}
fn event_key(&self, event: &SecurityEvent) -> String {
match event {
SecurityEvent::InvalidInput { input_type, .. } => format!("invalid_input_{input_type}"),
SecurityEvent::SuspiciousFileAccess { .. } => "suspicious_file_access".to_string(),
SecurityEvent::RateLimitExceeded { .. } => "rate_limit_exceeded".to_string(),
SecurityEvent::ConfigTampering { .. } => "config_tampering".to_string(),
SecurityEvent::ResourceExhaustion { .. } => "resource_exhaustion".to_string(),
}
}
fn is_critical_event(&self, event: &SecurityEvent) -> bool {
matches!(
event,
SecurityEvent::ConfigTampering { .. }
| SecurityEvent::SuspiciousFileAccess { .. }
| SecurityEvent::ResourceExhaustion { .. }
)
}
}
impl Default for SecurityMonitor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SecurityStatistics {
pub total_events: usize,
pub events_last_hour: usize,
pub events_last_day: usize,
pub critical_events: usize,
pub event_types: HashMap<String, u32>,
}
#[derive(Debug, Clone)]
pub enum SecurityAnomaly {
EventBurst {
event_count: usize,
time_window: Duration,
},
RepeatedInvalidInput { source: String, attempt_count: u32 },
UnusualAccessPattern {
pattern_description: String,
confidence: f32,
},
}
static mut SECURITY_MONITOR: Option<SecurityMonitor> = None;
static mut MONITOR_INITIALIZED: bool = false;
pub fn init_security_monitor() {
unsafe {
if !MONITOR_INITIALIZED {
SECURITY_MONITOR = Some(SecurityMonitor::new());
MONITOR_INITIALIZED = true;
}
}
}
pub fn enable_high_performance_security(enabled: bool) {
unsafe {
if let Some(ref mut monitor) = SECURITY_MONITOR {
monitor.set_high_performance_mode(enabled);
}
}
}
pub fn record_security_event(event: SecurityEvent) {
unsafe {
if let Some(ref mut monitor) = SECURITY_MONITOR {
monitor.record_event(event);
}
}
}
pub fn check_security_rate_limit(source: &str, max_per_minute: u32) -> Result<()> {
unsafe {
if let Some(ref mut monitor) = SECURITY_MONITOR {
monitor.check_rate_limit(source, max_per_minute)
} else {
Ok(())
}
}
}
#[allow(static_mut_refs)]
pub fn get_security_statistics() -> Option<SecurityStatistics> {
unsafe {
SECURITY_MONITOR
.as_ref()
.map(|monitor| monitor.get_statistics())
}
}
#[allow(static_mut_refs)]
pub fn check_security_anomalies() -> Vec<SecurityAnomaly> {
unsafe {
SECURITY_MONITOR
.as_ref()
.map(|monitor| monitor.check_anomalies())
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_event_recording() {
let mut monitor = SecurityMonitor::new();
let event = SecurityEvent::InvalidInput {
input_type: "interface_name".to_string(),
attempted_value: "../etc/passwd".to_string(),
source: "cli".to_string(),
};
monitor.record_event(event);
let stats = monitor.get_statistics();
assert_eq!(stats.total_events, 1);
assert_eq!(
stats.event_types.get("invalid_input_interface_name"),
Some(&1)
);
}
#[test]
fn test_rate_limiting() {
let mut monitor = SecurityMonitor::new();
for _i in 0..3 {
assert!(monitor.check_rate_limit("test_source", 5).is_ok());
}
assert!(monitor.check_rate_limit("test_source", 5).is_ok());
assert!(monitor.check_rate_limit("test_source", 5).is_ok());
assert!(monitor.check_rate_limit("test_source", 5).is_err());
}
#[test]
fn test_anomaly_detection() {
let mut monitor = SecurityMonitor::new();
for i in 0..15 {
let event = SecurityEvent::InvalidInput {
input_type: "test".to_string(),
attempted_value: format!("attack_{i}"),
source: "attacker".to_string(),
};
monitor.record_event(event);
}
let anomalies = monitor.check_anomalies();
assert!(!anomalies.is_empty());
assert!(anomalies
.iter()
.any(|a| matches!(a, SecurityAnomaly::EventBurst { .. })));
assert!(anomalies
.iter()
.any(|a| matches!(a, SecurityAnomaly::RepeatedInvalidInput { .. })));
}
#[test]
fn test_critical_event_detection() {
let mut monitor = SecurityMonitor::new();
let critical_event = SecurityEvent::ConfigTampering {
config_field: "log_file".to_string(),
old_value: "/tmp/netwatch.log".to_string(),
new_value: "/etc/passwd".to_string(),
};
monitor.record_event(critical_event);
let stats = monitor.get_statistics();
assert_eq!(stats.critical_events, 1);
}
}