Skip to main content

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}