ndn_strategy/filters/
rssi.rs1use crate::context::StrategyContext;
2use crate::cross_layer::LinkQualitySnapshot;
3use crate::filter::StrategyFilter;
4use ndn_transport::ForwardingAction;
5use smallvec::SmallVec;
6
7pub struct RssiFilter {
15 pub min_rssi_dbm: i8,
17}
18
19impl RssiFilter {
20 pub fn new(min_rssi_dbm: i8) -> Self {
22 Self { min_rssi_dbm }
23 }
24}
25
26impl StrategyFilter for RssiFilter {
27 fn name(&self) -> &str {
28 "rssi-filter"
29 }
30
31 fn filter(
32 &self,
33 ctx: &StrategyContext,
34 actions: SmallVec<[ForwardingAction; 2]>,
35 ) -> SmallVec<[ForwardingAction; 2]> {
36 let snapshot = match ctx.extensions.get::<LinkQualitySnapshot>() {
37 Some(s) => s,
38 None => return actions, };
40
41 actions
42 .into_iter()
43 .filter_map(|action| {
44 match action {
45 ForwardingAction::Forward(faces) => {
46 let filtered: SmallVec<[_; 4]> = faces
47 .into_iter()
48 .filter(|face_id| {
49 snapshot
50 .for_face(*face_id)
51 .and_then(|lq| lq.rssi_dbm)
52 .is_none_or(|rssi| rssi >= self.min_rssi_dbm)
53 })
54 .collect();
55 if filtered.is_empty() {
56 None } else {
58 Some(ForwardingAction::Forward(filtered))
59 }
60 }
61 other => Some(other),
62 }
63 })
64 .collect()
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::MeasurementsTable;
72 use crate::cross_layer::FaceLinkQuality;
73 use ndn_packet::Name;
74 use ndn_transport::{AnyMap, FaceId};
75 use smallvec::smallvec;
76 use std::sync::Arc;
77
78 fn make_ctx_with_snapshot<'a>(
79 name: &'a Arc<Name>,
80 measurements: &'a MeasurementsTable,
81 extensions: &'a AnyMap,
82 ) -> StrategyContext<'a> {
83 StrategyContext {
84 name,
85 in_face: FaceId(0),
86 fib_entry: None,
87 pit_token: None,
88 measurements,
89 extensions,
90 }
91 }
92
93 #[test]
94 fn passes_through_when_no_snapshot() {
95 let name = Arc::new(Name::root());
96 let m = MeasurementsTable::new();
97 let ext = AnyMap::new();
98 let ctx = make_ctx_with_snapshot(&name, &m, &ext);
99
100 let filter = RssiFilter::new(-60);
101 let actions = smallvec![ForwardingAction::Forward(smallvec![FaceId(1), FaceId(2)])];
102 let result = filter.filter(&ctx, actions);
103 assert_eq!(result.len(), 1);
104 match &result[0] {
105 ForwardingAction::Forward(faces) => assert_eq!(faces.len(), 2),
106 _ => panic!("expected Forward"),
107 }
108 }
109
110 #[test]
111 fn filters_low_rssi_faces() {
112 let name = Arc::new(Name::root());
113 let m = MeasurementsTable::new();
114 let mut ext = AnyMap::new();
115 ext.insert(LinkQualitySnapshot {
116 per_face: smallvec![
117 FaceLinkQuality {
118 face_id: FaceId(1),
119 rssi_dbm: Some(-50),
120 retransmit_rate: None,
121 observed_rtt_ms: None,
122 observed_tput: None
123 },
124 FaceLinkQuality {
125 face_id: FaceId(2),
126 rssi_dbm: Some(-70),
127 retransmit_rate: None,
128 observed_rtt_ms: None,
129 observed_tput: None
130 },
131 FaceLinkQuality {
132 face_id: FaceId(3),
133 rssi_dbm: None,
134 retransmit_rate: None,
135 observed_rtt_ms: None,
136 observed_tput: None
137 },
138 ],
139 });
140 let ctx = make_ctx_with_snapshot(&name, &m, &ext);
141
142 let filter = RssiFilter::new(-60);
143 let actions = smallvec![ForwardingAction::Forward(smallvec![
144 FaceId(1),
145 FaceId(2),
146 FaceId(3)
147 ])];
148 let result = filter.filter(&ctx, actions);
149 assert_eq!(result.len(), 1);
150 match &result[0] {
151 ForwardingAction::Forward(faces) => {
152 assert_eq!(faces.as_slice(), &[FaceId(1), FaceId(3)]);
154 }
155 _ => panic!("expected Forward"),
156 }
157 }
158
159 #[test]
160 fn all_filtered_drops_forward_action() {
161 let name = Arc::new(Name::root());
162 let m = MeasurementsTable::new();
163 let mut ext = AnyMap::new();
164 ext.insert(LinkQualitySnapshot {
165 per_face: smallvec![FaceLinkQuality {
166 face_id: FaceId(1),
167 rssi_dbm: Some(-80),
168 retransmit_rate: None,
169 observed_rtt_ms: None,
170 observed_tput: None
171 },],
172 });
173 let ctx = make_ctx_with_snapshot(&name, &m, &ext);
174
175 let filter = RssiFilter::new(-60);
176 let actions = smallvec![
177 ForwardingAction::Forward(smallvec![FaceId(1)]),
178 ForwardingAction::Nack(ndn_transport::NackReason::NoRoute),
179 ];
180 let result = filter.filter(&ctx, actions);
181 assert_eq!(result.len(), 1);
183 assert!(matches!(result[0], ForwardingAction::Nack(_)));
184 }
185}