1use super::*;
2
3impl TransportEngine {
4 pub fn tick(&mut self, now: f64, rng: &mut dyn Rng) -> Vec<TransportAction> {
6 let mut ctx = TickCtx {
7 now,
8 rng,
9 actions: Vec::new(),
10 };
11 self.process_tick_pending_announces(&mut ctx);
12
13 let mut queue_actions = self.announce_queues.process_queues(now, &self.interfaces);
14 ctx.actions.append(&mut queue_actions);
15
16 self.process_tick_ingress_release(&mut ctx);
17 self.cull_tick_tables(&mut ctx);
18 self.process_tick_rediscovery(&mut ctx);
19 ctx.actions
20 }
21
22 pub fn queue_path_rediscovery(
25 &mut self,
26 destination_hash: [u8; 16],
27 skip_interface: Option<InterfaceId>,
28 now: f64,
29 ) {
30 if self
31 .pending_discovery_prs
32 .iter()
33 .any(|(queued, _)| *queued == destination_hash)
34 || self.pending_discovery_prs.len() >= constants::MAX_QUEUED_DISCOVERY_PRS
35 {
36 return;
37 }
38 if self.pending_discovery_prs.is_empty() {
41 self.discovery_pr_next_tx = self
42 .discovery_pr_next_tx
43 .max(now + constants::DISCOVERY_PR_TX_THROTTLE);
44 }
45 self.pending_discovery_prs
46 .push_back((destination_hash, skip_interface));
47 }
48
49 pub fn last_path_request(&self, destination_hash: &[u8; 16]) -> Option<f64> {
52 self.path_requests.get(destination_hash).copied()
53 }
54
55 fn process_tick_rediscovery(&mut self, ctx: &mut TickCtx<'_>) {
58 if ctx.now < self.discovery_pr_next_tx {
59 return;
60 }
61 if let Some((destination_hash, skip_interface)) = self.pending_discovery_prs.pop_front() {
62 ctx.actions.push(TransportAction::RediscoverPath {
63 destination_hash,
64 skip_interface,
65 });
66 self.discovery_pr_next_tx = ctx.now + constants::DISCOVERY_PR_TX_THROTTLE;
67 }
68 }
69
70 fn rediscover_unproven_link_paths(&mut self, now: f64) {
74 let mut requests: Vec<([u8; 16], Option<InterfaceId>)> = Vec::new();
75 let unproven: Vec<LinkEntry> = self
76 .link_table
77 .values()
78 .filter(|entry| !entry.validated && now > entry.proof_timeout)
79 .cloned()
80 .collect();
81 for entry in unproven {
82 let destination_hash = entry.destination_hash;
83 let throttled = self
84 .last_path_request(&destination_hash)
85 .is_some_and(|requested_at| now - requested_at < constants::PATH_REQUEST_MI);
86 let mut skip_interface = None;
87 let mut mark_unresponsive = false;
88 let rediscover = if !self.has_path(&destination_hash) {
89 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
91 "Trying to rediscover path for {:02x?} since an attempted link was never established, and path is now missing",
92 &destination_hash[..4],
93 );
94 true
95 } else if throttled {
96 false
97 } else if entry.taken_hops == 0 {
98 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
99 "Trying to rediscover path for {:02x?} since an attempted local client link was never established",
100 &destination_hash[..4],
101 );
102 true
103 } else if self.hops_to(&destination_hash) == Some(1) {
104 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
107 "Trying to rediscover path for {:02x?} since an attempted link was never established, and destination was previously local to an interface on this instance",
108 &destination_hash[..4],
109 );
110 skip_interface = Some(entry.received_interface);
111 mark_unresponsive = true;
112 true
113 } else if entry.taken_hops == 1 {
114 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
116 "Trying to rediscover path for {:02x?} since an attempted link was never established, and link initiator is local to an interface on this instance",
117 &destination_hash[..4],
118 );
119 skip_interface = Some(entry.received_interface);
120 mark_unresponsive = true;
121 true
122 } else {
123 false
124 };
125 if !rediscover {
126 continue;
127 }
128 if mark_unresponsive && self.config.transport_enabled {
129 self.mark_path_unresponsive(&destination_hash, Some(entry.received_interface));
131 }
132 if !requests
133 .iter()
134 .any(|(queued, _)| *queued == destination_hash)
135 {
136 requests.push((destination_hash, skip_interface));
137 }
138 if !self.config.transport_enabled {
139 self.expire_path(&destination_hash);
142 }
143 }
144 for (destination_hash, skip_interface) in requests {
145 self.queue_path_rediscovery(destination_hash, skip_interface, now);
146 }
147 }
148
149 fn process_tick_pending_announces(&mut self, ctx: &mut TickCtx<'_>) {
150 if ctx.now <= self.announces_last_checked + constants::ANNOUNCES_CHECK_INTERVAL {
151 return;
152 }
153
154 self.cull_expired_announce_entries(ctx.now);
155 self.enforce_announce_retention_cap(ctx.now);
156 if let Some(identity_hash) = self.config.identity_hash {
157 let announce_actions = jobs::process_pending_announces(
158 &mut self.announce_table,
159 &mut self.held_announces,
160 &identity_hash,
161 ctx.now,
162 );
163 let gated = self.gate_retransmit_actions(announce_actions, ctx.now);
164 ctx.actions.extend(gated);
165 }
166 self.cull_expired_announce_entries(ctx.now);
167 self.enforce_announce_retention_cap(ctx.now);
168 self.announces_last_checked = ctx.now;
169 }
170
171 fn process_tick_ingress_release(&mut self, ctx: &mut TickCtx<'_>) {
172 let ic_interfaces = self.ingress_control.interfaces_with_held();
173 for iface_id in ic_interfaces {
174 let (ia_freq, started, ingress_config) = match self.interfaces.get(&iface_id) {
175 Some(info) => (info.ia_freq, info.started, info.ingress_control),
176 None => continue,
177 };
178 if !ingress_config.enabled {
179 continue;
180 }
181 if let Some(held) = self.ingress_control.process_held_announces(
182 iface_id,
183 &ingress_config,
184 ia_freq,
185 started,
186 ctx.now,
187 ) {
188 let released_actions = self.handle_inbound(
189 InboundFrame {
190 raw: &held.raw,
191 iface: held.receiving_interface,
192 now: ctx.now,
193 rx: held.rx,
194 },
195 ctx.rng,
196 );
197 ctx.actions.extend(released_actions);
198 }
199 }
200 }
201
202 fn cull_tick_tables(&mut self, ctx: &mut TickCtx<'_>) {
203 if ctx.now <= self.tables_last_culled + constants::TABLES_CULL_INTERVAL {
204 return;
205 }
206
207 let unused_path_linger = self.unused_path_linger();
208 jobs::cull_path_table(
209 &mut self.path_table,
210 &self.interfaces,
211 ctx.now,
212 unused_path_linger,
213 constants::PATH_POOL_UNUSED_TARGET,
214 );
215 jobs::cull_reverse_table(&mut self.reverse_table, &self.interfaces, ctx.now);
216 self.rediscover_unproven_link_paths(ctx.now);
217 let (_culled, link_closed_actions) =
218 jobs::cull_link_table(&mut self.link_table, &self.interfaces, ctx.now);
219 ctx.actions.extend(link_closed_actions);
220 jobs::cull_path_states(&mut self.path_states, &self.path_table);
221 self.cull_blackholed(ctx.now);
222 self.path_requests.retain(|_, requested_at| {
223 ctx.now < *requested_at + constants::PATH_REQUEST_GATE_TIMEOUT
224 });
225 self.discovery_path_requests.retain(|destination, req| {
226 let deadline = self
227 .discovery_path_request_deadlines
228 .get(destination)
229 .copied()
230 .unwrap_or(req.timestamp + constants::DISCOVERY_PATH_REQUEST_TIMEOUT);
231 ctx.now < deadline
232 });
233 self.discovery_path_request_deadlines
234 .retain(|destination, _| self.discovery_path_requests.contains_key(destination));
235 self.pending_local_path_requests
238 .retain(|_, (interface, requested_at)| {
239 self.interfaces.contains_key(interface)
240 && ctx.now < *requested_at + constants::PATH_REQUEST_TIMEOUT
241 });
242 self.tunnel_table
243 .void_missing_interfaces(|id| self.interfaces.contains_key(id));
244 self.tunnel_table.cull(ctx.now);
245 self.announce_sig_cache.cull(ctx.now);
246 self.tables_last_culled = ctx.now;
247 }
248
249 pub(super) fn gate_retransmit_actions(
254 &mut self,
255 actions: Vec<TransportAction>,
256 now: f64,
257 ) -> Vec<TransportAction> {
258 let mut result = Vec::new();
259 for action in actions {
260 match action {
261 TransportAction::SendOnInterface { interface, raw } => {
262 let (dest_hash, hops) = Self::extract_announce_info(&raw);
264 let (bitrate, airtime_profile, announce_cap) =
265 if let Some(info) = self.interfaces.get(&interface) {
266 (info.bitrate, info.airtime_profile, info.announce_cap)
267 } else {
268 (None, None, constants::ANNOUNCE_CAP)
269 };
270 if let Some(send_action) = self.announce_queues.gate_announce(
271 interface,
272 raw,
273 dest_hash,
274 hops,
275 now,
276 now,
277 bitrate,
278 airtime_profile,
279 announce_cap,
280 ) {
281 result.push(send_action);
282 }
283 }
284 TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
285 let (dest_hash, hops) = Self::extract_announce_info(&raw);
286 let iface_ids: Vec<(
289 InterfaceId,
290 Option<u64>,
291 Option<types::AirtimeProfile>,
292 f64,
293 )> = self
294 .interfaces
295 .iter()
296 .filter(|(_, info)| info.out_capable)
297 .filter(|(_, info)| self.config.transport_enabled || !info.is_local_client)
301 .filter(|(id, _)| {
302 if let Some(ref ex) = exclude {
303 **id != *ex
304 } else {
305 true
306 }
307 })
308 .filter(|(_, info)| {
309 should_transmit_announce(
310 info,
311 &dest_hash,
312 hops,
313 &self.local_destinations,
314 &self.path_table,
315 &self.interfaces,
316 )
317 })
318 .map(|(id, info)| {
319 (*id, info.bitrate, info.airtime_profile, info.announce_cap)
320 })
321 .collect();
322
323 for (iface_id, bitrate, airtime_profile, announce_cap) in iface_ids {
324 if let Some(send_action) = self.announce_queues.gate_announce(
325 iface_id,
326 raw.clone(),
327 dest_hash,
328 hops,
329 now,
330 now,
331 bitrate,
332 airtime_profile,
333 announce_cap,
334 ) {
335 result.push(send_action);
336 }
337 }
338 }
339 other => result.push(other),
340 }
341 }
342 result
343 }
344
345 fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
347 if raw.len() < 18 {
348 return ([0; 16], 0);
349 }
350 let header_type = (raw[0] >> 6) & 0x03;
351 let hops = raw[1];
352 if header_type == constants::HEADER_2 && raw.len() >= 34 {
353 let mut dest = [0u8; 16];
355 dest.copy_from_slice(&raw[18..34]);
356 (dest, hops)
357 } else {
358 let mut dest = [0u8; 16];
360 dest.copy_from_slice(&raw[2..18]);
361 (dest, hops)
362 }
363 }
364
365 #[cfg(test)]
366 #[allow(dead_code)]
367 pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
368 &self.link_table
369 }
370}