ndn_pipeline/
action.rs

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