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 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}