ndn_engine/pipeline/
action.rs

1use smallvec::SmallVec;
2
3use ndn_transport::FaceId;
4
5// Re-export from ndn-transport where they are defined; both ndn-engine
6// and ndn-strategy use these types, and ndn-strategy must not depend on
7// ndn-engine to avoid a circular dependency.
8pub use ndn_transport::{ForwardingAction, NackReason};
9
10/// Reason a packet was dropped.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum DropReason {
13    MalformedPacket,
14    UnknownFace,
15    LoopDetected,
16    Suppressed,
17    RateLimited,
18    HopLimitExceeded,
19    ScopeViolation,
20    /// Incomplete fragment reassembly — waiting for more fragments.
21    /// Not an error; suppresses noisy logging.
22    FragmentCollect,
23    /// Data packet failed signature/chain validation.
24    ValidationFailed,
25    /// Certificate fetch timed out during validation.
26    ValidationTimeout,
27    Other,
28}
29
30/// The return value from a pipeline stage.
31///
32/// Ownership-based: `Continue` returns the context back to the runner.
33/// All other variants consume the context, making it a compiler error to
34/// use the context after it has been handed off.
35pub enum Action {
36    /// Pass context to the next stage.
37    Continue(super::context::PacketContext),
38    /// Forward the packet to the given faces and exit the pipeline.
39    Send(super::context::PacketContext, SmallVec<[FaceId; 4]>),
40    /// Satisfy pending PIT entries and exit the pipeline.
41    Satisfy(super::context::PacketContext),
42    /// Drop the packet silently.
43    Drop(DropReason),
44    /// Send a Nack back to the incoming face.
45    Nack(super::context::PacketContext, NackReason),
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::pipeline::context::PacketContext;
52    use bytes::Bytes;
53    use ndn_transport::FaceId;
54    use smallvec::smallvec;
55
56    #[test]
57    fn drop_reason_variants_are_distinct() {
58        let reasons = [
59            DropReason::MalformedPacket,
60            DropReason::UnknownFace,
61            DropReason::LoopDetected,
62            DropReason::Suppressed,
63            DropReason::RateLimited,
64            DropReason::HopLimitExceeded,
65            DropReason::ScopeViolation,
66            DropReason::FragmentCollect,
67            DropReason::ValidationFailed,
68            DropReason::ValidationTimeout,
69            DropReason::Other,
70        ];
71        for (i, a) in reasons.iter().enumerate() {
72            for (j, b) in reasons.iter().enumerate() {
73                assert_eq!(i == j, a == b);
74            }
75        }
76    }
77
78    #[test]
79    fn nack_reason_variants_are_distinct() {
80        let reasons = [
81            NackReason::NoRoute,
82            NackReason::Duplicate,
83            NackReason::Congestion,
84            NackReason::NotYet,
85        ];
86        for (i, a) in reasons.iter().enumerate() {
87            for (j, b) in reasons.iter().enumerate() {
88                assert_eq!(i == j, a == b);
89            }
90        }
91    }
92
93    fn ctx() -> PacketContext {
94        PacketContext::new(Bytes::from_static(b"\x05\x01\x00"), FaceId(0), 0)
95    }
96
97    #[test]
98    fn action_continue_wraps_context() {
99        let a = Action::Continue(ctx());
100        assert!(matches!(a, Action::Continue(_)));
101    }
102
103    #[test]
104    fn action_drop_holds_reason() {
105        let a = Action::Drop(DropReason::LoopDetected);
106        assert!(matches!(a, Action::Drop(DropReason::LoopDetected)));
107    }
108
109    #[test]
110    fn action_nack_holds_reason() {
111        let a = Action::Nack(ctx(), NackReason::NoRoute);
112        assert!(matches!(a, Action::Nack(_, NackReason::NoRoute)));
113    }
114
115    #[test]
116    fn action_send_holds_faces() {
117        let faces: SmallVec<[FaceId; 4]> = smallvec![FaceId(1), FaceId(2)];
118        let a = Action::Send(ctx(), faces);
119        if let Action::Send(_, f) = a {
120            assert_eq!(f.len(), 2);
121        } else {
122            panic!("expected Send");
123        }
124    }
125
126    #[test]
127    fn forwarding_action_suppress() {
128        assert!(matches!(
129            ForwardingAction::Suppress,
130            ForwardingAction::Suppress
131        ));
132    }
133
134    #[test]
135    fn forwarding_action_forward_after() {
136        let delay = std::time::Duration::from_millis(10);
137        let a = ForwardingAction::ForwardAfter {
138            faces: smallvec![FaceId(3)],
139            delay,
140        };
141        if let ForwardingAction::ForwardAfter { faces, delay: d } = a {
142            assert_eq!(faces.len(), 1);
143            assert_eq!(d.as_millis(), 10);
144        } else {
145            panic!("expected ForwardAfter");
146        }
147    }
148}