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) {
114 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
115 "Answering path request for {:02x?}: destination is local",
116 &ctx.destination_hash[..4],
117 );
118 return alloc::vec![TransportAction::AnnounceLocalPathResponse {
119 destination_hash: ctx.destination_hash,
120 tag: ctx.tag[..ctx.tag_len].to_vec(),
121 interface: ctx.interface_id,
122 }];
123 }
124 if (self.config.transport_enabled || from_local_client)
127 && self.handle_known_path_request(&ctx, from_local_client)
128 {
129 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
130 "Answering path request for {:02x?}: path is known",
131 &ctx.destination_hash[..4],
132 );
133 return Vec::new();
134 }
135 if from_local_client {
136 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
138 "Forwarding path request from local client for {:02x?} to all other interfaces",
139 &ctx.destination_hash[..4],
140 );
141 return self.forward_path_request(&ctx, |info| info.id != ctx.interface_id);
142 }
143 if self.config.transport_enabled && self.discovery_search_mode(ctx.interface_id).is_some() {
144 return self.handle_discovery_path_request(&ctx, ingress_limited);
145 }
146 if self.serves_local_clients() {
147 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
150 "Forwarding path request for {:02x?} to local clients",
151 &ctx.destination_hash[..4],
152 );
153 return self.forward_path_request(&ctx, |info| info.is_local_client);
154 }
155 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
156 "Ignoring path request for {:02x?}: no path known",
157 &ctx.destination_hash[..4],
158 );
159 Vec::new()
160 }
161
162 fn forward_path_request(
165 &mut self,
166 ctx: &AcceptedPathRequest,
167 select: impl Fn(&InterfaceInfo) -> bool,
168 ) -> Vec<TransportAction> {
169 let mut seed = Vec::with_capacity(16 + 16 + 8 + 8);
173 seed.extend_from_slice(&ctx.destination_hash);
174 seed.extend_from_slice(&ctx.tag[..ctx.tag_len]);
175 seed.extend_from_slice(&ctx.interface_id.0.to_be_bytes());
176 seed.extend_from_slice(&ctx.now.to_bits().to_be_bytes());
177 let tag = hash::truncated_hash(&seed);
178 let mut unique_tag = [0u8; 32];
179 unique_tag[..16].copy_from_slice(&ctx.destination_hash);
180 unique_tag[16..].copy_from_slice(&tag);
181 self.insert_discovery_pr_tag(unique_tag);
182
183 let transport_identity_hash = if self.config.transport_enabled {
184 self.config.identity_hash.as_ref()
185 } else {
186 None
187 };
188 let Some((raw, _)) =
189 build_path_request_packet(&ctx.destination_hash, transport_identity_hash, &tag)
190 else {
191 return Vec::new();
192 };
193 self.interfaces
194 .values()
195 .filter(|info| info.out_capable && select(info))
196 .map(|info| TransportAction::SendOnInterface {
197 interface: info.id,
198 raw: raw.clone().into(),
199 })
200 .collect()
201 }
202
203 fn parse_path_request(
204 &mut self,
205 data: &[u8],
206 interface_id: InterfaceId,
207 now: f64,
208 ) -> Option<AcceptedPathRequest> {
209 if data.len() < 16 {
210 return None;
211 }
212
213 let mut destination_hash = [0u8; 16];
214 destination_hash.copy_from_slice(&data[..16]);
215
216 let tag_bytes = if data.len() > 32 {
217 Some(&data[32..])
218 } else if data.len() > 16 {
219 Some(&data[16..])
220 } else {
221 None
222 };
223 let Some(tag_bytes) = tag_bytes else {
224 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
225 "Ignoring tagless path request for {:02x?}",
226 &destination_hash[..4],
227 );
228 return None;
229 };
230
231 let tag_len = tag_bytes.len().min(16);
232 let mut unique_tag = [0u8; 32];
233 unique_tag[..16].copy_from_slice(&destination_hash);
234 unique_tag[16..16 + tag_len].copy_from_slice(&tag_bytes[..tag_len]);
235 if !self.insert_discovery_pr_tag(unique_tag) {
236 return None;
237 }
238
239 let already_in_flight = self.path_requests.contains_key(&destination_hash)
240 && !self.path_table.contains_key(&destination_hash)
241 && !self.local_destinations.contains_key(&destination_hash);
242 self.path_requests.entry(destination_hash).or_insert(now);
243
244 let mut tag = [0u8; 16];
245 tag[..tag_len].copy_from_slice(&tag_bytes[..tag_len]);
246 Some(AcceptedPathRequest {
247 tag,
248 tag_len,
249 interface_id,
250 now,
251 destination_hash,
252 already_in_flight,
253 })
254 }
255
256 fn batch_inflight_path_request(&mut self, ctx: &AcceptedPathRequest, ingress_limited: bool) {
257 let Some((ingress_control, ip_freq, started)) = self
258 .interfaces
259 .get(&ctx.interface_id)
260 .map(|info| (info.ingress_control, info.ip_freq, info.started))
261 else {
262 return;
263 };
264 if ingress_limited
265 || self.ingress_control.should_ingress_limit_pr(
266 ctx.interface_id,
267 &ingress_control,
268 ip_freq,
269 started,
270 ctx.now,
271 )
272 {
273 return;
274 }
275
276 let timeout = discovery_path_request_timeout(&self.interfaces);
277 let request = self
278 .discovery_path_requests
279 .entry(ctx.destination_hash)
280 .or_insert_with(|| DiscoveryPathRequest {
281 timestamp: ctx.now,
282 requesting_interfaces: Vec::new(),
283 engaged: false,
284 });
285 if !request.requesting_interfaces.contains(&ctx.interface_id) {
286 request.requesting_interfaces.push(ctx.interface_id);
287 }
288 self.discovery_path_request_deadlines
289 .entry(ctx.destination_hash)
290 .or_insert(ctx.now + timeout);
291 }
292
293 #[doc(hidden)]
295 pub fn record_outbound_path_request(&mut self, destination_hash: [u8; 16], now: f64) {
296 self.path_requests.insert(destination_hash, now);
297 }
298
299 fn handle_known_path_request(
300 &mut self,
301 ctx: &AcceptedPathRequest,
302 from_local_client: bool,
303 ) -> bool {
304 let Some(path) = self
305 .path_table
306 .get(&ctx.destination_hash)
307 .and_then(|ps| ps.primary())
308 .cloned()
309 else {
310 return false;
311 };
312
313 if let Some(recv_info) = self.interfaces.get(&ctx.interface_id) {
314 if recv_info.mode == constants::MODE_ROAMING
315 && path.receiving_interface == ctx.interface_id
316 {
317 return true;
318 }
319 }
320
321 let Some(raw) = path.announce_raw.as_ref() else {
322 return false;
323 };
324 if let Some(existing) = self.announce_table.get(&ctx.destination_hash).cloned() {
327 self.insert_held_announce(ctx.destination_hash, existing, ctx.now);
328 }
329 let retransmit_timeout =
332 if from_local_client || self.interface_is_local_client(path.receiving_interface) {
333 ctx.now
334 } else if let Some(iface_info) = self.interfaces.get(&ctx.interface_id) {
335 let base = ctx.now + constants::PATH_REQUEST_GRACE;
336 if iface_info.mode == constants::MODE_ROAMING {
337 base + constants::PATH_REQUEST_RG
338 } else {
339 base
340 }
341 } else {
342 ctx.now + constants::PATH_REQUEST_GRACE
343 };
344
345 let Ok(parsed) = RawPacket::unpack(raw) else {
346 return false;
347 };
348
349 let entry = AnnounceEntry {
350 timestamp: ctx.now,
351 retransmit_timeout,
352 retries: constants::PATHFINDER_R,
353 received_from: path.next_hop,
354 hops: path.hops,
355 packet_raw: raw.clone(),
356 packet_data: parsed.data().to_vec(),
357 destination_hash: ctx.destination_hash,
358 context_flag: parsed.flags.context_flag,
359 local_rebroadcasts: 0,
360 block_rebroadcasts: true,
361 attached_interface: Some(ctx.interface_id),
362 };
363
364 self.insert_announce_entry(ctx.destination_hash, entry, ctx.now);
365 true
366 }
367
368 fn discovery_search_mode(&self, interface_id: InterfaceId) -> Option<Option<&'static [u8]>> {
371 self.interfaces
372 .get(&interface_id)
373 .and_then(|info| search_mode_filter(info.mode, info.recursive_prs))
374 }
375
376 fn handle_discovery_path_request(
377 &mut self,
378 ctx: &AcceptedPathRequest,
379 ingress_limited: bool,
380 ) -> Vec<TransportAction> {
381 let Some((mode, recursive_prs, ingress_control, ip_freq, started)) =
382 self.interfaces.get(&ctx.interface_id).map(|info| {
383 (
384 info.mode,
385 info.recursive_prs,
386 info.ingress_control,
387 info.ip_freq,
388 info.started,
389 )
390 })
391 else {
392 return Vec::new();
393 };
394
395 let Some(search_mode_filter) = search_mode_filter(mode, recursive_prs) else {
396 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
397 "Not discovering path to {:02x?}: recursive path discovery is disabled on interface {}",
398 &ctx.destination_hash[..4],
399 ctx.interface_id.0,
400 );
401 return Vec::new();
402 };
403
404 if ingress_limited
405 || self.ingress_control.should_ingress_limit_pr(
406 ctx.interface_id,
407 &ingress_control,
408 ip_freq,
409 started,
410 ctx.now,
411 )
412 {
413 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
414 "Not discovering path to {:02x?}: ingress path-request limiting is active on interface {}",
415 &ctx.destination_hash[..4],
416 ctx.interface_id.0,
417 );
418 return Vec::new();
419 }
420
421 let egress_candidates: Vec<_> = self
422 .interfaces
423 .values()
424 .filter(|info| info.id != ctx.interface_id && info.out_capable)
425 .filter(|info| search_mode_filter.is_none_or(|modes| modes.contains(&info.mode)))
426 .map(|info| {
427 (
428 info.id,
429 info.ingress_control,
430 info.op_freq,
431 info.op_samples,
432 info.bitrate,
433 info.airtime_profile,
434 info.announce_cap,
435 )
436 })
437 .collect();
438
439 let Some((path_request_raw, path_request_len)) = build_path_request_packet(
440 &ctx.destination_hash,
441 self.config.identity_hash.as_ref(),
442 &ctx.tag[..ctx.tag_len],
443 ) else {
444 return Vec::new();
445 };
446
447 let mut actions = Vec::new();
448 for (id, ingress_control, op_freq, op_samples, bitrate, airtime_profile, announce_cap) in
449 egress_candidates
450 {
451 if self.ingress_control.should_egress_limit_pr(
452 id,
453 &ingress_control,
454 op_freq,
455 op_samples,
456 ) || self
457 .announce_queues
458 .blocks_recursive_path_request(id, ctx.now)
459 {
460 continue;
461 }
462
463 self.announce_queues.reserve_recursive_path_request(
464 id,
465 path_request_len + constants::HEADER_MINSIZE,
466 ctx.now,
467 bitrate,
468 airtime_profile,
469 announce_cap,
470 );
471 actions.push(TransportAction::SendOnInterface {
472 interface: id,
473 raw: path_request_raw.clone().into(),
474 });
475 }
476
477 if !actions.is_empty() {
478 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
479 "Discovering unknown path to {:02x?} on behalf of interface {} via {} interfaces",
480 &ctx.destination_hash[..4],
481 ctx.interface_id.0,
482 actions.len(),
483 );
484 let request = self
485 .discovery_path_requests
486 .entry(ctx.destination_hash)
487 .or_insert_with(|| DiscoveryPathRequest {
488 timestamp: ctx.now,
489 requesting_interfaces: Vec::new(),
490 engaged: false,
491 });
492 if !request.requesting_interfaces.contains(&ctx.interface_id) {
493 request.requesting_interfaces.push(ctx.interface_id);
494 }
495 request.engaged = true;
496 let timeout = discovery_path_request_timeout(&self.interfaces);
497 self.discovery_path_request_deadlines
498 .insert(ctx.destination_hash, ctx.now + timeout);
499 } else {
500 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
501 "Not discovering path to {:02x?}: no eligible egress interface",
502 &ctx.destination_hash[..4],
503 );
504 }
505
506 actions
507 }
508}
509
510fn search_mode_filter(mode: u8, recursive_prs: bool) -> Option<Option<&'static [u8]>> {
513 if recursive_prs || constants::DISCOVER_PATHS_FOR.contains(&mode) {
514 Some(None)
515 } else if mode == constants::MODE_BOUNDARY {
516 Some(Some(&constants::BOUNDARY_SEARCH_MODES))
517 } else {
518 None
519 }
520}
521
522fn build_path_request_packet(
523 destination_hash: &[u8; 16],
524 transport_identity_hash: Option<&[u8; 16]>,
525 tag: &[u8],
526) -> Option<(Vec<u8>, usize)> {
527 let mut data = Vec::with_capacity(16 + transport_identity_hash.map_or(0, |_| 16) + tag.len());
528 data.extend_from_slice(destination_hash);
529 if let Some(identity_hash) = transport_identity_hash {
530 data.extend_from_slice(identity_hash);
531 }
532 data.extend_from_slice(tag);
533
534 let flags = crate::packet::PacketFlags {
535 header_type: constants::HEADER_1,
536 context_flag: constants::FLAG_UNSET,
537 transport_type: constants::TRANSPORT_BROADCAST,
538 destination_type: constants::DESTINATION_PLAIN,
539 packet_type: constants::PACKET_TYPE_DATA,
540 };
541 let path_request_dest =
542 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
543
544 let data_len = data.len();
545 RawPacket::pack(
546 flags,
547 0,
548 &path_request_dest,
549 None,
550 constants::CONTEXT_NONE,
551 &data,
552 )
553 .ok()
554 .map(|packet| (packet.raw, data_len))
555}