scion_stack/path/strategy.rs
1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Path Selection defines how paths are filtered and ranked.
16
17use std::{cmp::Ordering, sync::Arc, time::SystemTime};
18
19use sciparse::path::ScionPath;
20
21use crate::path::{policy::PathPolicy, scoring::PathScorer, types::PathManagerPath};
22
23pub mod policy;
24pub(crate) mod scoring;
25
26/// `PathStrategy` combines multiple path operations into a single strategy.
27#[derive(Default)]
28pub struct PathStrategy {
29 /// The path policies to apply.
30 pub policies: Vec<Arc<dyn PathPolicy>>,
31 /// The path ranking functions to apply.
32 ///
33 /// Path scoring is an internal concern of the stack: it is populated with default scorers
34 /// and is not part of the public API.
35 pub(crate) scoring: PathScorer,
36}
37impl PathStrategy {
38 /// Appends a path policy to the list of policies.
39 pub fn add_policy(&mut self, policy: impl PathPolicy) {
40 self.policies.push(Arc::new(policy));
41 }
42
43 /// Adds a path scorer with the given impact weight.
44 ///
45 /// Scores from paths are used to select the best path among multiple candidates.
46 ///
47 /// `scorer` - The path scorer to add.
48 /// `impact` - The weight of the scorer in the final score aggregation.
49 /// e.g. Impact of 0.2 means the scorer can change the final score by up to ±0.2.
50 ///
51 /// Note:
52 /// The impact weight does not need to sum to 1.0 across all scorers.
53 #[cfg(test)]
54 pub(crate) fn add_scoring(
55 &mut self,
56 scoring: impl crate::path::scoring::PathScoring,
57 impact: f32,
58 ) {
59 self.scoring = std::mem::take(&mut self.scoring).with_scorer(scoring, impact);
60 }
61
62 /// Ranks the order of two paths based on preference.
63 ///
64 /// # Return
65 /// Returns the **preference ordering** between two paths.
66 ///
67 /// - `Ordering::Less` if `this` is preferred over `other`
68 /// - `Ordering::Greater` if `other` is preferred over `this`
69 /// - `Ordering::Equal` if both paths are equally preferred
70 pub(crate) fn rank_order(
71 &self,
72 this: &PathManagerPath,
73 other: &PathManagerPath,
74 now: SystemTime,
75 ) -> Ordering {
76 let this_score = self.scoring.score(this, now);
77 let other_score = self.scoring.score(other, now);
78
79 this_score.total_cmp(&other_score).reverse() // Reverse: Greater score -> Less (more preferred)
80 }
81
82 /// Sorts the given paths in place, placing the most preferred paths first.
83 ///
84 /// If no ranking functions are added, the paths are not modified.
85 pub(crate) fn rank_inplace(&self, path: &mut [PathManagerPath], now: SystemTime) {
86 path.sort_by(|a, b| self.rank_order(a, b, now));
87 }
88
89 /// Returns true if the given path is accepted by all policies.
90 ///
91 /// If no policies are added, all paths are accepted.
92 pub fn predicate(&self, path: &ScionPath) -> bool {
93 self.policies.iter().all(|policy| policy.predicate(path))
94 }
95
96 /// Filters the given paths based on all policies, removing paths that are not accepted.
97 pub fn filter_inplace(&self, paths: &mut Vec<ScionPath>) {
98 paths.retain(|p| self.predicate(p));
99 }
100}