ndn_strategy/
multicast.rs

1use bytes::Bytes;
2use smallvec::{SmallVec, smallvec};
3
4use ndn_packet::{Name, NameComponent};
5use ndn_transport::FaceId;
6use ndn_transport::{ForwardingAction, NackReason};
7
8use crate::{Strategy, StrategyContext};
9
10/// Multicast strategy: forward on all FIB nexthops except the incoming face.
11pub struct MulticastStrategy {
12    name: Name,
13}
14
15impl MulticastStrategy {
16    /// NFD strategy name: `/localhost/nfd/strategy/multicast`
17    pub fn strategy_name() -> Name {
18        Name::from_components([
19            NameComponent::generic(Bytes::from_static(b"localhost")),
20            NameComponent::generic(Bytes::from_static(b"nfd")),
21            NameComponent::generic(Bytes::from_static(b"strategy")),
22            NameComponent::generic(Bytes::from_static(b"multicast")),
23        ])
24    }
25
26    pub fn new() -> Self {
27        Self {
28            name: Self::strategy_name(),
29        }
30    }
31}
32
33impl Default for MulticastStrategy {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl Strategy for MulticastStrategy {
40    fn name(&self) -> &Name {
41        &self.name
42    }
43
44    fn decide(&self, ctx: &StrategyContext<'_>) -> Option<SmallVec<[ForwardingAction; 2]>> {
45        let Some(fib) = ctx.fib_entry else {
46            return Some(smallvec![ForwardingAction::Nack(NackReason::NoRoute)]);
47        };
48        let faces: SmallVec<[FaceId; 4]> = fib
49            .nexthops_excluding(ctx.in_face)
50            .into_iter()
51            .map(|n| n.face_id)
52            .collect();
53        if faces.is_empty() {
54            return Some(smallvec![ForwardingAction::Nack(NackReason::NoRoute)]);
55        }
56        Some(smallvec![ForwardingAction::Forward(faces)])
57    }
58
59    async fn after_receive_interest(
60        &self,
61        ctx: &StrategyContext<'_>,
62    ) -> SmallVec<[ForwardingAction; 2]> {
63        self.decide(ctx).unwrap()
64    }
65
66    async fn after_receive_data(
67        &self,
68        _ctx: &StrategyContext<'_>,
69    ) -> SmallVec<[ForwardingAction; 2]> {
70        SmallVec::new()
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::MeasurementsTable;
78    use crate::context::{FibEntry, FibNexthop};
79    use ndn_transport::FaceId;
80    use std::sync::Arc;
81
82    fn make_ctx<'a>(
83        name: &'a Arc<Name>,
84        in_face: FaceId,
85        fib_entry: Option<&'a FibEntry>,
86        measurements: &'a MeasurementsTable,
87    ) -> StrategyContext<'a> {
88        static EMPTY: std::sync::LazyLock<ndn_transport::AnyMap> =
89            std::sync::LazyLock::new(ndn_transport::AnyMap::new);
90        StrategyContext {
91            name,
92            in_face,
93            fib_entry,
94            pit_token: None,
95            measurements,
96            extensions: &EMPTY,
97        }
98    }
99
100    #[tokio::test]
101    async fn no_fib_returns_nack() {
102        let s = MulticastStrategy::new();
103        let name = Arc::new(Name::root());
104        let m = MeasurementsTable::new();
105        let ctx = make_ctx(&name, FaceId(0), None, &m);
106        let actions = s.after_receive_interest(&ctx).await;
107        assert!(matches!(
108            actions.as_slice(),
109            [ForwardingAction::Nack(NackReason::NoRoute)]
110        ));
111    }
112
113    #[tokio::test]
114    async fn all_nexthops_sent_except_in_face() {
115        let s = MulticastStrategy::new();
116        let name = Arc::new(Name::root());
117        let m = MeasurementsTable::new();
118        let fib = FibEntry {
119            nexthops: vec![
120                FibNexthop {
121                    face_id: FaceId(1),
122                    cost: 0,
123                },
124                FibNexthop {
125                    face_id: FaceId(2),
126                    cost: 0,
127                },
128                FibNexthop {
129                    face_id: FaceId(3),
130                    cost: 0,
131                },
132            ],
133        };
134        let ctx = make_ctx(&name, FaceId(1), Some(&fib), &m);
135        let actions = s.after_receive_interest(&ctx).await;
136        if let [ForwardingAction::Forward(faces)] = actions.as_slice() {
137            assert_eq!(faces.len(), 2);
138            assert!(faces.contains(&FaceId(2)));
139            assert!(faces.contains(&FaceId(3)));
140            assert!(!faces.contains(&FaceId(1)));
141        } else {
142            panic!("expected Forward");
143        }
144    }
145
146    #[tokio::test]
147    async fn all_nexthops_excluded_returns_nack() {
148        let s = MulticastStrategy::new();
149        let name = Arc::new(Name::root());
150        let m = MeasurementsTable::new();
151        let fib = FibEntry {
152            nexthops: vec![FibNexthop {
153                face_id: FaceId(1),
154                cost: 0,
155            }],
156        };
157        let ctx = make_ctx(&name, FaceId(1), Some(&fib), &m);
158        let actions = s.after_receive_interest(&ctx).await;
159        assert!(matches!(
160            actions.as_slice(),
161            [ForwardingAction::Nack(NackReason::NoRoute)]
162        ));
163    }
164
165    #[tokio::test]
166    async fn after_receive_data_is_empty() {
167        let s = MulticastStrategy::new();
168        let name = Arc::new(Name::root());
169        let m = MeasurementsTable::new();
170        let ctx = make_ctx(&name, FaceId(0), None, &m);
171        assert!(s.after_receive_data(&ctx).await.is_empty());
172    }
173}