use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::types::AnomalyType;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendsConfig {
pub enabled: bool,
pub bucket_size_ms: u64,
pub retention_hours: u32,
pub max_signals_per_bucket: usize,
pub anomaly_check_interval_ms: u64,
pub anomaly_risk: HashMap<AnomalyType, u32>,
pub max_entities: usize,
pub max_recent_signals: usize,
pub max_anomalies: usize,
}
impl Default for TrendsConfig {
fn default() -> Self {
let mut anomaly_risk = HashMap::new();
anomaly_risk.insert(AnomalyType::FingerprintChange, 30);
anomaly_risk.insert(AnomalyType::SessionSharing, 50);
anomaly_risk.insert(AnomalyType::TokenReuse, 40);
anomaly_risk.insert(AnomalyType::VelocitySpike, 15);
anomaly_risk.insert(AnomalyType::RotationPattern, 35);
anomaly_risk.insert(AnomalyType::TimingAnomaly, 10);
anomaly_risk.insert(AnomalyType::ImpossibleTravel, 25);
anomaly_risk.insert(AnomalyType::Ja4RotationPattern, 45);
anomaly_risk.insert(AnomalyType::Ja4IpCluster, 35);
anomaly_risk.insert(AnomalyType::Ja4BrowserSpoofing, 60);
anomaly_risk.insert(AnomalyType::Ja4hChange, 25);
anomaly_risk.insert(AnomalyType::OversizedRequest, 20);
anomaly_risk.insert(AnomalyType::OversizedResponse, 15);
anomaly_risk.insert(AnomalyType::BandwidthSpike, 25);
anomaly_risk.insert(AnomalyType::ExfiltrationPattern, 40);
anomaly_risk.insert(AnomalyType::UploadPattern, 35);
Self {
enabled: true,
bucket_size_ms: 60_000, retention_hours: 24,
max_signals_per_bucket: 10_000,
anomaly_check_interval_ms: 60_000,
anomaly_risk,
max_entities: 10_000,
max_recent_signals: 100,
max_anomalies: 1_000,
}
}
}
impl TrendsConfig {
pub fn disabled() -> Self {
Self {
enabled: false,
..Default::default()
}
}
pub fn get_anomaly_risk(&self, anomaly_type: &AnomalyType) -> u32 {
self.anomaly_risk.get(anomaly_type).copied().unwrap_or(0)
}
pub fn bucket_count(&self) -> usize {
let retention_ms = self.retention_hours as u64 * 60 * 60 * 1000;
(retention_ms / self.bucket_size_ms) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = TrendsConfig::default();
assert!(config.enabled);
assert_eq!(config.bucket_size_ms, 60_000);
assert_eq!(config.retention_hours, 24);
assert_eq!(config.max_signals_per_bucket, 10_000);
}
#[test]
fn test_disabled_config() {
let config = TrendsConfig::disabled();
assert!(!config.enabled);
}
#[test]
fn test_anomaly_risk_lookup() {
let config = TrendsConfig::default();
assert_eq!(config.get_anomaly_risk(&AnomalyType::FingerprintChange), 30);
assert_eq!(config.get_anomaly_risk(&AnomalyType::SessionSharing), 50);
}
#[test]
fn test_bucket_count() {
let config = TrendsConfig::default();
assert_eq!(config.bucket_count(), 1440);
}
}