ndn_strategy/
multicast.rs1use 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
10pub struct MulticastStrategy {
12 name: Name,
13}
14
15impl MulticastStrategy {
16 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}