1use std::collections::HashMap;
4use std::time::Duration;
5
6use elara_core::NodeId;
7
8use crate::chaos::{ChaosConfig, ChaosNetwork};
9
10pub struct NetworkLink {
12 pub from: NodeId,
14 pub to: NodeId,
16 pub network: ChaosNetwork,
18}
19
20pub struct NetworkSimulator {
22 links: HashMap<(NodeId, NodeId), ChaosNetwork>,
24 default_config: ChaosConfig,
26 current_time: Duration,
28 seed_counter: u64,
30}
31
32impl NetworkSimulator {
33 pub fn new(default_config: ChaosConfig) -> Self {
35 NetworkSimulator {
36 links: HashMap::new(),
37 default_config,
38 current_time: Duration::ZERO,
39 seed_counter: 0,
40 }
41 }
42
43 pub fn good() -> Self {
45 Self::new(ChaosConfig::good())
46 }
47
48 pub fn poor() -> Self {
50 Self::new(ChaosConfig::poor())
51 }
52
53 pub fn hostile() -> Self {
55 Self::new(ChaosConfig::hostile())
56 }
57
58 fn get_or_create_link(&mut self, from: NodeId, to: NodeId) -> &mut ChaosNetwork {
60 let seed = self.seed_counter;
61 self.seed_counter += 1;
62
63 self.links
64 .entry((from, to))
65 .or_insert_with(|| ChaosNetwork::with_seed(self.default_config.clone(), seed))
66 }
67
68 pub fn send(&mut self, from: NodeId, to: NodeId, data: Vec<u8>) {
70 let link = self.get_or_create_link(from, to);
71 link.send(data);
72 }
73
74 pub fn tick(&mut self, dt: Duration) -> Vec<(NodeId, NodeId, Vec<u8>)> {
76 self.current_time += dt;
77
78 let mut delivered = Vec::new();
79
80 for ((from, to), link) in &mut self.links {
81 let packets = link.tick(dt);
82 for data in packets {
83 delivered.push((*from, *to, data));
84 }
85 }
86
87 delivered
88 }
89
90 pub fn current_time(&self) -> Duration {
92 self.current_time
93 }
94
95 pub fn set_link_config(&mut self, from: NodeId, to: NodeId, config: ChaosConfig) {
97 let seed = self.seed_counter;
98 self.seed_counter += 1;
99 self.links
100 .insert((from, to), ChaosNetwork::with_seed(config, seed));
101 }
102
103 pub fn link_stats(&self, from: NodeId, to: NodeId) -> Option<&crate::chaos::ChaosStats> {
105 self.links.get(&(from, to)).map(|l| l.stats())
106 }
107
108 pub fn all_stats(&self) -> Vec<((NodeId, NodeId), &crate::chaos::ChaosStats)> {
110 self.links.iter().map(|(k, v)| (*k, v.stats())).collect()
111 }
112}
113
114pub struct ScenarioBuilder {
116 nodes: Vec<NodeId>,
117 config: ChaosConfig,
118 duration: Duration,
119 tick_interval: Duration,
120}
121
122impl ScenarioBuilder {
123 pub fn new() -> Self {
124 ScenarioBuilder {
125 nodes: Vec::new(),
126 config: ChaosConfig::default(),
127 duration: Duration::from_secs(60),
128 tick_interval: Duration::from_millis(10),
129 }
130 }
131
132 pub fn with_nodes(mut self, count: usize) -> Self {
134 self.nodes = (0..count).map(|i| NodeId::new(i as u64)).collect();
135 self
136 }
137
138 pub fn with_config(mut self, config: ChaosConfig) -> Self {
140 self.config = config;
141 self
142 }
143
144 pub fn with_duration(mut self, duration: Duration) -> Self {
146 self.duration = duration;
147 self
148 }
149
150 pub fn with_tick_interval(mut self, interval: Duration) -> Self {
152 self.tick_interval = interval;
153 self
154 }
155
156 pub fn build(self) -> (NetworkSimulator, Vec<NodeId>) {
158 (NetworkSimulator::new(self.config), self.nodes)
159 }
160
161 pub fn duration(&self) -> Duration {
163 self.duration
164 }
165
166 pub fn tick_interval(&self) -> Duration {
168 self.tick_interval
169 }
170}
171
172impl Default for ScenarioBuilder {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_network_simulator_basic() {
184 let mut sim = NetworkSimulator::good();
185
186 let node1 = NodeId::new(1);
187 let node2 = NodeId::new(2);
188
189 for i in 0..10 {
191 sim.send(node1, node2, vec![i]);
192 sim.send(node2, node1, vec![i + 100]);
193 }
194
195 let mut total_delivered = 0;
197 for _ in 0..100 {
198 let delivered = sim.tick(Duration::from_millis(10));
199 total_delivered += delivered.len();
200 }
201
202 assert!(total_delivered >= 18);
204 }
205
206 #[test]
207 fn test_scenario_builder() {
208 let (mut sim, nodes) = ScenarioBuilder::new()
209 .with_nodes(5)
210 .with_config(ChaosConfig::poor())
211 .with_duration(Duration::from_secs(10))
212 .build();
213
214 assert_eq!(nodes.len(), 5);
215
216 for from in &nodes {
218 for to in &nodes {
219 if from != to {
220 sim.send(*from, *to, vec![1, 2, 3]);
221 }
222 }
223 }
224
225 for _ in 0..200 {
227 sim.tick(Duration::from_millis(10));
228 }
229
230 let stats = sim.all_stats();
232 assert!(!stats.is_empty());
233 }
234}