1use crate::latency::LatencyProfile;
7use crate::traffic_shaping::{BandwidthConfig, BurstLossConfig, TrafficShapingConfig};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct NetworkProfile {
14 pub name: String,
16 pub description: String,
18 pub latency: LatencyProfile,
20 pub traffic_shaping: TrafficShapingConfig,
22}
23
24impl NetworkProfile {
25 pub fn custom(
27 name: String,
28 description: String,
29 latency: LatencyProfile,
30 traffic_shaping: TrafficShapingConfig,
31 ) -> Self {
32 Self {
33 name,
34 description,
35 latency,
36 traffic_shaping,
37 }
38 }
39
40 pub fn apply(&self) -> (LatencyProfile, TrafficShapingConfig) {
42 (self.latency.clone(), self.traffic_shaping.clone())
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct NetworkProfileCatalog {
49 profiles: HashMap<String, NetworkProfile>,
50}
51
52impl Default for NetworkProfileCatalog {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl NetworkProfileCatalog {
59 pub fn new() -> Self {
61 let mut catalog = Self {
62 profiles: HashMap::new(),
63 };
64
65 catalog.add_builtin_profiles();
67 catalog
68 }
69
70 fn add_builtin_profiles(&mut self) {
72 self.add_profile(Self::perfect_network());
74
75 self.add_profile(Self::mobile_5g());
77 self.add_profile(Self::mobile_4g());
78 self.add_profile(Self::mobile_3g());
79 self.add_profile(Self::mobile_2g());
80 self.add_profile(Self::mobile_edge());
81
82 self.add_profile(Self::satellite_leo()); self.add_profile(Self::satellite_geo()); self.add_profile(Self::congested_network());
88 self.add_profile(Self::lossy_network());
89 self.add_profile(Self::high_latency());
90
91 self.add_profile(Self::intermittent_connection());
93 self.add_profile(Self::extremely_poor());
94 }
95
96 pub fn add_profile(&mut self, profile: NetworkProfile) {
98 self.profiles.insert(profile.name.clone(), profile);
99 }
100
101 pub fn get(&self, name: &str) -> Option<&NetworkProfile> {
103 self.profiles.get(name)
104 }
105
106 pub fn list_profiles(&self) -> Vec<String> {
108 let mut names: Vec<String> = self.profiles.keys().cloned().collect();
109 names.sort();
110 names
111 }
112
113 pub fn list_profiles_with_description(&self) -> Vec<(String, String)> {
115 let mut profiles: Vec<_> = self
116 .profiles
117 .values()
118 .map(|p| (p.name.clone(), p.description.clone()))
119 .collect();
120 profiles.sort_by(|a, b| a.0.cmp(&b.0));
121 profiles
122 }
123
124 fn perfect_network() -> NetworkProfile {
130 NetworkProfile {
131 name: "perfect".to_string(),
132 description: "Perfect network with no degradation".to_string(),
133 latency: LatencyProfile::new(0, 0),
134 traffic_shaping: TrafficShapingConfig::default(),
135 }
136 }
137
138 fn mobile_5g() -> NetworkProfile {
140 NetworkProfile {
141 name: "5g".to_string(),
142 description: "5G mobile network (10-30ms latency, ~100 Mbps)".to_string(),
143 latency: LatencyProfile::with_normal_distribution(20, 5.0)
144 .with_min_ms(10)
145 .with_max_ms(30),
146 traffic_shaping: TrafficShapingConfig {
147 bandwidth: BandwidthConfig::new(
148 100_000_000 / 8, 10_000_000, ),
151 burst_loss: BurstLossConfig {
152 enabled: false,
153 ..Default::default()
154 },
155 },
156 }
157 }
158
159 fn mobile_4g() -> NetworkProfile {
161 NetworkProfile {
162 name: "4g".to_string(),
163 description: "4G/LTE mobile network (30-60ms latency, ~20 Mbps)".to_string(),
164 latency: LatencyProfile::with_normal_distribution(45, 10.0)
165 .with_min_ms(30)
166 .with_max_ms(70),
167 traffic_shaping: TrafficShapingConfig {
168 bandwidth: BandwidthConfig::new(
169 20_000_000 / 8, 2_500_000, ),
172 burst_loss: BurstLossConfig {
173 enabled: true,
174 burst_probability: 0.05, burst_duration_ms: 2000, loss_rate_during_burst: 0.1, recovery_time_ms: 30000, ..Default::default()
179 },
180 },
181 }
182 }
183
184 fn mobile_3g() -> NetworkProfile {
186 NetworkProfile {
187 name: "3g".to_string(),
188 description: "3G mobile network (100-200ms latency, ~1 Mbps)".to_string(),
189 latency: LatencyProfile::with_normal_distribution(150, 30.0)
190 .with_min_ms(100)
191 .with_max_ms(250),
192 traffic_shaping: TrafficShapingConfig {
193 bandwidth: BandwidthConfig::new(
194 1_000_000 / 8, 125_000, ),
197 burst_loss: BurstLossConfig {
198 enabled: true,
199 burst_probability: 0.1,
200 burst_duration_ms: 3000,
201 loss_rate_during_burst: 0.15,
202 recovery_time_ms: 20000,
203 ..Default::default()
204 },
205 },
206 }
207 }
208
209 fn mobile_2g() -> NetworkProfile {
211 NetworkProfile {
212 name: "2g".to_string(),
213 description: "2G/EDGE mobile network (300-500ms latency, ~250 Kbps)".to_string(),
214 latency: LatencyProfile::with_normal_distribution(400, 80.0)
215 .with_min_ms(300)
216 .with_max_ms(600),
217 traffic_shaping: TrafficShapingConfig {
218 bandwidth: BandwidthConfig::new(
219 250_000 / 8, 31_250, ),
222 burst_loss: BurstLossConfig {
223 enabled: true,
224 burst_probability: 0.15,
225 burst_duration_ms: 5000,
226 loss_rate_during_burst: 0.2,
227 recovery_time_ms: 15000,
228 ..Default::default()
229 },
230 },
231 }
232 }
233
234 fn mobile_edge() -> NetworkProfile {
236 NetworkProfile {
237 name: "edge".to_string(),
238 description: "EDGE mobile network (500-800ms latency, ~100 Kbps)".to_string(),
239 latency: LatencyProfile::with_normal_distribution(650, 120.0)
240 .with_min_ms(500)
241 .with_max_ms(1000),
242 traffic_shaping: TrafficShapingConfig {
243 bandwidth: BandwidthConfig::new(
244 100_000 / 8, 12_500, ),
247 burst_loss: BurstLossConfig {
248 enabled: true,
249 burst_probability: 0.2,
250 burst_duration_ms: 8000,
251 loss_rate_during_burst: 0.25,
252 recovery_time_ms: 10000,
253 ..Default::default()
254 },
255 },
256 }
257 }
258
259 fn satellite_leo() -> NetworkProfile {
261 NetworkProfile {
262 name: "satellite_leo".to_string(),
263 description: "LEO satellite (20-40ms latency, ~100 Mbps, variable)".to_string(),
264 latency: LatencyProfile::with_pareto_distribution(30, 2.5)
265 .with_min_ms(20)
266 .with_max_ms(150), traffic_shaping: TrafficShapingConfig {
268 bandwidth: BandwidthConfig::new(
269 100_000_000 / 8, 10_000_000, ),
272 burst_loss: BurstLossConfig {
273 enabled: true,
274 burst_probability: 0.08,
275 burst_duration_ms: 3000,
276 loss_rate_during_burst: 0.15,
277 recovery_time_ms: 25000,
278 ..Default::default()
279 },
280 },
281 }
282 }
283
284 fn satellite_geo() -> NetworkProfile {
286 NetworkProfile {
287 name: "satellite_geo".to_string(),
288 description: "GEO satellite (550-750ms latency, ~15 Mbps)".to_string(),
289 latency: LatencyProfile::with_normal_distribution(650, 80.0)
290 .with_min_ms(550)
291 .with_max_ms(850),
292 traffic_shaping: TrafficShapingConfig {
293 bandwidth: BandwidthConfig::new(
294 15_000_000 / 8, 1_875_000, ),
297 burst_loss: BurstLossConfig {
298 enabled: true,
299 burst_probability: 0.1,
300 burst_duration_ms: 5000,
301 loss_rate_during_burst: 0.2,
302 recovery_time_ms: 20000,
303 ..Default::default()
304 },
305 },
306 }
307 }
308
309 fn congested_network() -> NetworkProfile {
311 NetworkProfile {
312 name: "congested".to_string(),
313 description: "Congested network (100-500ms latency, ~2 Mbps, high jitter)".to_string(),
314 latency: LatencyProfile::with_pareto_distribution(150, 1.8)
315 .with_min_ms(100)
316 .with_max_ms(800),
317 traffic_shaping: TrafficShapingConfig {
318 bandwidth: BandwidthConfig::new(
319 2_000_000 / 8, 250_000, ),
322 burst_loss: BurstLossConfig {
323 enabled: true,
324 burst_probability: 0.12,
325 burst_duration_ms: 4000,
326 loss_rate_during_burst: 0.2,
327 recovery_time_ms: 18000,
328 ..Default::default()
329 },
330 },
331 }
332 }
333
334 fn lossy_network() -> NetworkProfile {
336 NetworkProfile {
337 name: "lossy".to_string(),
338 description: "Lossy network (50-100ms latency, 20% packet loss)".to_string(),
339 latency: LatencyProfile::with_normal_distribution(75, 15.0)
340 .with_min_ms(50)
341 .with_max_ms(120),
342 traffic_shaping: TrafficShapingConfig {
343 bandwidth: BandwidthConfig::new(
344 10_000_000 / 8, 1_250_000, ),
347 burst_loss: BurstLossConfig {
348 enabled: true,
349 burst_probability: 0.3, burst_duration_ms: 2000,
351 loss_rate_during_burst: 0.5, recovery_time_ms: 8000,
353 ..Default::default()
354 },
355 },
356 }
357 }
358
359 fn high_latency() -> NetworkProfile {
361 NetworkProfile {
362 name: "high_latency".to_string(),
363 description: "High latency network (500-1000ms latency, normal bandwidth)".to_string(),
364 latency: LatencyProfile::with_normal_distribution(750, 150.0)
365 .with_min_ms(500)
366 .with_max_ms(1200),
367 traffic_shaping: TrafficShapingConfig {
368 bandwidth: BandwidthConfig::new(
369 10_000_000 / 8, 1_250_000, ),
372 burst_loss: BurstLossConfig {
373 enabled: false,
374 ..Default::default()
375 },
376 },
377 }
378 }
379
380 fn intermittent_connection() -> NetworkProfile {
382 NetworkProfile {
383 name: "intermittent".to_string(),
384 description: "Intermittent connection (100-300ms latency, frequent drops)".to_string(),
385 latency: LatencyProfile::with_normal_distribution(200, 50.0)
386 .with_min_ms(100)
387 .with_max_ms(400),
388 traffic_shaping: TrafficShapingConfig {
389 bandwidth: BandwidthConfig::new(
390 5_000_000 / 8, 625_000, ),
393 burst_loss: BurstLossConfig {
394 enabled: true,
395 burst_probability: 0.4, burst_duration_ms: 5000, loss_rate_during_burst: 0.8, recovery_time_ms: 10000,
399 ..Default::default()
400 },
401 },
402 }
403 }
404
405 fn extremely_poor() -> NetworkProfile {
407 NetworkProfile {
408 name: "extremely_poor".to_string(),
409 description: "Extremely poor network (1000ms+ latency, <50 Kbps, high loss)"
410 .to_string(),
411 latency: LatencyProfile::with_pareto_distribution(1000, 1.5)
412 .with_min_ms(800)
413 .with_max_ms(3000),
414 traffic_shaping: TrafficShapingConfig {
415 bandwidth: BandwidthConfig::new(
416 50_000 / 8, 6_250, ),
419 burst_loss: BurstLossConfig {
420 enabled: true,
421 burst_probability: 0.5,
422 burst_duration_ms: 10000,
423 loss_rate_during_burst: 0.7,
424 recovery_time_ms: 5000,
425 ..Default::default()
426 },
427 },
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_network_profile_creation() {
438 let profile = NetworkProfile::custom(
439 "test".to_string(),
440 "Test profile".to_string(),
441 LatencyProfile::new(100, 20),
442 TrafficShapingConfig::default(),
443 );
444
445 assert_eq!(profile.name, "test");
446 assert_eq!(profile.description, "Test profile");
447 }
448
449 #[test]
450 fn test_catalog_has_builtin_profiles() {
451 let catalog = NetworkProfileCatalog::new();
452 let profiles = catalog.list_profiles();
453
454 assert!(profiles.contains(&"perfect".to_string()));
456 assert!(profiles.contains(&"5g".to_string()));
457 assert!(profiles.contains(&"4g".to_string()));
458 assert!(profiles.contains(&"3g".to_string()));
459 assert!(profiles.contains(&"2g".to_string()));
460 assert!(profiles.contains(&"edge".to_string()));
461 assert!(profiles.contains(&"satellite_leo".to_string()));
462 assert!(profiles.contains(&"satellite_geo".to_string()));
463 assert!(profiles.contains(&"congested".to_string()));
464 assert!(profiles.contains(&"lossy".to_string()));
465 assert!(profiles.contains(&"high_latency".to_string()));
466 assert!(profiles.contains(&"intermittent".to_string()));
467 assert!(profiles.contains(&"extremely_poor".to_string()));
468
469 assert!(profiles.len() >= 13);
470 }
471
472 #[test]
473 fn test_get_profile() {
474 let catalog = NetworkProfileCatalog::new();
475
476 let profile_3g = catalog.get("3g");
477 assert!(profile_3g.is_some());
478 assert_eq!(profile_3g.unwrap().name, "3g");
479
480 let profile_nonexistent = catalog.get("nonexistent");
481 assert!(profile_nonexistent.is_none());
482 }
483
484 #[test]
485 fn test_apply_profile() {
486 let catalog = NetworkProfileCatalog::new();
487 let profile = catalog.get("4g").unwrap();
488
489 let (latency, traffic_shaping) = profile.apply();
490
491 assert!(latency.base_ms >= 30);
493 assert!(latency.base_ms <= 70);
494
495 assert!(traffic_shaping.bandwidth.enabled);
497 }
498
499 #[test]
500 fn test_list_profiles_with_description() {
501 let catalog = NetworkProfileCatalog::new();
502 let profiles = catalog.list_profiles_with_description();
503
504 assert!(!profiles.is_empty());
506
507 for (name, desc) in &profiles {
508 assert!(!name.is_empty());
509 assert!(!desc.is_empty());
510 }
511 }
512
513 #[test]
514 fn test_custom_profile_addition() {
515 let mut catalog = NetworkProfileCatalog::new();
516
517 let custom = NetworkProfile::custom(
518 "custom_test".to_string(),
519 "Custom test profile".to_string(),
520 LatencyProfile::new(50, 10),
521 TrafficShapingConfig::default(),
522 );
523
524 catalog.add_profile(custom);
525
526 let profiles = catalog.list_profiles();
527 assert!(profiles.contains(&"custom_test".to_string()));
528 }
529
530 #[test]
531 fn test_profile_characteristics() {
532 let catalog = NetworkProfileCatalog::new();
533
534 let profile_5g = catalog.get("5g").unwrap();
536 let profile_3g = catalog.get("3g").unwrap();
537 assert!(profile_5g.latency.base_ms < profile_3g.latency.base_ms);
538
539 let profile_sat = catalog.get("satellite_geo").unwrap();
541 assert!(profile_sat.latency.base_ms >= 550);
542
543 let profile_lossy = catalog.get("lossy").unwrap();
545 assert!(profile_lossy.traffic_shaping.burst_loss.enabled);
546 }
547}