1use std::time::Duration;
6
7use ndn_transport::FaceId;
8
9use crate::sim_face::SimFace;
10
11#[derive(Clone, Debug)]
13pub struct LinkConfig {
14 pub delay: Duration,
16 pub jitter: Duration,
18 pub loss_rate: f64,
20 pub bandwidth_bps: u64,
22}
23
24impl Default for LinkConfig {
25 fn default() -> Self {
26 Self {
27 delay: Duration::ZERO,
28 jitter: Duration::ZERO,
29 loss_rate: 0.0,
30 bandwidth_bps: 0,
31 }
32 }
33}
34
35impl LinkConfig {
36 pub fn direct() -> Self {
38 Self::default()
39 }
40
41 pub fn lan() -> Self {
43 Self {
44 delay: Duration::from_millis(1),
45 jitter: Duration::from_micros(100),
46 loss_rate: 0.0,
47 bandwidth_bps: 1_000_000_000,
48 }
49 }
50
51 pub fn wifi() -> Self {
53 Self {
54 delay: Duration::from_millis(5),
55 jitter: Duration::from_millis(2),
56 loss_rate: 0.01,
57 bandwidth_bps: 54_000_000,
58 }
59 }
60
61 pub fn wan() -> Self {
63 Self {
64 delay: Duration::from_millis(50),
65 jitter: Duration::from_millis(5),
66 loss_rate: 0.001,
67 bandwidth_bps: 100_000_000,
68 }
69 }
70
71 pub fn lossy_wireless() -> Self {
73 Self {
74 delay: Duration::from_millis(10),
75 jitter: Duration::from_millis(5),
76 loss_rate: 0.05,
77 bandwidth_bps: 11_000_000,
78 }
79 }
80}
81
82pub struct SimLink;
84
85impl SimLink {
86 pub fn pair(
104 id_a: FaceId,
105 id_b: FaceId,
106 config: LinkConfig,
107 buffer: usize,
108 ) -> (SimFace, SimFace) {
109 Self::pair_asymmetric(id_a, id_b, config.clone(), config, buffer)
110 }
111
112 pub fn pair_asymmetric(
117 id_a: FaceId,
118 id_b: FaceId,
119 config_a_to_b: LinkConfig,
120 config_b_to_a: LinkConfig,
121 buffer: usize,
122 ) -> (SimFace, SimFace) {
123 let (tx_a, rx_a) = tokio::sync::mpsc::channel(buffer);
124 let (tx_b, rx_b) = tokio::sync::mpsc::channel(buffer);
125
126 let face_a = SimFace::new(id_a, tx_b, rx_a, config_a_to_b);
129 let face_b = SimFace::new(id_b, tx_a, rx_b, config_b_to_a);
130
131 (face_a, face_b)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use ndn_transport::Face;
139
140 #[tokio::test]
141 async fn direct_link_delivers_packet() {
142 let (face_a, face_b) = SimLink::pair(FaceId(1), FaceId(2), LinkConfig::direct(), 16);
143
144 let payload = bytes::Bytes::from_static(b"hello");
145 face_a.send(payload.clone()).await.unwrap();
146
147 let received = face_b.recv().await.unwrap();
148 assert_eq!(received, payload);
149 }
150
151 #[tokio::test]
152 async fn bidirectional_delivery() {
153 let (face_a, face_b) = SimLink::pair(FaceId(1), FaceId(2), LinkConfig::direct(), 16);
154
155 face_a
156 .send(bytes::Bytes::from_static(b"ping"))
157 .await
158 .unwrap();
159 face_b
160 .send(bytes::Bytes::from_static(b"pong"))
161 .await
162 .unwrap();
163
164 let at_b = face_b.recv().await.unwrap();
165 let at_a = face_a.recv().await.unwrap();
166 assert_eq!(at_b, &b"ping"[..]);
167 assert_eq!(at_a, &b"pong"[..]);
168 }
169
170 #[tokio::test]
171 async fn delayed_link() {
172 let config = LinkConfig {
173 delay: Duration::from_millis(50),
174 ..Default::default()
175 };
176 let (face_a, face_b) = SimLink::pair(FaceId(1), FaceId(2), config, 16);
177
178 let start = tokio::time::Instant::now();
179 face_a.send(bytes::Bytes::from_static(b"hi")).await.unwrap();
180 let _received = face_b.recv().await.unwrap();
181 let elapsed = start.elapsed();
182
183 assert!(
184 elapsed >= Duration::from_millis(45),
185 "expected ~50ms delay, got {elapsed:?}"
186 );
187 }
188
189 #[tokio::test]
190 async fn lossy_link_drops_some_packets() {
191 let config = LinkConfig {
192 loss_rate: 1.0, ..Default::default()
194 };
195 let (face_a, face_b) = SimLink::pair(FaceId(1), FaceId(2), config, 16);
196
197 for _ in 0..10 {
198 face_a.send(bytes::Bytes::from_static(b"x")).await.unwrap();
199 }
200
201 let result = tokio::time::timeout(Duration::from_millis(100), face_b.recv()).await;
203 assert!(result.is_err(), "expected timeout with 100% loss");
204 }
205}