Skip to main content

elara_test/
simulator.rs

1//! Network simulator for ELARA protocol testing
2
3use std::collections::HashMap;
4use std::time::Duration;
5
6use elara_core::NodeId;
7
8use crate::chaos::{ChaosConfig, ChaosNetwork};
9
10/// Simulated network link between two nodes
11pub struct NetworkLink {
12    /// Source node
13    pub from: NodeId,
14    /// Destination node
15    pub to: NodeId,
16    /// Chaos network for this link
17    pub network: ChaosNetwork,
18}
19
20/// Network simulator for multi-node testing
21pub struct NetworkSimulator {
22    /// Links between nodes (keyed by (from, to))
23    links: HashMap<(NodeId, NodeId), ChaosNetwork>,
24    /// Default chaos config for new links
25    default_config: ChaosConfig,
26    /// Current simulation time
27    current_time: Duration,
28    /// RNG seed counter
29    seed_counter: u64,
30}
31
32impl NetworkSimulator {
33    /// Create a new network simulator
34    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    /// Create with good network conditions
44    pub fn good() -> Self {
45        Self::new(ChaosConfig::good())
46    }
47
48    /// Create with poor network conditions
49    pub fn poor() -> Self {
50        Self::new(ChaosConfig::poor())
51    }
52
53    /// Create with hostile network conditions
54    pub fn hostile() -> Self {
55        Self::new(ChaosConfig::hostile())
56    }
57
58    /// Get or create a link between two nodes
59    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    /// Send a packet from one node to another
69    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    /// Advance simulation time and collect delivered packets
75    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    /// Get current simulation time
91    pub fn current_time(&self) -> Duration {
92        self.current_time
93    }
94
95    /// Set custom config for a specific link
96    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    /// Get statistics for a link
104    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    /// Get all link statistics
109    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
114/// Test scenario builder
115pub 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    /// Add nodes to the scenario
133    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    /// Set network conditions
139    pub fn with_config(mut self, config: ChaosConfig) -> Self {
140        self.config = config;
141        self
142    }
143
144    /// Set test duration
145    pub fn with_duration(mut self, duration: Duration) -> Self {
146        self.duration = duration;
147        self
148    }
149
150    /// Set tick interval
151    pub fn with_tick_interval(mut self, interval: Duration) -> Self {
152        self.tick_interval = interval;
153        self
154    }
155
156    /// Build the simulator
157    pub fn build(self) -> (NetworkSimulator, Vec<NodeId>) {
158        (NetworkSimulator::new(self.config), self.nodes)
159    }
160
161    /// Get duration
162    pub fn duration(&self) -> Duration {
163        self.duration
164    }
165
166    /// Get tick interval
167    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        // Send packets both ways
190        for i in 0..10 {
191            sim.send(node1, node2, vec![i]);
192            sim.send(node2, node1, vec![i + 100]);
193        }
194
195        // Advance time
196        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        // Most should be delivered
203        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        // Send between all pairs
217        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        // Advance and collect
226        for _ in 0..200 {
227            sim.tick(Duration::from_millis(10));
228        }
229
230        // Check stats
231        let stats = sim.all_stats();
232        assert!(!stats.is_empty());
233    }
234}