1use serde::{Serialize, Deserialize};
4use std::collections::HashSet;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum AdversaryType {
9 PassiveObserver,
11 ActiveAttacker,
13 GlobalObserver,
15 CompromisedRelays,
17 AdvancedPersistent,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct AdversaryCapabilities {
24 pub can_observe_metadata: bool,
26 pub can_read_content: bool,
28 pub can_inject: bool,
30 pub can_drop: bool,
32 pub can_delay: bool,
34 pub can_correlate: bool,
36 pub network_fraction: f64,
38 pub vantage_points: usize,
40}
41
42impl AdversaryCapabilities {
43 pub fn for_type(adversary_type: AdversaryType) -> Self {
45 match adversary_type {
46 AdversaryType::PassiveObserver => Self {
47 can_observe_metadata: true,
48 can_read_content: false,
49 can_inject: false,
50 can_drop: false,
51 can_delay: false,
52 can_correlate: true,
53 network_fraction: 0.0,
54 vantage_points: 1,
55 },
56 AdversaryType::ActiveAttacker => Self {
57 can_observe_metadata: true,
58 can_read_content: false,
59 can_inject: true,
60 can_drop: true,
61 can_delay: true,
62 can_correlate: true,
63 network_fraction: 0.0,
64 vantage_points: 1,
65 },
66 AdversaryType::GlobalObserver => Self {
67 can_observe_metadata: true,
68 can_read_content: false,
69 can_inject: false,
70 can_drop: false,
71 can_delay: false,
72 can_correlate: true,
73 network_fraction: 0.0,
74 vantage_points: 100,
75 },
76 AdversaryType::CompromisedRelays => Self {
77 can_observe_metadata: true,
78 can_read_content: true,
79 can_inject: true,
80 can_drop: true,
81 can_delay: true,
82 can_correlate: true,
83 network_fraction: 0.1,
84 vantage_points: 5,
85 },
86 AdversaryType::AdvancedPersistent => Self {
87 can_observe_metadata: true,
88 can_read_content: false,
89 can_inject: true,
90 can_drop: true,
91 can_delay: true,
92 can_correlate: true,
93 network_fraction: 0.2,
94 vantage_points: 20,
95 },
96 }
97 }
98
99 pub fn threat_level(&self) -> f64 {
101 let mut level = 0.0;
102 if self.can_observe_metadata { level += 0.1; }
103 if self.can_read_content { level += 0.2; }
104 if self.can_inject { level += 0.15; }
105 if self.can_drop { level += 0.1; }
106 if self.can_delay { level += 0.05; }
107 if self.can_correlate { level += 0.15; }
108 level += self.network_fraction * 0.25;
109 level.min(1.0)
110 }
111}
112
113pub struct AdversaryModel {
115 pub adversary_type: AdversaryType,
116 pub capabilities: AdversaryCapabilities,
117 pub compromised_nodes: HashSet<[u8; 32]>,
119 pub observed_flows: Vec<ObservedFlow>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ObservedFlow {
126 pub source_id: [u8; 32],
127 pub dest_id: [u8; 32],
128 pub packet_sizes: Vec<usize>,
129 pub timestamps_us: Vec<u64>,
130 pub encrypted: bool,
131}
132
133impl AdversaryModel {
134 pub fn new(adversary_type: AdversaryType) -> Self {
135 Self {
136 capabilities: AdversaryCapabilities::for_type(adversary_type),
137 adversary_type,
138 compromised_nodes: HashSet::new(),
139 observed_flows: Vec::new(),
140 }
141 }
142
143 pub fn compromise_node(&mut self, node_id: [u8; 32]) {
145 self.compromised_nodes.insert(node_id);
146 }
147
148 pub fn is_compromised(&self, node_id: &[u8; 32]) -> bool {
150 self.compromised_nodes.contains(node_id)
151 }
152
153 pub fn observe_flow(&mut self, flow: ObservedFlow) {
155 self.observed_flows.push(flow);
156 }
157
158 pub fn correlate_flows(&self, a: &ObservedFlow, b: &ObservedFlow) -> f64 {
160 if !self.capabilities.can_correlate {
161 return 0.0;
162 }
163
164 let delays_a = inter_packet_delays(&a.timestamps_us);
166 let delays_b = inter_packet_delays(&b.timestamps_us);
167
168 if delays_a.is_empty() || delays_b.is_empty() {
169 return 0.0;
170 }
171
172 pearson_correlation(&delays_a, &delays_b).abs()
174 }
175
176 pub fn circuit_compromise_probability(&self, path_length: usize) -> f64 {
178 let f = self.capabilities.network_fraction;
180 f * f
184 }
185
186 pub fn observed_count(&self) -> usize {
188 self.observed_flows.len()
189 }
190}
191
192fn inter_packet_delays(timestamps: &[u64]) -> Vec<f64> {
194 timestamps
195 .windows(2)
196 .map(|w| (w[1] as f64) - (w[0] as f64))
197 .collect()
198}
199
200fn pearson_correlation(a: &[f64], b: &[f64]) -> f64 {
202 let n = a.len().min(b.len());
203 if n < 2 {
204 return 0.0;
205 }
206
207 let mean_a = a[..n].iter().sum::<f64>() / n as f64;
208 let mean_b = b[..n].iter().sum::<f64>() / n as f64;
209
210 let mut cov = 0.0;
211 let mut var_a = 0.0;
212 let mut var_b = 0.0;
213
214 for i in 0..n {
215 let da = a[i] - mean_a;
216 let db = b[i] - mean_b;
217 cov += da * db;
218 var_a += da * da;
219 var_b += db * db;
220 }
221
222 let denom = (var_a * var_b).sqrt();
223 if denom < 1e-12 {
224 0.0
225 } else {
226 cov / denom
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_adversary_capabilities() {
236 let caps = AdversaryCapabilities::for_type(AdversaryType::PassiveObserver);
237 assert!(caps.can_observe_metadata);
238 assert!(!caps.can_inject);
239 assert!(!caps.can_read_content);
240 }
241
242 #[test]
243 fn test_threat_levels() {
244 let passive = AdversaryCapabilities::for_type(AdversaryType::PassiveObserver);
245 let active = AdversaryCapabilities::for_type(AdversaryType::ActiveAttacker);
246 let apt = AdversaryCapabilities::for_type(AdversaryType::AdvancedPersistent);
247
248 assert!(passive.threat_level() < active.threat_level());
249 assert!(active.threat_level() < apt.threat_level());
250 }
251
252 #[test]
253 fn test_flow_correlation() {
254 let model = AdversaryModel::new(AdversaryType::GlobalObserver);
255 let flow_a = ObservedFlow {
257 source_id: [1; 32],
258 dest_id: [2; 32],
259 packet_sizes: vec![100, 200, 150],
260 timestamps_us: vec![0, 1000, 2500],
261 encrypted: true,
262 };
263 let flow_b = ObservedFlow {
264 source_id: [3; 32],
265 dest_id: [4; 32],
266 packet_sizes: vec![100, 200, 150],
267 timestamps_us: vec![50, 1050, 2550], encrypted: true,
269 };
270
271 let corr = model.correlate_flows(&flow_a, &flow_b);
272 assert!(corr > 0.99, "Same-pattern flows should be highly correlated: {}", corr);
273 }
274
275 #[test]
276 fn test_circuit_compromise_probability() {
277 let mut model = AdversaryModel::new(AdversaryType::CompromisedRelays);
278 model.capabilities.network_fraction = 0.1;
279 let prob = model.circuit_compromise_probability(3);
280 assert!((prob - 0.01).abs() < 0.001); }
282}