Skip to main content

synapse_pingora/trends/
config.rs

1//! Configuration for the trends subsystem.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::types::AnomalyType;
7
8/// Configuration for the trends subsystem.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TrendsConfig {
11    /// Whether trends tracking is enabled
12    pub enabled: bool,
13
14    /// Size of each time bucket in milliseconds (default: 60000 = 1 minute)
15    pub bucket_size_ms: u64,
16
17    /// How long to retain data in hours (default: 24)
18    pub retention_hours: u32,
19
20    /// Maximum signals per bucket (default: 10000)
21    pub max_signals_per_bucket: usize,
22
23    /// Anomaly detection check interval in milliseconds (default: 60000)
24    pub anomaly_check_interval_ms: u64,
25
26    /// Risk scores to apply for each anomaly type (0 = detection only)
27    pub anomaly_risk: HashMap<AnomalyType, u32>,
28
29    /// Maximum entities to track (LRU eviction)
30    pub max_entities: usize,
31
32    /// Maximum recent signals to cache per entity
33    pub max_recent_signals: usize,
34
35    /// Maximum anomalies to retain
36    pub max_anomalies: usize,
37}
38
39impl Default for TrendsConfig {
40    fn default() -> Self {
41        let mut anomaly_risk = HashMap::new();
42
43        // Authentication/session anomalies
44        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        // JA4 fingerprint anomalies
53        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        // Payload anomalies
59        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, // 1 minute
68            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    /// Create a disabled configuration.
81    pub fn disabled() -> Self {
82        Self {
83            enabled: false,
84            ..Default::default()
85        }
86    }
87
88    /// Get the risk score for an anomaly type.
89    pub fn get_anomaly_risk(&self, anomaly_type: &AnomalyType) -> u32 {
90        self.anomaly_risk.get(anomaly_type).copied().unwrap_or(0)
91    }
92
93    /// Calculate bucket count based on retention.
94    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        // 24 hours * 60 min/hour = 1440 buckets
130        assert_eq!(config.bucket_count(), 1440);
131    }
132}