ndn_engine/stages/
strategy.rs1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use smallvec::SmallVec;
6use tracing::trace;
7
8use crate::Fib;
9use crate::enricher::ContextEnricher;
10use crate::pipeline::{
11 Action, AnyMap, DecodedPacket, DropReason, ForwardingAction, NackReason, PacketContext,
12};
13use ndn_discovery::scope::is_link_local;
14use ndn_packet::Name;
15use ndn_store::{Pit, StrategyTable};
16use ndn_strategy::{MeasurementsTable, Strategy, StrategyContext};
17use ndn_transport::face::FaceScope;
18
19pub trait ErasedStrategy: Send + Sync + 'static {
21 fn name(&self) -> &Name;
23
24 fn decide_sync(&self, ctx: &StrategyContext<'_>) -> Option<SmallVec<[ForwardingAction; 2]>>;
27
28 fn after_receive_interest_erased<'a>(
30 &'a self,
31 ctx: &'a StrategyContext<'a>,
32 ) -> Pin<Box<dyn Future<Output = SmallVec<[ForwardingAction; 2]>> + Send + 'a>>;
33
34 fn on_nack_erased<'a>(
36 &'a self,
37 ctx: &'a StrategyContext<'a>,
38 reason: NackReason,
39 ) -> Pin<Box<dyn Future<Output = ForwardingAction> + Send + 'a>>;
40}
41
42impl<S: Strategy> ErasedStrategy for S {
43 fn name(&self) -> &Name {
44 Strategy::name(self)
45 }
46
47 fn decide_sync(&self, ctx: &StrategyContext<'_>) -> Option<SmallVec<[ForwardingAction; 2]>> {
48 self.decide(ctx)
49 }
50
51 fn after_receive_interest_erased<'a>(
52 &'a self,
53 ctx: &'a StrategyContext<'a>,
54 ) -> Pin<Box<dyn Future<Output = SmallVec<[ForwardingAction; 2]>> + Send + 'a>> {
55 Box::pin(self.after_receive_interest(ctx))
56 }
57
58 fn on_nack_erased<'a>(
59 &'a self,
60 ctx: &'a StrategyContext<'a>,
61 reason: NackReason,
62 ) -> Pin<Box<dyn Future<Output = ForwardingAction> + Send + 'a>> {
63 Box::pin(self.on_nack(ctx, reason))
64 }
65}
66
67pub struct StrategyStage {
73 pub strategy_table: Arc<StrategyTable<dyn ErasedStrategy>>,
74 pub default_strategy: Arc<dyn ErasedStrategy>,
75 pub fib: Arc<Fib>,
76 pub measurements: Arc<MeasurementsTable>,
77 pub pit: Arc<Pit>,
78 pub face_table: Arc<ndn_transport::FaceTable>,
79 pub enrichers: Vec<Arc<dyn ContextEnricher>>,
81}
82
83impl StrategyStage {
84 pub async fn process(&self, mut ctx: PacketContext) -> Action {
86 match &ctx.packet {
87 DecodedPacket::Interest(_) => {}
88 _ => return Action::Continue(ctx),
90 };
91
92 let name = match &ctx.name {
93 Some(n) => n.clone(),
94 None => return Action::Drop(DropReason::MalformedPacket),
95 };
96
97 let fib_entry_arc = self.fib.lpm(&name);
98 let fib_entry_ref = fib_entry_arc.as_deref();
99
100 if let Some(e) = fib_entry_ref {
101 trace!(face=%ctx.face_id, name=%name, nexthops=?e.nexthops.iter().map(|nh| (nh.face_id, nh.cost)).collect::<Vec<_>>(), "strategy: FIB LPM hit");
102 } else {
103 trace!(face=%ctx.face_id, name=%name, "strategy: FIB LPM miss (no route)");
104 }
105
106 let strategy_fib: Option<ndn_strategy::FibEntry> =
108 fib_entry_ref.map(|e| ndn_strategy::FibEntry {
109 nexthops: e
110 .nexthops
111 .iter()
112 .map(|nh| ndn_strategy::FibNexthop {
113 face_id: nh.face_id,
114 cost: nh.cost,
115 })
116 .collect(),
117 });
118
119 let mut extensions = AnyMap::new();
121 for enricher in &self.enrichers {
122 enricher.enrich(strategy_fib.as_ref(), &mut extensions);
123 }
124
125 let sctx = StrategyContext {
126 name: &name,
127 in_face: ctx.face_id,
128 fib_entry: strategy_fib.as_ref(),
129 pit_token: ctx.pit_token,
130 measurements: &self.measurements,
131 extensions: &extensions,
132 };
133
134 let strategy = self
136 .strategy_table
137 .lpm(&name)
138 .unwrap_or_else(|| Arc::clone(&self.default_strategy));
139 trace!(face=%ctx.face_id, name=%name, strategy=%strategy.name(), "strategy: selected");
140
141 let actions = if let Some(a) = strategy.decide_sync(&sctx) {
144 a
145 } else {
146 strategy.after_receive_interest_erased(&sctx).await
147 };
148
149 if let Some(action) = actions.into_iter().next() {
151 match action {
152 ForwardingAction::Forward(faces) => {
153 trace!(face=%ctx.face_id, name=%name, out_faces=?faces, "strategy: Forward");
154 let effective_faces: SmallVec<[ndn_transport::FaceId; 4]> = if is_link_local(
158 &name,
159 ) {
160 faces.iter().copied().filter(|fid| {
161 let keep = self.face_table.get(*fid)
162 .map(|f| f.kind().scope() == FaceScope::Local)
163 .unwrap_or(false);
164 if !keep {
165 trace!(face=%ctx.face_id, name=%name, out_face=%fid, "strategy: dropping link-local packet on non-local face");
166 }
167 keep
168 }).collect()
169 } else {
170 faces.iter().copied().collect()
171 };
172 if effective_faces.is_empty() {
173 return Action::Nack(ctx, NackReason::NoRoute);
175 }
176 ctx.out_faces.extend_from_slice(&effective_faces);
177 let out = ctx.out_faces.clone();
178 return Action::Send(ctx, out);
179 }
180 ForwardingAction::ForwardAfter { faces, delay } => {
181 trace!(face=%ctx.face_id, name=%name, out_faces=?faces, delay_ms=%delay.as_millis(), "strategy: ForwardAfter");
182 let pit = Arc::clone(&self.pit);
184 let face_table = Arc::clone(&self.face_table);
185 let raw_bytes = ctx.raw_bytes.clone();
186 let pit_token = ctx.pit_token;
187 tokio::spawn(async move {
188 tokio::time::sleep(delay).await;
189 if let Some(token) = pit_token
192 && !pit.contains(&token)
193 {
194 return; }
196 for face_id in &faces {
197 if let Some(face) = face_table.get(*face_id) {
198 let _ = face.send_bytes(raw_bytes.clone()).await;
199 }
200 }
201 });
202 return Action::Drop(DropReason::Other); }
204 ForwardingAction::Nack(reason) => {
205 trace!(face=%ctx.face_id, name=%name, reason=?reason, "strategy: Nack");
206 return Action::Nack(ctx, reason);
207 }
208 ForwardingAction::Suppress => {
209 trace!(face=%ctx.face_id, name=%name, "strategy: Suppress");
210 return Action::Drop(DropReason::Suppressed);
211 }
212 }
213 }
214
215 trace!(face=%ctx.face_id, name=%name, "strategy: no actionable decision, Nack NoRoute");
217 Action::Nack(ctx, NackReason::NoRoute)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn link_local_scope_check_is_accurate() {
227 use std::str::FromStr;
228 let link_local = ndn_packet::Name::from_str("/ndn/local/nd/hello/1").unwrap();
229 let global = ndn_packet::Name::from_str("/ndn/edu/test").unwrap();
230 assert!(is_link_local(&link_local), "/ndn/local/ must be link-local");
231 assert!(!is_link_local(&global), "/ndn/edu/ must not be link-local");
232 }
233}