synapse_pingora/trends/
config.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::types::AnomalyType;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TrendsConfig {
11 pub enabled: bool,
13
14 pub bucket_size_ms: u64,
16
17 pub retention_hours: u32,
19
20 pub max_signals_per_bucket: usize,
22
23 pub anomaly_check_interval_ms: u64,
25
26 pub anomaly_risk: HashMap<AnomalyType, u32>,
28
29 pub max_entities: usize,
31
32 pub max_recent_signals: usize,
34
35 pub max_anomalies: usize,
37}
38
39impl Default for TrendsConfig {
40 fn default() -> Self {
41 let mut anomaly_risk = HashMap::new();
42
43 anomaly_risk.insert(AnomalyType::FingerprintChange, 30);
45 anomaly_risk.insert(AnomalyType::SessionSharing, 50);
46 anomaly_risk.insert(AnomalyType::TokenReuse, 40);
47 anomaly_risk.insert(AnomalyType::VelocitySpike, 15);
48 anomaly_risk.insert(AnomalyType::RotationPattern, 35);
49 anomaly_risk.insert(AnomalyType::TimingAnomaly, 10);
50 anomaly_risk.insert(AnomalyType::ImpossibleTravel, 25);
51
52 anomaly_risk.insert(AnomalyType::Ja4RotationPattern, 45);
54 anomaly_risk.insert(AnomalyType::Ja4IpCluster, 35);
55 anomaly_risk.insert(AnomalyType::Ja4BrowserSpoofing, 60);
56 anomaly_risk.insert(AnomalyType::Ja4hChange, 25);
57
58 anomaly_risk.insert(AnomalyType::OversizedRequest, 20);
60 anomaly_risk.insert(AnomalyType::OversizedResponse, 15);
61 anomaly_risk.insert(AnomalyType::BandwidthSpike, 25);
62 anomaly_risk.insert(AnomalyType::ExfiltrationPattern, 40);
63 anomaly_risk.insert(AnomalyType::UploadPattern, 35);
64
65 Self {
66 enabled: true,
67 bucket_size_ms: 60_000, retention_hours: 24,
69 max_signals_per_bucket: 10_000,
70 anomaly_check_interval_ms: 60_000,
71 anomaly_risk,
72 max_entities: 10_000,
73 max_recent_signals: 100,
74 max_anomalies: 1_000,
75 }
76 }
77}
78
79impl TrendsConfig {
80 pub fn disabled() -> Self {
82 Self {
83 enabled: false,
84 ..Default::default()
85 }
86 }
87
88 pub fn get_anomaly_risk(&self, anomaly_type: &AnomalyType) -> u32 {
90 self.anomaly_risk.get(anomaly_type).copied().unwrap_or(0)
91 }
92
93 pub fn bucket_count(&self) -> usize {
95 let retention_ms = self.retention_hours as u64 * 60 * 60 * 1000;
96 (retention_ms / self.bucket_size_ms) as usize
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_default_config() {
106 let config = TrendsConfig::default();
107 assert!(config.enabled);
108 assert_eq!(config.bucket_size_ms, 60_000);
109 assert_eq!(config.retention_hours, 24);
110 assert_eq!(config.max_signals_per_bucket, 10_000);
111 }
112
113 #[test]
114 fn test_disabled_config() {
115 let config = TrendsConfig::disabled();
116 assert!(!config.enabled);
117 }
118
119 #[test]
120 fn test_anomaly_risk_lookup() {
121 let config = TrendsConfig::default();
122 assert_eq!(config.get_anomaly_risk(&AnomalyType::FingerprintChange), 30);
123 assert_eq!(config.get_anomaly_risk(&AnomalyType::SessionSharing), 50);
124 }
125
126 #[test]
127 fn test_bucket_count() {
128 let config = TrendsConfig::default();
129 assert_eq!(config.bucket_count(), 1440);
131 }
132}