ndn_store/
strategy_table.rs

1use std::sync::Arc;
2
3use ndn_packet::Name;
4
5use crate::NameTrie;
6
7/// Maps name prefixes to strategy instances via longest-prefix match.
8///
9/// This is a second `NameTrie` that runs in parallel with the FIB. It maps
10/// name prefixes to strategy objects of type `S` (typically `dyn Strategy`
11/// from `ndn-strategy`). Keeping the type parameter generic avoids a
12/// dependency on `ndn-strategy` from `ndn-store`.
13pub struct StrategyTable<S: Send + Sync + 'static + ?Sized>(NameTrie<Arc<S>>);
14
15impl<S: Send + Sync + 'static + ?Sized> StrategyTable<S> {
16    pub fn new() -> Self {
17        Self(NameTrie::new())
18    }
19
20    /// Longest-prefix match — returns the strategy at the deepest matching node.
21    pub fn lpm(&self, name: &Name) -> Option<Arc<S>> {
22        self.0.lpm(name)
23    }
24
25    /// Register a strategy for `prefix`, replacing any existing entry.
26    pub fn insert(&self, prefix: &Name, strategy: Arc<S>) {
27        self.0.insert(prefix, strategy);
28    }
29
30    /// Remove the strategy registered at exactly `prefix`.
31    pub fn remove(&self, prefix: &Name) {
32        self.0.remove(prefix);
33    }
34
35    /// Return all (prefix, strategy) entries for status reporting.
36    pub fn dump(&self) -> Vec<(Name, Arc<S>)> {
37        self.0.dump()
38    }
39}
40
41impl<S: Send + Sync + 'static + ?Sized> Default for StrategyTable<S> {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use bytes::Bytes;
51    use ndn_packet::NameComponent;
52
53    fn name(components: &[&str]) -> Name {
54        Name::from_components(
55            components
56                .iter()
57                .map(|s| NameComponent::generic(Bytes::copy_from_slice(s.as_bytes()))),
58        )
59    }
60
61    // A trivial stand-in for a real strategy.
62    struct MockStrategy(u32);
63
64    #[test]
65    fn lpm_empty_returns_none() {
66        let table: StrategyTable<MockStrategy> = StrategyTable::new();
67        assert!(table.lpm(&name(&["a", "b"])).is_none());
68    }
69
70    #[test]
71    fn lpm_exact_match() {
72        let table: StrategyTable<MockStrategy> = StrategyTable::new();
73        table.insert(&name(&["a"]), Arc::new(MockStrategy(1)));
74        let s = table.lpm(&name(&["a"])).unwrap();
75        assert_eq!(s.0, 1);
76    }
77
78    #[test]
79    fn lpm_most_specific_wins() {
80        let table: StrategyTable<MockStrategy> = StrategyTable::new();
81        table.insert(&name(&["a"]), Arc::new(MockStrategy(10)));
82        table.insert(&name(&["a", "b"]), Arc::new(MockStrategy(20)));
83        let s = table.lpm(&name(&["a", "b", "c"])).unwrap();
84        assert_eq!(s.0, 20);
85    }
86
87    #[test]
88    fn lpm_fallback_to_shorter_prefix() {
89        let table: StrategyTable<MockStrategy> = StrategyTable::new();
90        table.insert(&name(&["a"]), Arc::new(MockStrategy(5)));
91        let s = table.lpm(&name(&["a", "b"])).unwrap();
92        assert_eq!(s.0, 5);
93    }
94
95    #[test]
96    fn lpm_default_strategy_at_root() {
97        let table: StrategyTable<MockStrategy> = StrategyTable::new();
98        table.insert(&Name::root(), Arc::new(MockStrategy(99)));
99        let s = table.lpm(&name(&["x", "y", "z"])).unwrap();
100        assert_eq!(s.0, 99);
101    }
102
103    #[test]
104    fn remove_clears_entry() {
105        let table: StrategyTable<MockStrategy> = StrategyTable::new();
106        table.insert(&name(&["a"]), Arc::new(MockStrategy(1)));
107        table.remove(&name(&["a"]));
108        assert!(table.lpm(&name(&["a"])).is_none());
109    }
110
111    #[test]
112    fn insert_replaces_strategy() {
113        let table: StrategyTable<MockStrategy> = StrategyTable::new();
114        table.insert(&name(&["a"]), Arc::new(MockStrategy(1)));
115        table.insert(&name(&["a"]), Arc::new(MockStrategy(2)));
116        let s = table.lpm(&name(&["a"])).unwrap();
117        assert_eq!(s.0, 2);
118    }
119}