ndn_strategy/
best_route.rs1use bytes::Bytes;
2use smallvec::{SmallVec, smallvec};
3
4use ndn_packet::{Name, NameComponent};
5use ndn_transport::{ForwardingAction, NackReason};
6
7use crate::{Strategy, StrategyContext};
8
9pub struct BestRouteStrategy {
12 name: Name,
13}
14
15impl BestRouteStrategy {
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"best-route")),
23 ])
24 }
25
26 pub fn new() -> Self {
27 Self {
28 name: Self::strategy_name(),
29 }
30 }
31}
32
33impl Default for BestRouteStrategy {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl Strategy for BestRouteStrategy {
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 nexthops = fib.nexthops_excluding(ctx.in_face);
49 match nexthops.first() {
50 Some(nh) => Some(smallvec![ForwardingAction::Forward(smallvec![nh.face_id])]),
51 None => Some(smallvec![ForwardingAction::Nack(NackReason::NoRoute)]),
52 }
53 }
54
55 async fn after_receive_interest(
56 &self,
57 ctx: &StrategyContext<'_>,
58 ) -> SmallVec<[ForwardingAction; 2]> {
59 self.decide(ctx).unwrap()
62 }
63
64 async fn after_receive_data(
65 &self,
66 _ctx: &StrategyContext<'_>,
67 ) -> SmallVec<[ForwardingAction; 2]> {
68 SmallVec::new()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::MeasurementsTable;
77 use crate::context::{FibEntry, FibNexthop};
78 use ndn_transport::FaceId;
79 use std::sync::Arc;
80
81 fn make_ctx<'a>(
82 name: &'a Arc<Name>,
83 in_face: FaceId,
84 fib_entry: Option<&'a FibEntry>,
85 measurements: &'a MeasurementsTable,
86 ) -> StrategyContext<'a> {
87 static EMPTY: std::sync::LazyLock<ndn_transport::AnyMap> =
88 std::sync::LazyLock::new(ndn_transport::AnyMap::new);
89 StrategyContext {
90 name,
91 in_face,
92 fib_entry,
93 pit_token: None,
94 measurements,
95 extensions: &EMPTY,
96 }
97 }
98
99 #[tokio::test]
100 async fn no_fib_entry_returns_nack_no_route() {
101 let strategy = BestRouteStrategy::new();
102 let name = Arc::new(Name::root());
103 let measurements = MeasurementsTable::new();
104 let ctx = make_ctx(&name, FaceId(0), None, &measurements);
105 let actions = strategy.after_receive_interest(&ctx).await;
106 assert!(matches!(
107 actions.as_slice(),
108 [ForwardingAction::Nack(NackReason::NoRoute)]
109 ));
110 }
111
112 #[tokio::test]
113 async fn best_nexthop_selected() {
114 let strategy = BestRouteStrategy::new();
115 let name = Arc::new(Name::root());
116 let measurements = MeasurementsTable::new();
117 let fib = FibEntry {
118 nexthops: vec![
119 FibNexthop {
120 face_id: FaceId(2),
121 cost: 10,
122 },
123 FibNexthop {
124 face_id: FaceId(3),
125 cost: 20,
126 },
127 ],
128 };
129 let ctx = make_ctx(&name, FaceId(1), Some(&fib), &measurements);
130 let actions = strategy.after_receive_interest(&ctx).await;
131 if let [ForwardingAction::Forward(faces)] = actions.as_slice() {
133 assert_eq!(faces[0], FaceId(2));
134 } else {
135 panic!("expected Forward");
136 }
137 }
138
139 #[tokio::test]
140 async fn split_horizon_excludes_in_face() {
141 let strategy = BestRouteStrategy::new();
142 let name = Arc::new(Name::root());
143 let measurements = MeasurementsTable::new();
144 let fib = FibEntry {
146 nexthops: vec![FibNexthop {
147 face_id: FaceId(1),
148 cost: 0,
149 }],
150 };
151 let ctx = make_ctx(&name, FaceId(1), Some(&fib), &measurements);
152 let actions = strategy.after_receive_interest(&ctx).await;
153 assert!(matches!(
154 actions.as_slice(),
155 [ForwardingAction::Nack(NackReason::NoRoute)]
156 ));
157 }
158
159 #[tokio::test]
160 async fn after_receive_data_returns_empty() {
161 let strategy = BestRouteStrategy::new();
162 let name = Arc::new(Name::root());
163 let measurements = MeasurementsTable::new();
164 let ctx = make_ctx(&name, FaceId(0), None, &measurements);
165 let actions = strategy.after_receive_data(&ctx).await;
166 assert!(actions.is_empty());
167 }
168
169 #[test]
170 fn strategy_name() {
171 let s = BestRouteStrategy::new();
172 let comps = s.name().components();
173 assert_eq!(comps.len(), 4);
174 assert_eq!(comps[3].value.as_ref(), b"best-route");
175 }
176}