use anyhow::Result;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::{Duration, Instant};
use tracing::error;
use crate::config::ShieldConfig;
use crate::scanner::Threat;
use crate::traits::SecurityEventProcessor;
pub mod cli;
pub mod display;
pub mod universal_display;
pub use cli::{CliShield, DisplayFormat, ShieldStatus};
pub use display::ShieldDisplay;
pub use universal_display::{UniversalDisplay, UniversalDisplayConfig, UniversalShieldStatus};
pub struct Shield {
active: AtomicBool,
start_time: Instant,
threats_blocked: AtomicU64,
recent_threats: Arc<Mutex<VecDeque<TimestampedThreat>>>,
config: ShieldConfig,
event_processor_enabled: AtomicBool,
event_processor: Mutex<Option<Weak<dyn SecurityEventProcessor>>>,
}
#[derive(Clone)]
struct TimestampedThreat {
threat: Threat,
timestamp: Instant,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ShieldInfo {
pub active: bool,
#[serde(with = "serde_duration")]
pub uptime: Duration,
pub threats_blocked: u64,
pub recent_threat_rate: f64,
}
mod serde_duration {
use serde::{Deserialize, Deserializer, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(duration.as_secs())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let secs = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(secs))
}
}
pub struct ShieldStats {
pub threats_blocked: u64,
pub active: bool,
}
impl Default for Shield {
fn default() -> Self {
Self::new()
}
}
impl Shield {
pub fn new() -> Self {
Self::with_config(ShieldConfig::default())
}
pub fn with_config(config: ShieldConfig) -> Self {
Self {
active: AtomicBool::new(false),
start_time: Instant::now(),
threats_blocked: AtomicU64::new(0),
recent_threats: Arc::new(Mutex::new(VecDeque::with_capacity(1000))),
config,
event_processor_enabled: AtomicBool::new(false),
event_processor: Mutex::new(None),
}
}
pub fn set_active(&self, active: bool) {
self.active.store(active, Ordering::Relaxed);
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Relaxed)
}
pub fn set_event_processor_enabled(&self, enabled: bool) {
self.event_processor_enabled
.store(enabled, Ordering::Relaxed);
}
pub fn is_event_processor_enabled(&self) -> bool {
self.event_processor_enabled.load(Ordering::Relaxed)
}
pub fn set_event_processor(&self, processor: &Arc<dyn SecurityEventProcessor>) {
match self.event_processor.lock() {
Ok(mut ep) => *ep = Some(Arc::downgrade(processor)),
Err(e) => error!("Failed to acquire event processor lock: {}", e),
}
}
pub fn record_threats(&self, threats: &[Threat]) {
if threats.is_empty() {
return;
}
let count = threats.len() as u64;
self.threats_blocked.fetch_add(count, Ordering::Relaxed);
let now = Instant::now();
let Ok(mut recent) = self.recent_threats.lock() else {
error!("Failed to acquire recent threats lock");
return;
};
for threat in threats {
recent.push_back(TimestampedThreat {
threat: threat.clone(),
timestamp: now,
});
while recent.len() > 1000 {
recent.pop_front();
}
}
}
pub fn get_info(&self) -> ShieldInfo {
let now = Instant::now();
let uptime = now.duration_since(self.start_time);
let recent_rate = match self.recent_threats.lock() {
Ok(recent) => {
let five_mins_ago = now.checked_sub(Duration::from_secs(300)).unwrap_or(now); let recent_count = recent
.iter()
.filter(|t| t.timestamp > five_mins_ago)
.count() as f64;
recent_count / 5.0 },
Err(e) => {
error!("Failed to acquire recent threats lock: {}", e);
0.0 },
};
if self.is_event_processor_enabled() {
if let Ok(ep_lock) = self.event_processor.lock() {
if let Some(weak_proc) = ep_lock.as_ref() {
if let Some(processor) = weak_proc.upgrade() {
if processor.is_monitored("any") {
tracing::trace!("Attack pattern correlation active");
}
}
}
}
}
ShieldInfo {
active: self.is_active(),
uptime,
threats_blocked: self.threats_blocked.load(Ordering::Relaxed),
recent_threat_rate: recent_rate,
}
}
pub fn get_recent_threats(&self, limit: usize) -> Vec<Threat> {
match self.recent_threats.lock() {
Ok(recent) => recent
.iter()
.rev()
.take(limit)
.map(|t| t.threat.clone())
.collect(),
Err(e) => {
error!("Failed to acquire recent threats lock: {}", e);
vec![]
},
}
}
pub fn get_threat_stats(&self) -> std::collections::HashMap<crate::scanner::ThreatType, u64> {
use std::collections::HashMap;
match self.recent_threats.lock() {
Ok(recent) => {
let mut stats = HashMap::new();
for item in recent.iter() {
*stats.entry(item.threat.threat_type.clone()).or_insert(0) += 1;
}
stats
},
Err(e) => {
error!("Failed to acquire recent threats lock: {}", e);
HashMap::new()
},
}
}
pub async fn start_display(self: Arc<Self>) -> Result<()> {
if !self.config.enabled {
return Ok(());
}
let display = ShieldDisplay::new(self.clone(), self.config.clone());
display.run().await
}
pub const fn start_time(&self) -> Instant {
self.start_time
}
pub fn stats(&self) -> ShieldStats {
ShieldStats {
threats_blocked: self.threats_blocked.load(Ordering::Relaxed),
active: self.is_active(),
}
}
pub fn last_threat_type(&self) -> Option<String> {
match self.recent_threats.lock() {
Ok(recent) => recent.back().map(|t| format!("{}", t.threat.threat_type)),
Err(e) => {
error!("Failed to acquire recent threats lock: {}", e);
None
},
}
}
pub fn scanner_stats(&self) -> crate::scanner::ScannerStats {
match self.recent_threats.lock() {
Ok(threats) => {
let (unicode_count, injection_count) =
threats
.iter()
.fold((0u64, 0u64), |(unicode, injection), item| {
match &item.threat.threat_type {
crate::scanner::ThreatType::UnicodeInvisible
| crate::scanner::ThreatType::UnicodeBiDi
| crate::scanner::ThreatType::UnicodeHomograph => {
(unicode + 1, injection)
},
crate::scanner::ThreatType::SqlInjection
| crate::scanner::ThreatType::CommandInjection
| crate::scanner::ThreatType::PromptInjection
| crate::scanner::ThreatType::PathTraversal => {
(unicode, injection + 1)
},
_ => (unicode, injection),
}
});
crate::scanner::ScannerStats {
unicode_threats_detected: unicode_count,
injection_threats_detected: injection_count,
total_scans: self.threats_blocked.load(Ordering::Relaxed),
}
},
Err(_) => crate::scanner::ScannerStats {
unicode_threats_detected: 0,
injection_threats_detected: 0,
total_scans: 0,
},
}
}
pub fn set_enabled(&self, enabled: bool) {
if enabled {
tracing::info!("Shield display enabled");
self.set_active(true);
} else {
tracing::info!("Shield display disabled");
self.set_active(false);
}
}
}