1use std::collections::HashMap;
36use std::str::FromStr;
37
38use anyhow::{Result, bail};
39use ndn_engine::ForwarderEngine;
40use ndn_engine::builder::{EngineBuilder, EngineConfig};
41use ndn_engine::engine::ShutdownHandle;
42use ndn_packet::Name;
43use ndn_transport::FaceId;
44use tracing::info;
45
46use crate::sim_link::{LinkConfig, SimLink};
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub struct NodeId(pub usize);
51
52impl std::fmt::Display for NodeId {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "node#{}", self.0)
55 }
56}
57
58struct PendingLink {
60 a: NodeId,
61 b: NodeId,
62 config: LinkConfig,
63}
64
65struct PendingRoute {
67 node: NodeId,
68 prefix: Name,
69 nexthop_node: NodeId,
70}
71
72pub struct Simulation {
78 nodes: Vec<EngineConfig>,
79 links: Vec<PendingLink>,
80 routes: Vec<PendingRoute>,
81 channel_buffer: usize,
82}
83
84impl Default for Simulation {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Simulation {
91 pub fn new() -> Self {
92 Self {
93 nodes: Vec::new(),
94 links: Vec::new(),
95 routes: Vec::new(),
96 channel_buffer: 256,
97 }
98 }
99
100 pub fn channel_buffer(mut self, size: usize) -> Self {
102 self.channel_buffer = size;
103 self
104 }
105
106 pub fn add_node(&mut self, config: EngineConfig) -> NodeId {
108 let id = NodeId(self.nodes.len());
109 self.nodes.push(config);
110 id
111 }
112
113 pub fn link(&mut self, a: NodeId, b: NodeId, config: LinkConfig) {
115 self.links.push(PendingLink { a, b, config });
116 }
117
118 pub fn add_route(&mut self, node: NodeId, prefix: &str, nexthop_node: NodeId) {
121 self.routes.push(PendingRoute {
122 node,
123 prefix: Name::from_str(prefix).expect("valid NDN name"),
124 nexthop_node,
125 });
126 }
127
128 pub async fn start(self) -> Result<RunningSimulation> {
130 let n = self.nodes.len();
131 info!(nodes = n, links = self.links.len(), "Simulation: starting");
132
133 let mut engines = Vec::with_capacity(n);
135 let mut handles = Vec::with_capacity(n);
136 for config in self.nodes {
137 let (engine, handle) = EngineBuilder::new(config).build().await?;
138 engines.push(engine);
139 handles.push(handle);
140 }
141
142 let mut face_map: HashMap<(NodeId, NodeId), FaceId> = HashMap::new();
146
147 for link in &self.links {
148 let a = link.a.0;
149 let b = link.b.0;
150 if a >= n || b >= n {
151 bail!("link references non-existent node");
152 }
153
154 let id_a = engines[a].faces().alloc_id();
155 let id_b = engines[b].faces().alloc_id();
156
157 let (face_a, face_b) =
158 SimLink::pair(id_a, id_b, link.config.clone(), self.channel_buffer);
159
160 let cancel_a = handles[a].cancel_token();
161 let cancel_b = handles[b].cancel_token();
162 engines[a].add_face(face_a, cancel_a);
163 engines[b].add_face(face_b, cancel_b);
164
165 face_map.insert((link.a, link.b), id_a);
166 face_map.insert((link.b, link.a), id_b);
167
168 info!(
169 node_a = a, face_a = %id_a,
170 node_b = b, face_b = %id_b,
171 "Simulation: link created"
172 );
173 }
174
175 for route in &self.routes {
177 let face_id = face_map.get(&(route.node, route.nexthop_node));
178 if let Some(&fid) = face_id {
179 engines[route.node.0]
180 .fib()
181 .add_nexthop(&route.prefix, fid, 10);
182 info!(
183 node = route.node.0, prefix = %route.prefix, face = %fid,
184 "Simulation: route installed"
185 );
186 } else {
187 bail!(
188 "no link between {} and {} for route {}",
189 route.node,
190 route.nexthop_node,
191 route.prefix
192 );
193 }
194 }
195
196 Ok(RunningSimulation {
197 engines,
198 handles,
199 face_map,
200 })
201 }
202}
203
204pub struct RunningSimulation {
206 engines: Vec<ForwarderEngine>,
207 handles: Vec<ShutdownHandle>,
208 face_map: HashMap<(NodeId, NodeId), FaceId>,
209}
210
211impl RunningSimulation {
212 pub fn engine(&self, node: NodeId) -> &ForwarderEngine {
214 &self.engines[node.0]
215 }
216
217 pub fn engines(&self) -> &[ForwarderEngine] {
219 &self.engines
220 }
221
222 pub fn node_count(&self) -> usize {
224 self.engines.len()
225 }
226
227 pub fn add_route(&self, node: NodeId, prefix: &str, nexthop: NodeId) -> Result<()> {
229 let face_id = self
230 .face_map
231 .get(&(node, nexthop))
232 .ok_or_else(|| anyhow::anyhow!("no link between {node} and {nexthop}"))?;
233 let name = Name::from_str(prefix).expect("valid NDN name");
234 self.engines[node.0].fib().add_nexthop(&name, *face_id, 10);
235 Ok(())
236 }
237
238 pub fn face_between(&self, from: NodeId, to: NodeId) -> Option<FaceId> {
240 self.face_map.get(&(from, to)).copied()
241 }
242
243 pub async fn shutdown(self) {
245 for handle in self.handles {
246 handle.shutdown().await;
247 }
248 }
249}