use crate::types::{Cookie, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub mod anomaly_detector;
pub mod behavioral_analysis;
pub mod models;
pub mod pattern_recognition;
pub mod training;
pub mod zero_day_detector;
pub use anomaly_detector::{Anomaly, AnomalyDetector, AnomalyReason, AnomalySeverity};
pub use behavioral_analysis::{BehaviorChange, BehaviorReport, BehavioralAnalyzer};
pub use pattern_recognition::{CookiePattern, PatternRecognizer, PatternType};
pub use zero_day_detector::{ZeroDayDetector, ZeroDaySignature};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MLConfig {
pub anomaly_threshold: f64,
pub n_trees: usize,
pub sample_size: usize,
pub enable_behavioral_analysis: bool,
pub enable_pattern_recognition: bool,
pub enable_zero_day_detection: bool,
pub min_training_samples: usize,
pub max_features: usize,
pub random_seed: Option<u64>,
}
impl Default for MLConfig {
fn default() -> Self {
Self {
anomaly_threshold: 0.7,
n_trees: 100,
sample_size: 256,
enable_behavioral_analysis: true,
enable_pattern_recognition: true,
enable_zero_day_detection: true,
min_training_samples: 100,
max_features: 50,
random_seed: None,
}
}
}
#[derive(Debug)]
pub struct MLAnalyzer {
config: MLConfig,
anomaly_detector: Option<AnomalyDetector>,
behavioral_analyzer: Option<BehavioralAnalyzer>,
pattern_recognizer: Option<PatternRecognizer>,
#[allow(dead_code)]
zero_day_detector: Option<ZeroDayDetector>,
is_trained: bool,
}
impl MLAnalyzer {
#[must_use]
pub fn new() -> Self {
Self::with_config(&MLConfig::default())
}
#[must_use]
pub fn with_config(config: &MLConfig) -> Self {
Self {
config: config.clone(),
anomaly_detector: Some(AnomalyDetector::new(config.clone())),
behavioral_analyzer: if config.enable_behavioral_analysis {
Some(BehavioralAnalyzer::new())
} else {
None
},
pattern_recognizer: if config.enable_pattern_recognition {
Some(PatternRecognizer::new())
} else {
None
},
zero_day_detector: if config.enable_zero_day_detection {
Some(ZeroDayDetector::new())
} else {
None
},
is_trained: false,
}
}
pub fn train(&mut self, normal_cookies: &[Cookie]) -> Result<TrainingReport> {
if normal_cookies.len() < self.config.min_training_samples {
return Err(crate::types::Error::InvalidInput(format!(
"Not enough training samples: {} < {}",
normal_cookies.len(),
self.config.min_training_samples
)));
}
let mut report = TrainingReport {
samples_used: normal_cookies.len(),
models_trained: Vec::new(),
training_duration: std::time::Duration::from_secs(0),
success: true,
};
let start_time = std::time::Instant::now();
if let Some(ref mut detector) = self.anomaly_detector {
match detector.train(normal_cookies) {
Ok(()) => report.models_trained.push("Anomaly Detector".to_string()),
Err(e) => {
report.success = false;
tracing::error!("Failed to train anomaly detector: {}", e);
}
}
}
if let Some(ref mut analyzer) = self.behavioral_analyzer {
match analyzer.train(normal_cookies) {
Ok(()) => report
.models_trained
.push("Behavioral Analyzer".to_string()),
Err(e) => {
report.success = false;
tracing::error!("Failed to train behavioral analyzer: {}", e);
}
}
}
if let Some(ref mut recognizer) = self.pattern_recognizer {
match recognizer.train(normal_cookies) {
Ok(()) => report.models_trained.push("Pattern Recognizer".to_string()),
Err(e) => {
report.success = false;
tracing::error!("Failed to train pattern recognizer: {}", e);
}
}
}
report.training_duration = start_time.elapsed();
self.is_trained = report.success;
Ok(report)
}
pub fn detect(&self, cookies: &[Cookie]) -> Result<Vec<Anomaly>> {
if !self.is_trained {
return Err(crate::types::Error::InvalidState(
"ML models must be trained before detection".to_string(),
));
}
let mut all_anomalies = Vec::new();
if let Some(ref detector) = self.anomaly_detector {
let anomalies = detector.detect(cookies)?;
all_anomalies.extend(anomalies);
}
if let Some(ref analyzer) = self.behavioral_analyzer {
let behavior_changes = analyzer.detect_changes(cookies)?;
for change in behavior_changes {
all_anomalies.push(Anomaly {
cookie: change.cookie,
score: change.severity_score,
reasons: vec![AnomalyReason::UnexpectedBehavior],
severity: AnomalySeverity::from_score(change.severity_score),
explanation: Some(change.description),
detected_at: Utc::now(),
});
}
}
if let Some(ref recognizer) = self.pattern_recognizer {
let suspicious_patterns = recognizer.find_suspicious(cookies)?;
for cookie in suspicious_patterns {
all_anomalies.push(Anomaly {
cookie: cookie.clone(),
score: 0.8,
reasons: vec![AnomalyReason::SuspiciousPattern],
severity: AnomalySeverity::High,
explanation: Some("Suspicious pattern detected".to_string()),
detected_at: Utc::now(),
});
}
}
all_anomalies.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(all_anomalies
.into_iter()
.filter(|a| a.score >= self.config.anomaly_threshold)
.collect())
}
pub fn analyze_behavior(&self, cookies: &[Cookie]) -> Result<BehaviorReport> {
if let Some(ref analyzer) = self.behavioral_analyzer {
analyzer.analyze(cookies)
} else {
Err(crate::types::Error::InvalidState(
"Behavioral analysis is disabled".to_string(),
))
}
}
#[must_use]
pub fn is_trained(&self) -> bool {
self.is_trained
}
#[must_use]
pub fn config(&self) -> &MLConfig {
&self.config
}
}
impl Default for MLAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingReport {
pub samples_used: usize,
pub models_trained: Vec<String>,
#[serde(skip)]
pub training_duration: std::time::Duration,
pub success: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MLAnalysisResult {
pub anomalies: Vec<Anomaly>,
pub behavior: Option<BehaviorReport>,
pub risk_score: f64,
pub recommendations: Vec<String>,
pub analyzed_at: DateTime<Utc>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ml_config_default() {
let config = MLConfig::default();
assert_eq!(config.anomaly_threshold, 0.7);
assert_eq!(config.n_trees, 100);
assert!(config.enable_behavioral_analysis);
}
#[test]
fn test_ml_analyzer_creation() {
let analyzer = MLAnalyzer::new();
assert!(!analyzer.is_trained());
}
#[test]
fn test_ml_analyzer_requires_training() {
let analyzer = MLAnalyzer::new();
let cookies = vec![];
let result = analyzer.detect(&cookies);
assert!(result.is_err());
}
}