ndn_engine/
compose.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use smallvec::SmallVec;
6
7use crate::pipeline::{ForwardingAction, NackReason};
8use ndn_packet::Name;
9use ndn_strategy::{StrategyContext, StrategyFilter};
10
11use crate::stages::ErasedStrategy;
12
13/// A strategy that delegates to an inner strategy and post-processes
14/// its forwarding actions through a chain of filters.
15///
16/// This enables cross-layer filtering without modifying base strategies.
17/// For example, composing `BestRouteStrategy` with an `RssiFilter` produces
18/// "best-route but only on faces with acceptable signal strength."
19pub struct ComposedStrategy {
20    name: Name,
21    inner: Arc<dyn ErasedStrategy>,
22    filters: Vec<Arc<dyn StrategyFilter>>,
23}
24
25impl ComposedStrategy {
26    /// Build a composed strategy from an inner strategy and an ordered filter chain.
27    pub fn new(
28        name: Name,
29        inner: Arc<dyn ErasedStrategy>,
30        filters: Vec<Arc<dyn StrategyFilter>>,
31    ) -> Self {
32        Self {
33            name,
34            inner,
35            filters,
36        }
37    }
38
39    fn apply_filters(
40        &self,
41        ctx: &StrategyContext,
42        mut actions: SmallVec<[ForwardingAction; 2]>,
43    ) -> SmallVec<[ForwardingAction; 2]> {
44        for filter in &self.filters {
45            actions = filter.filter(ctx, actions);
46        }
47        actions
48    }
49}
50
51impl ErasedStrategy for ComposedStrategy {
52    fn name(&self) -> &Name {
53        &self.name
54    }
55
56    fn decide_sync(&self, ctx: &StrategyContext<'_>) -> Option<SmallVec<[ForwardingAction; 2]>> {
57        let actions = self.inner.decide_sync(ctx)?;
58        Some(self.apply_filters(ctx, actions))
59    }
60
61    fn after_receive_interest_erased<'a>(
62        &'a self,
63        ctx: &'a StrategyContext<'a>,
64    ) -> Pin<Box<dyn Future<Output = SmallVec<[ForwardingAction; 2]>> + Send + 'a>> {
65        Box::pin(async move {
66            let actions = self.inner.after_receive_interest_erased(ctx).await;
67            self.apply_filters(ctx, actions)
68        })
69    }
70
71    fn on_nack_erased<'a>(
72        &'a self,
73        ctx: &'a StrategyContext<'a>,
74        reason: NackReason,
75    ) -> Pin<Box<dyn Future<Output = ForwardingAction> + Send + 'a>> {
76        // Nack returns a single ForwardingAction, not a SmallVec —
77        // wrap it for filtering, then unwrap.
78        Box::pin(async move {
79            let action = self.inner.on_nack_erased(ctx, reason).await;
80            let mut actions = SmallVec::new();
81            actions.push(action);
82            let filtered = self.apply_filters(ctx, actions);
83            filtered
84                .into_iter()
85                .next()
86                .unwrap_or(ForwardingAction::Suppress)
87        })
88    }
89}