1use super::*;
2
3pub(in crate::node) enum TransitNextHopPlan {
4 Route(NodeAddr),
5 Loop(NodeAddr),
6 NoRoute,
7}
8
9impl Node {
10 pub(in crate::node) fn cache_current_root_coords(
13 &mut self,
14 node_addr: NodeAddr,
15 coords: crate::tree::TreeCoordinate,
16 now_ms: u64,
17 ) -> bool {
18 if coords.node_addr() != &node_addr
19 || coords.root_id() != self.tree_state.my_coords().root_id()
20 {
21 return false;
22 }
23 self.coord_cache.insert(node_addr, coords, now_ms);
24 true
25 }
26
27 pub(crate) fn is_tree_peer(&self, peer_addr: &NodeAddr) -> bool {
32 if !self.tree_state.is_root() && self.tree_state.my_declaration().parent_id() == peer_addr {
34 return true;
35 }
36 if let Some(decl) = self.tree_state.peer_declaration(peer_addr)
38 && decl.parent_id() == self.node_addr()
39 {
40 return true;
41 }
42 false
43 }
44
45 pub fn find_next_hop(&mut self, dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
72 if dest_node_addr == self.node_addr() {
74 return None;
75 }
76 let now_ms = Self::now_ms();
77 let failed_learned_routes = self.learned_routes.failed_next_hops(dest_node_addr, now_ms);
78 let direct_path_hard_degraded =
79 self.session_direct_path_is_degraded(dest_node_addr, now_ms);
80 let direct_path_soft_degraded = !direct_path_hard_degraded
81 && self.session_direct_discovered_endpoint_trust_expired(dest_node_addr, now_ms);
82 let fallback_peer_available = self.has_sendable_fallback_lookup_peer(dest_node_addr);
83 let direct_session_degraded =
84 fallback_peer_available && (direct_path_hard_degraded || direct_path_soft_degraded);
85 let direct_session_untrusted = !direct_session_degraded
86 && self.session_direct_path_exclusive_trust_expired(dest_node_addr, now_ms);
87 let stale_traversal_direct_route = self
88 .peers
89 .get(dest_node_addr)
90 .filter(|peer| {
91 !direct_path_hard_degraded
92 && !direct_session_degraded
93 && !peer.is_healthy()
94 && peer.can_send()
95 })
96 .and_then(|_| {
97 self.configured_peer(dest_node_addr)
98 .and_then(|peer_config| {
99 (peer_config.is_auto_connect()
100 && self.active_peer_uses_traversal_path(dest_node_addr, peer_config))
101 .then_some(*dest_node_addr)
102 })
103 });
104
105 let healthy_direct_route = self
106 .peers
107 .get(dest_node_addr)
108 .filter(|peer| peer.is_healthy() && !direct_session_degraded)
109 .map(|_| *dest_node_addr);
110 let active_fallback_affinity = (self.config.node.routing.mode == RoutingMode::ReplyLearned)
111 .then(|| {
112 let activity = self.dataplane.fsp_owner_activity(dest_node_addr)?;
113 let next_hop = activity.last_outbound_next_hop()?;
114 (next_hop != *dest_node_addr
115 && activity.has_recent_outbound_activity(
116 now_ms,
117 self.session_direct_path_exclusive_trust_timeout_ms(),
118 )
119 && !failed_learned_routes.contains(&next_hop)
120 && self
121 .peers
122 .get(&next_hop)
123 .is_some_and(|peer| peer.is_healthy() && peer.can_send()))
124 .then_some(next_hop)
125 })
126 .flatten();
127 if let Some(next_hop_addr) = active_fallback_affinity {
128 self.learned_routes
129 .record_selected(dest_node_addr, &next_hop_addr, now_ms);
130 return self.peers.get(&next_hop_addr);
131 }
132 let direct_session_has_recent_data_return =
133 self.session_direct_path_has_recent_data_return(dest_node_addr, now_ms);
134 if let Some(direct_addr) = healthy_direct_route
135 && direct_session_has_recent_data_return
136 {
137 return self.peers.get(&direct_addr);
138 }
139 if let Some(direct_addr) = healthy_direct_route
140 && !direct_session_untrusted
141 && self.dataplane_fmp_link_cost(&direct_addr)
142 <= 1.0 + ROUTING_FALLBACK_MIN_COST_ADVANTAGE
143 {
144 return self.peers.get(&direct_addr);
145 }
146 let direct_payload_eligible = healthy_direct_route.is_some();
147 let payload_candidate_can_send = |addr: &NodeAddr, peer: &ActivePeer| {
148 if addr == dest_node_addr {
149 direct_payload_eligible
150 } else {
151 peer.is_healthy() && !failed_learned_routes.contains(addr)
152 }
153 };
154
155 let fallback_beats_direct = |node: &Self, fallback_addr: NodeAddr| {
160 if direct_session_untrusted {
161 return healthy_direct_route != Some(fallback_addr)
162 && node
163 .peers
164 .get(&fallback_addr)
165 .is_some_and(|peer| peer.is_healthy());
166 }
167 node.route_candidate_beats_direct(healthy_direct_route, fallback_addr)
168 };
169
170 let sendable_learned_peers = if self.config.node.routing.mode == RoutingMode::ReplyLearned {
171 Some(
172 self.peers
173 .iter()
174 .filter(|(addr, peer)| payload_candidate_can_send(addr, peer))
175 .map(|(addr, _)| *addr)
176 .collect::<HashSet<_>>(),
177 )
178 } else {
179 None
180 };
181
182 let explore_fallback = sendable_learned_peers.as_ref().is_some_and(|sendable| {
183 self.learned_routes.should_explore_fallback(
184 dest_node_addr,
185 now_ms,
186 self.config.node.routing.learned_fallback_explore_interval,
187 |addr| sendable.contains(addr),
188 )
189 });
190 if let Some(sendable) = &sendable_learned_peers
197 && !explore_fallback
198 {
199 let eligible = sendable
200 .iter()
201 .copied()
202 .filter(|addr| fallback_beats_direct(self, *addr))
203 .collect::<HashSet<_>>();
204 if !eligible.is_empty()
205 && let Some(next_hop_addr) =
206 self.learned_routes
207 .select_next_hop(dest_node_addr, now_ms, |addr| eligible.contains(addr))
208 {
209 return self.peers.get(&next_hop_addr);
210 }
211 }
212
213 let Some(dest_coords) = self
215 .coord_cache
216 .get_and_touch(dest_node_addr, now_ms)
217 .cloned()
218 else {
219 if (healthy_direct_route.is_none() || explore_fallback)
220 && let Some(sendable) = &sendable_learned_peers
221 && let Some(next_hop_addr) =
222 self.learned_routes
223 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
224 {
225 return self.peers.get(&next_hop_addr);
226 }
227 if let Some(direct_addr) = healthy_direct_route {
228 return self.peers.get(&direct_addr);
229 }
230 if let Some(direct_addr) = stale_traversal_direct_route {
231 return self.peers.get(&direct_addr);
232 }
233 return None;
234 };
235
236 let coordinate_route_addr = {
239 let candidates: Vec<&ActivePeer> = self
240 .peers
241 .iter()
242 .filter(|(addr, peer)| {
243 payload_candidate_can_send(addr, peer) && peer.may_reach(dest_node_addr)
244 })
245 .map(|(_, peer)| peer)
246 .collect();
247 if !candidates.is_empty() {
248 self.select_best_candidate(&candidates, &dest_coords)
249 .map(|peer| *peer.node_addr())
250 } else {
251 None
252 }
253 };
254 if let Some(next_hop_addr) = coordinate_route_addr
255 && fallback_beats_direct(self, next_hop_addr)
256 {
257 return self.peers.get(&next_hop_addr);
258 }
259
260 let tree_route_addr = self.select_tree_payload_candidate(
262 &dest_coords,
263 dest_node_addr,
264 direct_payload_eligible,
265 );
266 if let Some(next_hop_addr) = tree_route_addr
267 && fallback_beats_direct(self, next_hop_addr)
268 {
269 return self.peers.get(&next_hop_addr);
270 }
271
272 if explore_fallback
273 && let Some(peer) = sendable_learned_peers.as_ref().and_then(|sendable| {
274 self.learned_routes
275 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
276 .and_then(|next_hop_addr| self.peers.get(&next_hop_addr))
277 })
278 {
279 return Some(peer);
280 }
281
282 if let Some(direct_addr) = healthy_direct_route {
283 return self.peers.get(&direct_addr);
284 }
285
286 if let Some(sendable) = &sendable_learned_peers
287 && let Some(next_hop_addr) =
288 self.learned_routes
289 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
290 {
291 return self.peers.get(&next_hop_addr);
292 }
293
294 if let Some(direct_addr) = stale_traversal_direct_route {
295 return self.peers.get(&direct_addr);
296 }
297
298 None
299 }
300
301 pub(in crate::node) fn plan_transit_next_hop(
302 &mut self,
303 dest_node_addr: &NodeAddr,
304 previous_hop: &NodeAddr,
305 ) -> TransitNextHopPlan {
306 if dest_node_addr == self.node_addr() {
307 return TransitNextHopPlan::NoRoute;
308 }
309
310 if dest_node_addr != previous_hop
311 && self
312 .peers
313 .get(dest_node_addr)
314 .is_some_and(|peer| peer.is_healthy())
315 {
316 return TransitNextHopPlan::Route(*dest_node_addr);
317 }
318
319 if self.config.node.routing.mode == RoutingMode::ReplyLearned {
325 let sendable = self
326 .peers
327 .iter()
328 .filter(|(addr, peer)| *addr != previous_hop && peer.is_healthy())
329 .map(|(addr, _)| *addr)
330 .collect::<HashSet<_>>();
331 if let Some(next_hop_addr) =
332 self.learned_routes
333 .select_handshake_route(dest_node_addr, Self::now_ms(), |addr| {
334 sendable.contains(addr)
335 })
336 {
337 return TransitNextHopPlan::Route(next_hop_addr);
338 }
339 }
340
341 let Some(next_hop_addr) = self
342 .find_next_hop(dest_node_addr)
343 .map(|peer| *peer.node_addr())
344 else {
345 return TransitNextHopPlan::NoRoute;
346 };
347 if next_hop_addr == *dest_node_addr && &next_hop_addr != previous_hop {
348 return TransitNextHopPlan::Route(next_hop_addr);
349 }
350
351 let now_ms = Self::now_ms();
352 let dest_coords = self
353 .coord_cache
354 .get_and_touch(dest_node_addr, now_ms)
355 .cloned();
356 let selected_strictly_progresses = dest_coords.as_ref().is_some_and(|dest_coords| {
357 self.tree_state.my_coords().root_id() == dest_coords.root_id()
358 && self
359 .tree_state
360 .peer_coords(&next_hop_addr)
361 .is_some_and(|peer_coords| {
362 peer_coords.distance_to(dest_coords)
363 < self.tree_state.my_coords().distance_to(dest_coords)
364 })
365 });
366
367 if &next_hop_addr != previous_hop && (dest_coords.is_none() || selected_strictly_progresses)
368 {
369 return TransitNextHopPlan::Route(next_hop_addr);
370 }
371
372 let coordinate_fallback = dest_coords.and_then(|dest_coords| {
373 self.select_tree_payload_candidate_avoiding(
374 &dest_coords,
375 dest_node_addr,
376 false,
377 Some(previous_hop),
378 )
379 });
380 if let Some(next_hop_addr) = coordinate_fallback {
381 return TransitNextHopPlan::Route(next_hop_addr);
382 }
383 TransitNextHopPlan::Loop(next_hop_addr)
384 }
385
386 #[cfg(test)]
387 pub(in crate::node) fn find_transit_next_hop(
388 &mut self,
389 dest_node_addr: &NodeAddr,
390 previous_hop: &NodeAddr,
391 ) -> Option<NodeAddr> {
392 match self.plan_transit_next_hop(dest_node_addr, previous_hop) {
393 TransitNextHopPlan::Route(next_hop_addr) => Some(next_hop_addr),
394 TransitNextHopPlan::Loop(next_hop_addr) => {
395 self.record_route_failure(*dest_node_addr, next_hop_addr);
396 None
397 }
398 TransitNextHopPlan::NoRoute => None,
399 }
400 }
401
402 pub(super) fn route_candidate_beats_direct(
403 &self,
404 healthy_direct_route: Option<NodeAddr>,
405 candidate_addr: NodeAddr,
406 ) -> bool {
407 let Some(direct_addr) = healthy_direct_route else {
408 return true;
409 };
410 if candidate_addr == direct_addr {
411 return false;
412 }
413
414 if !self.peers.contains_key(&direct_addr) {
415 return true;
416 }
417 if self.active_peer_uses_configured_static_udp_path(&direct_addr) {
418 return false;
419 }
420 let Some(candidate) = self.peers.get(&candidate_addr) else {
421 return false;
422 };
423 if !candidate.is_healthy() {
424 return false;
425 }
426
427 let direct_cost = self.dataplane_fmp_link_cost(&direct_addr);
428 let candidate_cost = self.dataplane_fmp_link_cost(&candidate_addr);
429 candidate_cost + ROUTING_FALLBACK_MIN_COST_ADVANTAGE < direct_cost
430 }
431
432 pub(super) fn select_tree_payload_candidate(
433 &self,
434 dest_coords: &crate::tree::TreeCoordinate,
435 direct_dest: &NodeAddr,
436 direct_payload_eligible: bool,
437 ) -> Option<NodeAddr> {
438 self.select_tree_payload_candidate_avoiding(
439 dest_coords,
440 direct_dest,
441 direct_payload_eligible,
442 None,
443 )
444 }
445
446 fn select_tree_payload_candidate_avoiding(
447 &self,
448 dest_coords: &crate::tree::TreeCoordinate,
449 direct_dest: &NodeAddr,
450 direct_payload_eligible: bool,
451 excluded: Option<&NodeAddr>,
452 ) -> Option<NodeAddr> {
453 if self.tree_state.my_coords().root_id() != dest_coords.root_id() {
454 return None;
455 }
456
457 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
458 let mut best: Option<(NodeAddr, usize)> = None;
459
460 for (peer_addr, peer) in &self.peers {
461 if excluded == Some(peer_addr) {
462 continue;
463 }
464 if peer_addr == direct_dest {
465 if !direct_payload_eligible {
466 continue;
467 }
468 } else if !peer.is_healthy() {
469 continue;
470 }
471
472 let Some(peer_coords) = self.tree_state.peer_coords(peer_addr) else {
473 continue;
474 };
475 let distance = peer_coords.distance_to(dest_coords);
476 if distance >= my_distance {
477 continue;
478 }
479
480 let dominated = match &best {
481 None => true,
482 Some((best_id, best_dist)) => {
483 distance < *best_dist || (distance == *best_dist && peer_addr < best_id)
484 }
485 };
486 if dominated {
487 best = Some((*peer_addr, distance));
488 }
489 }
490
491 best.map(|(peer_addr, _)| peer_addr)
492 }
493
494 pub(in crate::node) fn session_direct_path_is_degraded(
495 &mut self,
496 dest: &NodeAddr,
497 now_ms: u64,
498 ) -> bool {
499 self.session_direct_degradation.is_degraded(dest, now_ms)
500 }
501
502 pub(in crate::node) fn session_direct_path_degradation_active(
503 &self,
504 dest: &NodeAddr,
505 now_ms: u64,
506 ) -> bool {
507 self.session_direct_degradation.is_degraded_at(dest, now_ms)
508 }
509
510 pub(in crate::node) fn session_direct_path_blocks_direct_payload(
511 &mut self,
512 dest: &NodeAddr,
513 now_ms: u64,
514 ) -> bool {
515 self.session_direct_path_is_degraded(dest, now_ms)
516 || self.session_direct_discovered_endpoint_trust_expired(dest, now_ms)
517 }
518
519 pub(in crate::node) fn session_direct_path_exclusive_trust_timeout_ms(&self) -> u64 {
520 self.config
521 .node
522 .heartbeat_interval_secs
523 .saturating_mul(1000)
524 .saturating_add(1_500)
525 .max(SESSION_DIRECT_MIN_EXCLUSIVE_TRUST_MS)
526 }
527
528 pub(in crate::node) fn session_direct_path_exclusive_trust_expired(
529 &self,
530 dest: &NodeAddr,
531 now_ms: u64,
532 ) -> bool {
533 if !self
534 .peers
535 .get(dest)
536 .is_some_and(|peer| peer.is_healthy() && peer.can_send())
537 {
538 return false;
539 }
540 let Some(activity) = self.dataplane.fsp_owner_activity(dest) else {
541 return false;
542 };
543 activity.has_recent_outbound_without_data_return_from(
544 dest,
545 now_ms,
546 self.session_direct_path_exclusive_trust_timeout_ms(),
547 )
548 }
549
550 pub(in crate::node) fn session_direct_path_has_recent_data_return(
551 &self,
552 dest: &NodeAddr,
553 now_ms: u64,
554 ) -> bool {
555 self.dataplane
556 .fsp_owner_activity(dest)
557 .is_some_and(|activity| {
558 activity.has_recent_data_return_from(
559 dest,
560 now_ms,
561 self.session_direct_path_exclusive_trust_timeout_ms(),
562 )
563 })
564 }
565
566 fn session_direct_discovered_endpoint_trust_expired(
567 &self,
568 dest: &NodeAddr,
569 now_ms: u64,
570 ) -> bool {
571 self.session_direct_path_exclusive_trust_expired(dest, now_ms)
572 && self.configured_peer(dest).is_some_and(|peer_config| {
573 peer_config.is_auto_connect()
574 && self.active_peer_uses_traversal_path(dest, peer_config)
575 })
576 }
577
578 pub(in crate::node) fn mark_session_direct_path_degraded(
579 &mut self,
580 dest: NodeAddr,
581 now_ms: u64,
582 ) -> bool {
583 let changed = self.session_direct_degradation.mark_degraded(
584 dest,
585 now_ms,
586 SESSION_DIRECT_DEGRADED_HOLD_MS,
587 );
588 if changed {
589 let _ = self.refresh_dataplane_fsp_owner_routes(&dest);
590 }
591 changed
592 }
593
594 pub(in crate::node) fn clear_session_direct_path_degraded(&mut self, dest: &NodeAddr) -> bool {
595 let changed = self.session_direct_degradation.clear(dest);
596 if changed {
597 let _ = self.refresh_dataplane_fsp_owner_routes(dest);
598 }
599 changed
600 }
601
602 pub(in crate::node) fn clear_session_direct_path_degraded_after_promotion(
603 &mut self,
604 dest: &NodeAddr,
605 now_ms: u64,
606 ) {
607 let direct_was_degraded = self.session_direct_path_degradation_active(dest, now_ms);
608 let active_fallback_next_hop = self
609 .dataplane
610 .fsp_owner_activity(dest)
611 .and_then(|activity| activity.last_outbound_next_hop())
612 .filter(|next_hop| next_hop != dest);
613 if direct_was_degraded || active_fallback_next_hop.is_some() {
614 if let Some(fallback_next_hop) = active_fallback_next_hop {
615 let _ = self
616 .dataplane
617 .forget_fsp_data_route(*dest, fallback_next_hop);
618 }
619 debug!(
620 peer = %self.peer_display_name(dest),
621 direct_was_degraded,
622 released_fallback_affinity = active_fallback_next_hop.is_some(),
623 "Authenticated direct-path promotion restored payload eligibility"
624 );
625 if !self.clear_session_direct_path_degraded(dest) {
626 let _ = self.refresh_dataplane_fsp_owner_routes(dest);
627 }
628 return;
629 }
630
631 let keep_degraded = self.session_direct_path_blocks_direct_payload(dest, now_ms);
632 if !keep_degraded {
633 self.clear_session_direct_path_degraded(dest);
634 } else if self.promoted_path_matches_configured_static_peer(dest) {
635 debug!(
636 peer = %self.peer_display_name(dest),
637 "Clearing direct payload degradation after configured direct-path promotion"
638 );
639 self.clear_session_direct_path_degraded(dest);
640 } else {
641 debug!(
642 peer = %self.peer_display_name(dest),
643 "Keeping direct payload degraded after direct-path promotion"
644 );
645 }
646 }
647
648 fn promoted_path_matches_configured_static_peer(&self, peer_node_addr: &NodeAddr) -> bool {
649 self.config
650 .auto_connect_peers()
651 .filter(|peer_config| {
652 PeerIdentity::from_npub(&peer_config.npub)
653 .ok()
654 .is_some_and(|identity| identity.node_addr() == peer_node_addr)
655 })
656 .any(|peer_config| {
657 self.static_peer_addresses(peer_config)
658 .iter()
659 .any(|candidate| self.active_peer_matches_candidate(peer_node_addr, candidate))
660 })
661 }
662
663 pub(in crate::node) fn learn_reverse_route(
664 &mut self,
665 destination: NodeAddr,
666 next_hop: NodeAddr,
667 ) {
668 if self.config.node.routing.mode != RoutingMode::ReplyLearned
669 || destination == *self.node_addr()
670 {
671 return;
672 }
673 let now_ms = Self::now_ms();
674 self.learned_routes.learn(
675 destination,
676 next_hop,
677 now_ms,
678 self.config.node.routing.learned_ttl_secs,
679 self.config.node.routing.max_learned_routes_per_dest,
680 );
681 let _ = self.refresh_dataplane_fsp_owner_routes_retaining_current(&destination);
686 }
687
688 pub(in crate::node) fn pin_handshake_reverse_route(
689 &mut self,
690 destination: NodeAddr,
691 next_hop: NodeAddr,
692 ) {
693 if self.config.node.routing.mode != RoutingMode::ReplyLearned
694 || destination == *self.node_addr()
695 {
696 return;
697 }
698 self.learned_routes.pin_handshake_route(
699 destination,
700 next_hop,
701 Self::now_ms(),
702 self.config.node.routing.learned_ttl_secs,
703 self.config.node.routing.max_learned_routes_per_dest,
704 );
705 }
706
707 pub(in crate::node) fn pin_duplicate_handshake_reverse_route(
708 &mut self,
709 destination: NodeAddr,
710 next_hop: NodeAddr,
711 ) {
712 if self.config.node.routing.mode != RoutingMode::ReplyLearned
713 || destination == *self.node_addr()
714 {
715 return;
716 }
717 let now_ms = Self::now_ms();
718 if self
719 .learned_routes
720 .active_handshake_route(&destination, now_ms)
721 .is_some_and(|pinned_hop| pinned_hop != next_hop)
722 {
723 return;
724 }
725 self.learned_routes.pin_handshake_route(
726 destination,
727 next_hop,
728 now_ms,
729 self.config.node.routing.learned_ttl_secs,
730 self.config.node.routing.max_learned_routes_per_dest,
731 );
732 }
733
734 pub(in crate::node) fn routing_error_matches_active_path(
735 &mut self,
736 destination: &NodeAddr,
737 previous_hop: &NodeAddr,
738 ) -> bool {
739 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
740 return true;
741 }
742
743 if let Some(last_outbound_next_hop) = self
749 .dataplane
750 .fsp_owner_activity(destination)
751 .and_then(|activity| activity.last_outbound_next_hop())
752 {
753 return last_outbound_next_hop == *previous_hop;
754 }
755
756 if let Some(pinned_hop) = self
757 .learned_routes
758 .active_handshake_route(destination, Self::now_ms())
759 {
760 return pinned_hop == *previous_hop;
764 }
765
766 self.dataplane
775 .fsp_owner_next_hop(destination)
776 .is_none_or(|next_hop| next_hop == *previous_hop)
777 }
778
779 pub(in crate::node) fn record_route_failure(
780 &mut self,
781 destination: NodeAddr,
782 next_hop: NodeAddr,
783 ) {
784 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
785 return;
786 }
787 let _ = self.dataplane.forget_fsp_data_route(destination, next_hop);
788 self.learned_routes.record_failure(&destination, &next_hop);
789 let _ = self.refresh_dataplane_fsp_owner_routes(&destination);
790 }
791
792 pub(crate) fn learned_route_table_snapshot(&self, now_ms: u64) -> LearnedRouteTableSnapshot {
793 self.learned_routes.snapshot(now_ms)
794 }
795
796 pub(in crate::node) fn purge_learned_routes(&mut self, now_ms: u64) {
797 self.learned_routes.purge_expired(now_ms);
798 }
799
800 pub(super) fn select_best_candidate<'a>(
809 &'a self,
810 candidates: &[&'a ActivePeer],
811 dest_coords: &crate::tree::TreeCoordinate,
812 ) -> Option<&'a ActivePeer> {
813 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
814
815 let mut best: Option<(&ActivePeer, f64, usize)> = None;
816
817 for &candidate in candidates {
818 if !candidate.can_send() {
819 continue;
820 }
821
822 let cost = self.dataplane_fmp_link_cost(candidate.node_addr());
823
824 let dist = self
825 .tree_state
826 .peer_coords(candidate.node_addr())
827 .map(|pc| pc.distance_to(dest_coords))
828 .unwrap_or(usize::MAX);
829
830 if dist >= my_distance {
833 continue;
834 }
835
836 let dominated = match &best {
837 None => true,
838 Some((_, best_cost, best_dist)) => {
839 cost < *best_cost
840 || (cost == *best_cost && dist < *best_dist)
841 || (cost == *best_cost
842 && dist == *best_dist
843 && candidate.node_addr() < best.as_ref().unwrap().0.node_addr())
844 }
845 };
846
847 if dominated {
848 best = Some((candidate, cost, dist));
849 }
850 }
851
852 best.map(|(peer, _, _)| peer)
853 }
854
855 pub fn destination_in_filters(&self, dest: &NodeAddr) -> Vec<&ActivePeer> {
857 self.peers.values().filter(|p| p.may_reach(dest)).collect()
858 }
859}