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 scion_proto::path::Path;
20
21use crate::path::{
22 policy::PathPolicy,
23 scoring::{PathScorer, PathScoring},
24 types::PathManagerPath,
25};
26
27pub mod policy;
28pub mod scoring;
29
30/// PathStrategy combines multiple path operations into a single strategy.
31#[derive(Default)]
32pub struct PathStrategy {
33 /// The path policies to apply.
34 pub policies: Vec<Arc<dyn PathPolicy>>,
35 /// The path ranking functions to apply.
36 pub scoring: PathScorer,
37}
38impl PathStrategy {
39 /// Appends a path policy to the list of policies.
40 pub fn add_policy(&mut self, policy: impl PathPolicy) {
41 self.policies.push(Arc::new(policy));
42 }
43
44 /// Adds a path scorer with the given impact weight.
45 ///
46 /// Scores from paths are used to select the best path among multiple candidates.
47 ///
48 /// `scorer` - The path scorer to add.
49 /// `impact` - The weight of the scorer in the final score aggregation.
50 /// e.g. Impact of 0.2 means the scorer can change the final score by up to ±0.2.
51 ///
52 /// Note:
53 /// The impact weight does not need to sum to 1.0 across all scorers.
54 pub fn add_scoring(&mut self, scoring: impl PathScoring, impact: f32) {
55 self.scoring = self.scoring.clone().with_scorer(scoring, impact);
56 }
57
58 /// Ranks the order of two paths based on preference.
59 ///
60 /// # Return
61 /// Returns the **preference ordering** between two paths.
62 ///
63 /// - `Ordering::Less` if `this` is preferred over `other`
64 /// - `Ordering::Greater` if `other` is preferred over `this`
65 /// - `Ordering::Equal` if both paths are equally preferred
66 pub fn rank_order(
67 &self,
68 this: &PathManagerPath,
69 other: &PathManagerPath,
70 now: SystemTime,
71 ) -> Ordering {
72 let this_score = self.scoring.score(this, now);
73 let other_score = self.scoring.score(other, now);
74
75 this_score.total_cmp(&other_score).reverse() // Reverse: Greater score -> Less (more preferred)
76 }
77
78 /// Sorts the given paths in place, placing the most preferred paths first.
79 ///
80 /// If no ranking functions are added, the paths are not modified.
81 pub fn rank_inplace(&self, path: &mut [PathManagerPath], now: SystemTime) {
82 path.sort_by(|a, b| self.rank_order(a, b, now));
83 }
84
85 /// Returns true if the given path is accepted by all policies.
86 ///
87 /// If no policies are added, all paths are accepted.
88 pub fn predicate(&self, path: &Path) -> bool {
89 self.policies.iter().all(|policy| policy.predicate(path))
90 }
91
92 /// Filters the given paths based on all policies, removing paths that are not accepted.
93 pub fn filter_inplace<'path: 'iter, 'iter>(&self, paths: &mut Vec<Path>) {
94 paths.retain(|p| self.predicate(p));
95 }
96}