1use super::*;
2
3pub fn lowest_interface_bitrate(interfaces: &BTreeMap<InterfaceId, InterfaceInfo>) -> Option<u64> {
4 interfaces
7 .values()
8 .filter_map(|interface| interface.bitrate)
9 .filter(|bitrate| *bitrate > 0)
10 .min()
11}
12
13pub fn medium_path_timeout(interfaces: &BTreeMap<InterfaceId, InterfaceInfo>) -> f64 {
14 let Some(lowest_bitrate) = lowest_interface_bitrate(interfaces) else {
15 return 0.0;
16 };
17 let effective_bitrate = lowest_bitrate.max(constants::MINIMUM_BITRATE);
18 2.0 * (constants::MTU as f64 * 8.0 / effective_bitrate as f64)
19 + constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP
20}
21
22pub(super) fn discovery_path_request_timeout(
23 interfaces: &BTreeMap<InterfaceId, InterfaceInfo>,
24) -> f64 {
25 constants::PATH_REQUEST_TIMEOUT.max(medium_path_timeout(interfaces))
26}
27
28impl TransportEngine {
29 #[doc(hidden)]
31 pub fn should_egress_limit_path_request(
32 &mut self,
33 interface_id: InterfaceId,
34 pr_freq: f64,
35 sample_count: usize,
36 ) -> bool {
37 let Some(config) = self
38 .interfaces
39 .get(&interface_id)
40 .map(|info| info.ingress_control)
41 else {
42 return false;
43 };
44 self.ingress_control
45 .should_egress_limit_pr(interface_id, &config, pr_freq, sample_count)
46 }
47
48 pub fn handle_path_request(
49 &mut self,
50 data: &[u8],
51 interface_id: InterfaceId,
52 now: f64,
53 ) -> Vec<TransportAction> {
54 self.handle_path_request_with_ingress_limit(data, interface_id, now, false)
55 }
56
57 #[doc(hidden)]
60 pub fn handle_path_request_with_ingress_limit(
61 &mut self,
62 data: &[u8],
63 interface_id: InterfaceId,
64 now: f64,
65 ingress_limited: bool,
66 ) -> Vec<TransportAction> {
67 let Some(request) = self.accept_path_request(data, interface_id, now) else {
68 return Vec::new();
69 };
70 self.handle_accepted_path_request_with_ingress_limit(request, ingress_limited)
71 }
72
73 #[doc(hidden)]
75 pub fn accept_path_request(
76 &mut self,
77 data: &[u8],
78 interface_id: InterfaceId,
79 now: f64,
80 ) -> Option<AcceptedPathRequest> {
81 self.parse_path_request(data, interface_id, now)
82 }
83
84 #[doc(hidden)]
86 pub fn handle_accepted_path_request_with_ingress_limit(
87 &mut self,
88 ctx: AcceptedPathRequest,
89 ingress_limited: bool,
90 ) -> Vec<TransportAction> {
91 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
92 "Path request for {:02x?} on interface {}",
93 &ctx.destination_hash[..4],
94 ctx.interface_id.0,
95 );
96 if ctx.already_in_flight {
97 self.batch_inflight_path_request(&ctx, ingress_limited);
98 return Vec::new();
99 }
100 let from_local_client = self.serves_local_client(ctx.interface_id);
101 if self
104 .next_hop_interface(&ctx.destination_hash)
105 .is_some_and(|iface| self.serves_local_client(iface))
106 {
107 self.pending_local_path_requests
108 .insert(ctx.destination_hash, (ctx.interface_id, ctx.now));
109 }
110 if self.local_destinations.contains_key(&ctx.destination_hash) {
111 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
112 "Ignoring path request for {:02x?}: destination is local",
113 &ctx.destination_hash[..4],
114 );
115 return Vec::new();
116 }
117 if (self.config.transport_enabled || from_local_client)
120 && self.handle_known_path_request(&ctx, from_local_client)
121 {
122 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
123 "Answering path request for {:02x?}: path is known",
124 &ctx.destination_hash[..4],
125 );
126 return Vec::new();
127 }
128 if from_local_client {
129 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
131 "Forwarding path request from local client for {:02x?} to all other interfaces",
132 &ctx.destination_hash[..4],
133 );
134 return self.forward_path_request(&ctx, |info| info.id != ctx.interface_id);
135 }
136 if self.config.transport_enabled && self.discovery_search_mode(ctx.interface_id).is_some() {
137 return self.handle_discovery_path_request(&ctx, ingress_limited);
138 }
139 if self.serves_local_clients() {
140 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
143 "Forwarding path request for {:02x?} to local clients",
144 &ctx.destination_hash[..4],
145 );
146 return self.forward_path_request(&ctx, |info| info.is_local_client);
147 }
148 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
149 "Ignoring path request for {:02x?}: no path known",
150 &ctx.destination_hash[..4],
151 );
152 Vec::new()
153 }
154
155 fn forward_path_request(
158 &mut self,
159 ctx: &AcceptedPathRequest,
160 select: impl Fn(&InterfaceInfo) -> bool,
161 ) -> Vec<TransportAction> {
162 let mut seed = Vec::with_capacity(16 + 16 + 8 + 8);
166 seed.extend_from_slice(&ctx.destination_hash);
167 seed.extend_from_slice(&ctx.tag[..ctx.tag_len]);
168 seed.extend_from_slice(&ctx.interface_id.0.to_be_bytes());
169 seed.extend_from_slice(&ctx.now.to_bits().to_be_bytes());
170 let tag = hash::truncated_hash(&seed);
171 let mut unique_tag = [0u8; 32];
172 unique_tag[..16].copy_from_slice(&ctx.destination_hash);
173 unique_tag[16..].copy_from_slice(&tag);
174 self.insert_discovery_pr_tag(unique_tag);
175
176 let transport_identity_hash = if self.config.transport_enabled {
177 self.config.identity_hash.as_ref()
178 } else {
179 None
180 };
181 let Some((raw, _)) =
182 build_path_request_packet(&ctx.destination_hash, transport_identity_hash, &tag)
183 else {
184 return Vec::new();
185 };
186 self.interfaces
187 .values()
188 .filter(|info| info.out_capable && select(info))
189 .map(|info| TransportAction::SendOnInterface {
190 interface: info.id,
191 raw: raw.clone().into(),
192 })
193 .collect()
194 }
195
196 fn parse_path_request(
197 &mut self,
198 data: &[u8],
199 interface_id: InterfaceId,
200 now: f64,
201 ) -> Option<AcceptedPathRequest> {
202 if data.len() < 16 {
203 return None;
204 }
205
206 let mut destination_hash = [0u8; 16];
207 destination_hash.copy_from_slice(&data[..16]);
208
209 let tag_bytes = if data.len() > 32 {
210 Some(&data[32..])
211 } else if data.len() > 16 {
212 Some(&data[16..])
213 } else {
214 None
215 };
216 let Some(tag_bytes) = tag_bytes else {
217 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
218 "Ignoring tagless path request for {:02x?}",
219 &destination_hash[..4],
220 );
221 return None;
222 };
223
224 let tag_len = tag_bytes.len().min(16);
225 let mut unique_tag = [0u8; 32];
226 unique_tag[..16].copy_from_slice(&destination_hash);
227 unique_tag[16..16 + tag_len].copy_from_slice(&tag_bytes[..tag_len]);
228 if !self.insert_discovery_pr_tag(unique_tag) {
229 return None;
230 }
231
232 let already_in_flight = self.path_requests.contains_key(&destination_hash)
233 && !self.path_table.contains_key(&destination_hash)
234 && !self.local_destinations.contains_key(&destination_hash);
235 self.path_requests.entry(destination_hash).or_insert(now);
236
237 let mut tag = [0u8; 16];
238 tag[..tag_len].copy_from_slice(&tag_bytes[..tag_len]);
239 Some(AcceptedPathRequest {
240 tag,
241 tag_len,
242 interface_id,
243 now,
244 destination_hash,
245 already_in_flight,
246 })
247 }
248
249 fn batch_inflight_path_request(&mut self, ctx: &AcceptedPathRequest, ingress_limited: bool) {
250 let Some((ingress_control, ip_freq, started)) = self
251 .interfaces
252 .get(&ctx.interface_id)
253 .map(|info| (info.ingress_control, info.ip_freq, info.started))
254 else {
255 return;
256 };
257 if ingress_limited
258 || self.ingress_control.should_ingress_limit_pr(
259 ctx.interface_id,
260 &ingress_control,
261 ip_freq,
262 started,
263 ctx.now,
264 )
265 {
266 return;
267 }
268
269 let timeout = discovery_path_request_timeout(&self.interfaces);
270 let request = self
271 .discovery_path_requests
272 .entry(ctx.destination_hash)
273 .or_insert_with(|| DiscoveryPathRequest {
274 timestamp: ctx.now,
275 requesting_interfaces: Vec::new(),
276 engaged: false,
277 });
278 if !request.requesting_interfaces.contains(&ctx.interface_id) {
279 request.requesting_interfaces.push(ctx.interface_id);
280 }
281 self.discovery_path_request_deadlines
282 .entry(ctx.destination_hash)
283 .or_insert(ctx.now + timeout);
284 }
285
286 #[doc(hidden)]
288 pub fn record_outbound_path_request(&mut self, destination_hash: [u8; 16], now: f64) {
289 self.path_requests.insert(destination_hash, now);
290 }
291
292 fn handle_known_path_request(
293 &mut self,
294 ctx: &AcceptedPathRequest,
295 from_local_client: bool,
296 ) -> bool {
297 let Some(path) = self
298 .path_table
299 .get(&ctx.destination_hash)
300 .and_then(|ps| ps.primary())
301 .cloned()
302 else {
303 return false;
304 };
305
306 if let Some(recv_info) = self.interfaces.get(&ctx.interface_id) {
307 if recv_info.mode == constants::MODE_ROAMING
308 && path.receiving_interface == ctx.interface_id
309 {
310 return true;
311 }
312 }
313
314 let Some(raw) = path.announce_raw.as_ref() else {
315 return false;
316 };
317 if let Some(existing) = self.announce_table.get(&ctx.destination_hash).cloned() {
320 self.insert_held_announce(ctx.destination_hash, existing, ctx.now);
321 }
322 let retransmit_timeout =
325 if from_local_client || self.interface_is_local_client(path.receiving_interface) {
326 ctx.now
327 } else if let Some(iface_info) = self.interfaces.get(&ctx.interface_id) {
328 let base = ctx.now + constants::PATH_REQUEST_GRACE;
329 if iface_info.mode == constants::MODE_ROAMING {
330 base + constants::PATH_REQUEST_RG
331 } else {
332 base
333 }
334 } else {
335 ctx.now + constants::PATH_REQUEST_GRACE
336 };
337
338 let Ok(parsed) = RawPacket::unpack(raw) else {
339 return false;
340 };
341
342 let entry = AnnounceEntry {
343 timestamp: ctx.now,
344 retransmit_timeout,
345 retries: constants::PATHFINDER_R,
346 received_from: path.next_hop,
347 hops: path.hops,
348 packet_raw: raw.clone(),
349 packet_data: parsed.data().to_vec(),
350 destination_hash: ctx.destination_hash,
351 context_flag: parsed.flags.context_flag,
352 local_rebroadcasts: 0,
353 block_rebroadcasts: true,
354 attached_interface: Some(ctx.interface_id),
355 };
356
357 self.insert_announce_entry(ctx.destination_hash, entry, ctx.now);
358 true
359 }
360
361 fn discovery_search_mode(&self, interface_id: InterfaceId) -> Option<Option<&'static [u8]>> {
364 self.interfaces
365 .get(&interface_id)
366 .and_then(|info| search_mode_filter(info.mode, info.recursive_prs))
367 }
368
369 fn handle_discovery_path_request(
370 &mut self,
371 ctx: &AcceptedPathRequest,
372 ingress_limited: bool,
373 ) -> Vec<TransportAction> {
374 let Some((mode, recursive_prs, ingress_control, ip_freq, started)) =
375 self.interfaces.get(&ctx.interface_id).map(|info| {
376 (
377 info.mode,
378 info.recursive_prs,
379 info.ingress_control,
380 info.ip_freq,
381 info.started,
382 )
383 })
384 else {
385 return Vec::new();
386 };
387
388 let Some(search_mode_filter) = search_mode_filter(mode, recursive_prs) else {
389 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
390 "Not discovering path to {:02x?}: recursive path discovery is disabled on interface {}",
391 &ctx.destination_hash[..4],
392 ctx.interface_id.0,
393 );
394 return Vec::new();
395 };
396
397 if ingress_limited
398 || self.ingress_control.should_ingress_limit_pr(
399 ctx.interface_id,
400 &ingress_control,
401 ip_freq,
402 started,
403 ctx.now,
404 )
405 {
406 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
407 "Not discovering path to {:02x?}: ingress path-request limiting is active on interface {}",
408 &ctx.destination_hash[..4],
409 ctx.interface_id.0,
410 );
411 return Vec::new();
412 }
413
414 let egress_candidates: Vec<_> = self
415 .interfaces
416 .values()
417 .filter(|info| info.id != ctx.interface_id && info.out_capable)
418 .filter(|info| search_mode_filter.is_none_or(|modes| modes.contains(&info.mode)))
419 .map(|info| {
420 (
421 info.id,
422 info.ingress_control,
423 info.op_freq,
424 info.op_samples,
425 info.bitrate,
426 info.airtime_profile,
427 info.announce_cap,
428 )
429 })
430 .collect();
431
432 let Some((path_request_raw, path_request_len)) = build_path_request_packet(
433 &ctx.destination_hash,
434 self.config.identity_hash.as_ref(),
435 &ctx.tag[..ctx.tag_len],
436 ) else {
437 return Vec::new();
438 };
439
440 let mut actions = Vec::new();
441 for (id, ingress_control, op_freq, op_samples, bitrate, airtime_profile, announce_cap) in
442 egress_candidates
443 {
444 if self.ingress_control.should_egress_limit_pr(
445 id,
446 &ingress_control,
447 op_freq,
448 op_samples,
449 ) || self
450 .announce_queues
451 .blocks_recursive_path_request(id, ctx.now)
452 {
453 continue;
454 }
455
456 self.announce_queues.reserve_recursive_path_request(
457 id,
458 path_request_len + constants::HEADER_MINSIZE,
459 ctx.now,
460 bitrate,
461 airtime_profile,
462 announce_cap,
463 );
464 actions.push(TransportAction::SendOnInterface {
465 interface: id,
466 raw: path_request_raw.clone().into(),
467 });
468 }
469
470 if !actions.is_empty() {
471 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
472 "Discovering unknown path to {:02x?} on behalf of interface {} via {} interfaces",
473 &ctx.destination_hash[..4],
474 ctx.interface_id.0,
475 actions.len(),
476 );
477 let request = self
478 .discovery_path_requests
479 .entry(ctx.destination_hash)
480 .or_insert_with(|| DiscoveryPathRequest {
481 timestamp: ctx.now,
482 requesting_interfaces: Vec::new(),
483 engaged: false,
484 });
485 if !request.requesting_interfaces.contains(&ctx.interface_id) {
486 request.requesting_interfaces.push(ctx.interface_id);
487 }
488 request.engaged = true;
489 let timeout = discovery_path_request_timeout(&self.interfaces);
490 self.discovery_path_request_deadlines
491 .insert(ctx.destination_hash, ctx.now + timeout);
492 } else {
493 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
494 "Not discovering path to {:02x?}: no eligible egress interface",
495 &ctx.destination_hash[..4],
496 );
497 }
498
499 actions
500 }
501}
502
503fn search_mode_filter(mode: u8, recursive_prs: bool) -> Option<Option<&'static [u8]>> {
506 if recursive_prs || constants::DISCOVER_PATHS_FOR.contains(&mode) {
507 Some(None)
508 } else if mode == constants::MODE_BOUNDARY {
509 Some(Some(&constants::BOUNDARY_SEARCH_MODES))
510 } else {
511 None
512 }
513}
514
515fn build_path_request_packet(
516 destination_hash: &[u8; 16],
517 transport_identity_hash: Option<&[u8; 16]>,
518 tag: &[u8],
519) -> Option<(Vec<u8>, usize)> {
520 let mut data = Vec::with_capacity(16 + transport_identity_hash.map_or(0, |_| 16) + tag.len());
521 data.extend_from_slice(destination_hash);
522 if let Some(identity_hash) = transport_identity_hash {
523 data.extend_from_slice(identity_hash);
524 }
525 data.extend_from_slice(tag);
526
527 let flags = crate::packet::PacketFlags {
528 header_type: constants::HEADER_1,
529 context_flag: constants::FLAG_UNSET,
530 transport_type: constants::TRANSPORT_BROADCAST,
531 destination_type: constants::DESTINATION_PLAIN,
532 packet_type: constants::PACKET_TYPE_DATA,
533 };
534 let path_request_dest =
535 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
536
537 let data_len = data.len();
538 RawPacket::pack(
539 flags,
540 0,
541 &path_request_dest,
542 None,
543 constants::CONTEXT_NONE,
544 &data,
545 )
546 .ok()
547 .map(|packet| (packet.raw, data_len))
548}