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, false);
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, true);
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 relay_to_local_clients: bool,
169 ) -> Vec<TransportAction> {
170 let tag = &ctx.tag[..ctx.tag_len];
176 if !relay_to_local_clients && self.stamp_prs && ctx.ext_payload.is_none() {
177 return self
182 .interfaces
183 .values()
184 .filter(|info| info.out_capable && select(info))
185 .map(|info| TransportAction::StampPathRequest {
186 destination_hash: ctx.destination_hash,
187 tag: tag.to_vec(),
188 interface: info.id,
189 })
190 .collect();
191 }
192 let ext_payload = ctx
193 .ext_payload
194 .or_else(|| (relay_to_local_clients && self.stamp_prs).then(unstamped_relay_payload));
195
196 let Some((raw, _)) = build_path_request_packet(
198 &ctx.destination_hash,
199 self.config.identity_hash.as_ref(),
200 tag,
201 ext_payload.as_ref(),
202 ) else {
203 return Vec::new();
204 };
205 self.interfaces
206 .values()
207 .filter(|info| info.out_capable && select(info))
208 .map(|info| TransportAction::SendOnInterface {
209 interface: info.id,
210 raw: raw.clone().into(),
211 })
212 .collect()
213 }
214
215 fn parse_path_request(
216 &mut self,
217 data: &[u8],
218 interface_id: InterfaceId,
219 now: f64,
220 ) -> Option<AcceptedPathRequest> {
221 if data.len() < 16 {
222 return None;
223 }
224
225 let mut destination_hash = [0u8; 16];
226 destination_hash.copy_from_slice(&data[..16]);
227
228 if data.len() <= 16 {
229 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
230 "Ignoring tagless path request for {:02x?}",
231 &destination_hash[..4],
232 );
233 return None;
234 }
235
236 if !constants::is_valid_path_request_length(data.len()) {
242 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
243 "Invalid path request size {} for {:02x?}",
244 data.len(),
245 &destination_hash[..4],
246 );
247 return None;
248 }
249 let ext_payload = if data.len() == constants::PATH_REQUEST_STAMPED_LENGTH {
250 let mut ext = [0u8; constants::PR_EXT_PAYLOAD_LENGTH];
251 ext.copy_from_slice(&data[constants::PATH_REQUEST_LENGTH..]);
252 Some(ext)
253 } else {
254 None
255 };
256 let tag_bytes = if data.len() == constants::PATH_REQUEST_LEGACY_LENGTH {
257 &data[16..32]
258 } else {
259 &data[32..48]
260 };
261
262 let tag_len = tag_bytes.len();
263
264 let mut carried_stamp_value = None;
265 let mut stamped = false;
266 if constants::STAMPS_ENABLED {
267 let valid = match ext_payload {
274 None => {
275 log::trace!("Unstamped path request for {:02x?}", &destination_hash[..4]);
276 false
277 }
278 Some(ext) if ext[0] != constants::EXT_PAYLOAD_MARKER => {
279 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
280 "Unrecognized stamp payload in path request for {:02x?}",
281 &destination_hash[..4]
282 );
283 false
284 }
285 Some(ext) => {
286 let mut material = Vec::with_capacity(16 + tag_len);
287 material.extend_from_slice(&destination_hash);
288 material.extend_from_slice(&tag_bytes[..tag_len]);
289 let workblock = crate::stamp::stamp_workblock(&material, 0);
290 let stamp = &ext[constants::EXT_HEADER_LENGTH..];
291 let value = crate::stamp::stamp_value(&workblock, stamp);
292 let required_value = self
294 .interfaces
295 .get(&interface_id)
296 .map(|info| info.stamp_costs.pr_cost(&self.stamp_requirements))
297 .unwrap_or(self.stamp_requirements.path_request_cost);
298 let valid = crate::stamp::stamp_valid(stamp, required_value, &workblock);
299 carried_stamp_value = Some(value);
300 stamped = valid;
301 let relayed = ext[1] == constants::PR_FLAG_UNSTAMPED_RELAY;
302 if relayed {
303 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
304 "Lenient-relayed path request for {:02x?} with value {value}",
305 &destination_hash[..4]
306 );
307 } else {
308 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
309 "Stamped path request for {:02x?} with value {value}",
310 &destination_hash[..4]
311 );
312 }
313 if !valid && relayed {
314 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
315 "Unstamped relayed path request from stamp-aware node for {:02x?}",
316 &destination_hash[..4]
317 );
318 } else if !valid {
319 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
320 "Invalid or insufficient (v:{value}) path request stamp for {:02x?}",
321 &destination_hash[..4]
322 );
323 }
324 valid
325 }
326 };
327 if self.stamp_requirements.enforced && !valid {
328 return None;
329 }
330 }
331
332 let mut unique_tag = [0u8; 32];
333 unique_tag[..16].copy_from_slice(&destination_hash);
334 unique_tag[16..16 + tag_len].copy_from_slice(&tag_bytes[..tag_len]);
335 if !self.insert_discovery_pr_tag(unique_tag) {
336 return None;
337 }
338
339 let already_in_flight = self.path_requests.contains_key(&destination_hash)
340 && !self.path_table.contains_key(&destination_hash)
341 && !self.local_destinations.contains_key(&destination_hash);
342 self.path_requests.entry(destination_hash).or_insert(now);
343
344 let mut tag = [0u8; 16];
345 tag[..tag_len].copy_from_slice(&tag_bytes[..tag_len]);
346 Some(AcceptedPathRequest {
347 tag,
348 tag_len,
349 interface_id,
350 now,
351 destination_hash,
352 already_in_flight,
353 ext_payload,
354 stamp_value: carried_stamp_value,
355 stamped,
356 })
357 }
358
359 fn batch_inflight_path_request(&mut self, ctx: &AcceptedPathRequest, ingress_limited: bool) {
360 let Some((ingress_control, ip_freq, started)) = self
361 .interfaces
362 .get(&ctx.interface_id)
363 .map(|info| (info.ingress_control, info.ip_freq, info.started))
364 else {
365 return;
366 };
367 if ingress_limited
368 || self.ingress_control.should_ingress_limit_pr(
369 ctx.interface_id,
370 &ingress_control,
371 ip_freq,
372 started,
373 ctx.now,
374 )
375 {
376 return;
377 }
378
379 let timeout = discovery_path_request_timeout(&self.interfaces);
380 let request = self
381 .discovery_path_requests
382 .entry(ctx.destination_hash)
383 .or_insert_with(|| DiscoveryPathRequest {
384 timestamp: ctx.now,
385 requesting_interfaces: Vec::new(),
386 engaged: false,
387 });
388 if !request.requesting_interfaces.contains(&ctx.interface_id) {
389 request.requesting_interfaces.push(ctx.interface_id);
390 }
391 self.discovery_path_request_deadlines
392 .entry(ctx.destination_hash)
393 .or_insert(ctx.now + timeout);
394 }
395
396 #[doc(hidden)]
398 pub fn record_outbound_path_request(&mut self, destination_hash: [u8; 16], now: f64) {
399 self.path_requests.insert(destination_hash, now);
400 }
401
402 pub fn stamp_prs(&self) -> bool {
405 self.stamp_prs
406 }
407
408 #[doc(hidden)]
410 pub fn set_stamp_prs(&mut self, enabled: bool) {
411 self.stamp_prs = enabled;
412 }
413
414 pub fn stamp_requirements(&self) -> StampRequirements {
416 self.stamp_requirements
417 }
418
419 pub fn destination_filter(&self) -> &super::destination_filter::DestinationTypeFilter {
421 &self.destination_filter
422 }
423
424 pub fn destination_filter_mut(
427 &mut self,
428 ) -> &mut super::destination_filter::DestinationTypeFilter {
429 &mut self.destination_filter
430 }
431
432 pub fn set_stamp_requirements(&mut self, requirements: StampRequirements) {
435 self.stamp_requirements = requirements;
436 }
437
438 pub fn build_path_request(
445 &self,
446 destination_hash: &[u8; 16],
447 tag: &[u8],
448 ext_payload: Option<&[u8; constants::PR_EXT_PAYLOAD_LENGTH]>,
449 ) -> Option<Vec<u8>> {
450 build_path_request_packet(
451 destination_hash,
452 self.config.identity_hash.as_ref(),
453 tag,
454 ext_payload,
455 )
456 .map(|(raw, _)| raw)
457 }
458
459 fn handle_known_path_request(
460 &mut self,
461 ctx: &AcceptedPathRequest,
462 from_local_client: bool,
463 ) -> bool {
464 let Some(path) = self
465 .path_table
466 .get(&ctx.destination_hash)
467 .and_then(|ps| ps.primary())
468 .cloned()
469 else {
470 return false;
471 };
472
473 if let Some(recv_info) = self.interfaces.get(&ctx.interface_id) {
474 if recv_info.mode == constants::MODE_ROAMING
475 && path.receiving_interface == ctx.interface_id
476 {
477 return true;
478 }
479 }
480
481 let Some(raw) = path.announce_raw.as_ref() else {
482 return false;
483 };
484 if let Some(existing) = self.announce_table.get(&ctx.destination_hash).cloned() {
487 self.insert_held_announce(ctx.destination_hash, existing, ctx.now);
488 }
489 let retransmit_timeout =
492 if from_local_client || self.interface_is_local_client(path.receiving_interface) {
493 ctx.now
494 } else if let Some(iface_info) = self.interfaces.get(&ctx.interface_id) {
495 let base = ctx.now + constants::PATH_REQUEST_GRACE;
496 if iface_info.mode == constants::MODE_ROAMING {
497 base + constants::PATH_REQUEST_RG
498 } else {
499 base
500 }
501 } else {
502 ctx.now + constants::PATH_REQUEST_GRACE
503 };
504
505 let Ok(parsed) = RawPacket::unpack(raw) else {
506 return false;
507 };
508
509 let entry = AnnounceEntry {
510 timestamp: ctx.now,
511 retransmit_timeout,
512 retries: constants::PATHFINDER_R,
513 received_from: path.next_hop,
514 hops: path.hops,
515 packet_raw: raw.clone(),
516 packet_data: parsed.data().to_vec(),
517 destination_hash: ctx.destination_hash,
518 context_flag: parsed.flags.context_flag,
519 local_rebroadcasts: 0,
520 block_rebroadcasts: true,
521 attached_interface: Some(ctx.interface_id),
522 };
523
524 self.insert_announce_entry(ctx.destination_hash, entry, ctx.now);
525 true
526 }
527
528 fn discovery_search_mode(&self, interface_id: InterfaceId) -> Option<Option<&'static [u8]>> {
531 self.interfaces
532 .get(&interface_id)
533 .and_then(|info| search_mode_filter(info.mode, info.recursive_prs))
534 }
535
536 fn handle_discovery_path_request(
537 &mut self,
538 ctx: &AcceptedPathRequest,
539 ingress_limited: bool,
540 ) -> Vec<TransportAction> {
541 let Some((mode, recursive_prs, ingress_control, ip_freq, started)) =
542 self.interfaces.get(&ctx.interface_id).map(|info| {
543 (
544 info.mode,
545 info.recursive_prs,
546 info.ingress_control,
547 info.ip_freq,
548 info.started,
549 )
550 })
551 else {
552 return Vec::new();
553 };
554
555 let Some(search_mode_filter) = search_mode_filter(mode, recursive_prs) else {
556 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
557 "Not discovering path to {:02x?}: recursive path discovery is disabled on interface {}",
558 &ctx.destination_hash[..4],
559 ctx.interface_id.0,
560 );
561 return Vec::new();
562 };
563
564 if ingress_limited
565 || self.ingress_control.should_ingress_limit_pr(
566 ctx.interface_id,
567 &ingress_control,
568 ip_freq,
569 started,
570 ctx.now,
571 )
572 {
573 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
574 "Not discovering path to {:02x?}: ingress path-request limiting is active on interface {}",
575 &ctx.destination_hash[..4],
576 ctx.interface_id.0,
577 );
578 return Vec::new();
579 }
580
581 let egress_candidates: Vec<_> = self
582 .interfaces
583 .values()
584 .filter(|info| info.id != ctx.interface_id && info.out_capable)
585 .filter(|info| search_mode_filter.is_none_or(|modes| modes.contains(&info.mode)))
586 .map(|info| {
587 (
588 info.id,
589 info.ingress_control,
590 info.op_freq,
591 info.op_samples,
592 info.bitrate,
593 info.airtime_profile,
594 info.announce_cap,
595 )
596 })
597 .collect();
598
599 let ext_payload = ctx
603 .ext_payload
604 .or_else(|| self.stamp_prs.then(unstamped_relay_payload));
605 let Some((path_request_raw, path_request_len)) = build_path_request_packet(
606 &ctx.destination_hash,
607 self.config.identity_hash.as_ref(),
608 &ctx.tag[..ctx.tag_len],
609 ext_payload.as_ref(),
610 ) else {
611 return Vec::new();
612 };
613
614 let mut actions = Vec::new();
615 for (id, ingress_control, op_freq, op_samples, bitrate, airtime_profile, announce_cap) in
616 egress_candidates
617 {
618 if self.ingress_control.should_egress_limit_pr(
619 id,
620 &ingress_control,
621 op_freq,
622 op_samples,
623 ) || self
624 .announce_queues
625 .blocks_recursive_path_request(id, ctx.now)
626 {
627 continue;
628 }
629
630 self.announce_queues.reserve_recursive_path_request(
631 id,
632 path_request_len + constants::HEADER_MINSIZE,
633 ctx.now,
634 bitrate,
635 airtime_profile,
636 announce_cap,
637 );
638 actions.push(TransportAction::SendOnInterface {
639 interface: id,
640 raw: path_request_raw.clone().into(),
641 });
642 }
643
644 if !actions.is_empty() {
645 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
646 "Discovering unknown path to {:02x?} on behalf of interface {} via {} interfaces",
647 &ctx.destination_hash[..4],
648 ctx.interface_id.0,
649 actions.len(),
650 );
651 let request = self
652 .discovery_path_requests
653 .entry(ctx.destination_hash)
654 .or_insert_with(|| DiscoveryPathRequest {
655 timestamp: ctx.now,
656 requesting_interfaces: Vec::new(),
657 engaged: false,
658 });
659 if !request.requesting_interfaces.contains(&ctx.interface_id) {
660 request.requesting_interfaces.push(ctx.interface_id);
661 }
662 request.engaged = true;
663 let timeout = discovery_path_request_timeout(&self.interfaces);
664 self.discovery_path_request_deadlines
665 .insert(ctx.destination_hash, ctx.now + timeout);
666 } else {
667 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
668 "Not discovering path to {:02x?}: no eligible egress interface",
669 &ctx.destination_hash[..4],
670 );
671 }
672
673 actions
674 }
675}
676
677fn search_mode_filter(mode: u8, recursive_prs: bool) -> Option<Option<&'static [u8]>> {
680 if recursive_prs || constants::DISCOVER_PATHS_FOR.contains(&mode) {
681 Some(None)
682 } else if mode == constants::MODE_BOUNDARY {
683 Some(Some(&constants::BOUNDARY_SEARCH_MODES))
684 } else {
685 None
686 }
687}
688
689pub(crate) fn unstamped_relay_payload() -> [u8; constants::PR_EXT_PAYLOAD_LENGTH] {
692 let mut ext = [0u8; constants::PR_EXT_PAYLOAD_LENGTH];
693 ext[0] = constants::EXT_PAYLOAD_MARKER;
694 ext[1] = constants::PR_FLAG_UNSTAMPED_RELAY;
695 ext
696}
697
698fn build_path_request_packet(
704 destination_hash: &[u8; 16],
705 transport_identity_hash: Option<&[u8; 16]>,
706 tag: &[u8],
707 ext_payload: Option<&[u8; constants::PR_EXT_PAYLOAD_LENGTH]>,
708) -> Option<(Vec<u8>, usize)> {
709 let mut data = Vec::with_capacity(
710 16 + transport_identity_hash.map_or(0, |_| 16)
711 + tag.len()
712 + ext_payload.map_or(0, |_| constants::PR_EXT_PAYLOAD_LENGTH),
713 );
714 data.extend_from_slice(destination_hash);
715 if let Some(identity_hash) = transport_identity_hash {
716 data.extend_from_slice(identity_hash);
717 }
718 data.extend_from_slice(tag);
719 if let Some(ext) = ext_payload {
720 data.extend_from_slice(ext);
721 }
722
723 let flags = crate::packet::PacketFlags {
724 header_type: constants::HEADER_1,
725 context_flag: constants::FLAG_UNSET,
726 transport_type: constants::TRANSPORT_BROADCAST,
727 destination_type: constants::DESTINATION_PLAIN,
728 packet_type: constants::PACKET_TYPE_DATA,
729 };
730 let path_request_dest =
731 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
732
733 let data_len = data.len();
734 RawPacket::pack(
735 flags,
736 0,
737 &path_request_dest,
738 None,
739 constants::CONTEXT_NONE,
740 &data,
741 )
742 .ok()
743 .map(|packet| (packet.raw, data_len))
744}