netstack3_base/
matchers.rs1use alloc::format;
8use alloc::string::String;
9use core::fmt::Debug;
10use core::num::NonZeroU64;
11
12use derivative::Derivative;
13use net_types::ip::{IpAddress, Subnet};
14
15use crate::{InspectableValue, Inspector};
16
17pub trait MatcherBindingsTypes {
23 type DeviceClass: Clone + Debug;
25}
26
27pub trait Matcher<T> {
31 fn matches(&self, actual: &T) -> bool;
33
34 fn required_matches(&self, actual: Option<&T>) -> bool {
36 actual.map_or(false, |actual| self.matches(actual))
37 }
38}
39
40impl<T, O> Matcher<T> for Option<O>
43where
44 O: Matcher<T>,
45{
46 fn matches(&self, actual: &T) -> bool {
47 self.as_ref().map_or(true, |expected| expected.matches(actual))
48 }
49
50 fn required_matches(&self, actual: Option<&T>) -> bool {
51 self.as_ref().map_or(true, |expected| expected.required_matches(actual))
52 }
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub struct SubnetMatcher<A: IpAddress>(pub Subnet<A>);
58
59impl<A: IpAddress> Matcher<A> for SubnetMatcher<A> {
60 fn matches(&self, actual: &A) -> bool {
61 let Self(matcher) = self;
62 matcher.contains(actual)
63 }
64}
65
66#[derive(Clone, Derivative, PartialEq, Eq)]
68#[derivative(Debug)]
69pub enum InterfaceMatcher<DeviceClass> {
70 Id(NonZeroU64),
72 Name(String),
74 DeviceClass(DeviceClass),
76}
77
78impl<DeviceClass: Debug> InspectableValue for InterfaceMatcher<DeviceClass> {
79 fn record<I: Inspector>(&self, name: &str, inspector: &mut I) {
80 match self {
81 InterfaceMatcher::Id(id) => inspector.record_string(name, format!("Id({})", id.get())),
82 InterfaceMatcher::Name(iface_name) => {
83 inspector.record_string(name, format!("Name({iface_name})"))
84 }
85 InterfaceMatcher::DeviceClass(class) => {
86 inspector.record_debug(name, format!("Class({class:?})"))
87 }
88 };
89 }
90}
91
92pub trait InterfaceProperties<DeviceClass> {
96 fn id_matches(&self, id: &NonZeroU64) -> bool;
98
99 fn name_matches(&self, name: &str) -> bool;
101
102 fn device_class_matches(&self, device_class: &DeviceClass) -> bool;
104}
105
106impl<DeviceClass, I: InterfaceProperties<DeviceClass>> Matcher<I>
107 for InterfaceMatcher<DeviceClass>
108{
109 fn matches(&self, actual: &I) -> bool {
110 match self {
111 InterfaceMatcher::Id(id) => actual.id_matches(id),
112 InterfaceMatcher::Name(name) => actual.name_matches(name),
113 InterfaceMatcher::DeviceClass(device_class) => {
114 actual.device_class_matches(device_class)
115 }
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
122pub enum BoundInterfaceMatcher<DeviceClass> {
123 Bound(InterfaceMatcher<DeviceClass>),
125 Unbound,
127}
128
129impl<'a, DeviceClass, D: InterfaceProperties<DeviceClass>> Matcher<Option<&'a D>>
130 for BoundInterfaceMatcher<DeviceClass>
131{
132 fn matches(&self, actual: &Option<&'a D>) -> bool {
133 match self {
134 BoundInterfaceMatcher::Bound(matcher) => matcher.required_matches(actual.as_deref()),
135 BoundInterfaceMatcher::Unbound => actual.is_none(),
136 }
137 }
138}
139
140impl<DeviceClass: Debug> InspectableValue for BoundInterfaceMatcher<DeviceClass> {
141 fn record<I: Inspector>(&self, name: &str, inspector: &mut I) {
142 match self {
143 BoundInterfaceMatcher::Unbound => inspector.record_str(name, "Unbound"),
144 BoundInterfaceMatcher::Bound(interface) => {
145 inspector.record_inspectable_value(name, interface)
146 }
147 }
148 }
149}
150
151#[cfg(any(test, feature = "testutils"))]
152pub(crate) mod testutil {
153 use alloc::string::String;
154 use core::num::NonZeroU64;
155
156 use crate::matchers::InterfaceProperties;
157 use crate::testutil::{FakeDeviceClass, FakeStrongDeviceId, FakeWeakDeviceId};
158 use crate::{DeviceIdentifier, StrongDeviceIdentifier};
159
160 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
162 #[allow(missing_docs)]
163 pub struct FakeMatcherDeviceId {
164 pub id: NonZeroU64,
165 pub name: String,
166 pub class: FakeDeviceClass,
167 }
168
169 #[allow(missing_docs)]
170 impl FakeMatcherDeviceId {
171 pub fn wlan_interface() -> FakeMatcherDeviceId {
172 FakeMatcherDeviceId {
173 id: NonZeroU64::new(1).unwrap(),
174 name: String::from("wlan"),
175 class: FakeDeviceClass::Wlan,
176 }
177 }
178
179 pub fn ethernet_interface() -> FakeMatcherDeviceId {
180 FakeMatcherDeviceId {
181 id: NonZeroU64::new(2).unwrap(),
182 name: String::from("eth"),
183 class: FakeDeviceClass::Ethernet,
184 }
185 }
186 }
187
188 impl StrongDeviceIdentifier for FakeMatcherDeviceId {
189 type Weak = FakeWeakDeviceId<Self>;
190
191 fn downgrade(&self) -> Self::Weak {
192 FakeWeakDeviceId(self.clone())
193 }
194 }
195
196 impl DeviceIdentifier for FakeMatcherDeviceId {
197 fn is_loopback(&self) -> bool {
198 false
199 }
200 }
201
202 impl FakeStrongDeviceId for FakeMatcherDeviceId {
203 fn is_alive(&self) -> bool {
204 true
205 }
206 }
207
208 impl PartialEq<FakeWeakDeviceId<FakeMatcherDeviceId>> for FakeMatcherDeviceId {
209 fn eq(&self, FakeWeakDeviceId(other): &FakeWeakDeviceId<FakeMatcherDeviceId>) -> bool {
210 self == other
211 }
212 }
213
214 impl InterfaceProperties<FakeDeviceClass> for FakeMatcherDeviceId {
215 fn id_matches(&self, id: &NonZeroU64) -> bool {
216 &self.id == id
217 }
218
219 fn name_matches(&self, name: &str) -> bool {
220 &self.name == name
221 }
222
223 fn device_class_matches(&self, class: &FakeDeviceClass) -> bool {
224 &self.class == class
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use alloc::format;
232
233 use ip_test_macro::ip_test;
234 use net_types::ip::Ip;
235
236 use super::*;
237 use crate::testutil::{FakeDeviceId, TestIpExt};
238
239 #[derive(Debug)]
241 struct TrueMatcher;
242
243 impl Matcher<bool> for TrueMatcher {
244 fn matches(&self, actual: &bool) -> bool {
245 *actual
246 }
247 }
248
249 #[test]
250 fn test_optional_matcher_optional_value() {
251 assert!(TrueMatcher.matches(&true));
252 assert!(!TrueMatcher.matches(&false));
253
254 assert!(TrueMatcher.required_matches(Some(&true)));
255 assert!(!TrueMatcher.required_matches(Some(&false)));
256 assert!(!TrueMatcher.required_matches(None));
257
258 assert!(Some(TrueMatcher).matches(&true));
259 assert!(!Some(TrueMatcher).matches(&false));
260 assert!(None::<TrueMatcher>.matches(&true));
261 assert!(None::<TrueMatcher>.matches(&false));
262
263 assert!(Some(TrueMatcher).required_matches(Some(&true)));
264 assert!(!Some(TrueMatcher).required_matches(Some(&false)));
265 assert!(!Some(TrueMatcher).required_matches(None));
266 assert!(None::<TrueMatcher>.required_matches(Some(&true)));
267 assert!(None::<TrueMatcher>.required_matches(Some(&false)));
268 assert!(None::<TrueMatcher>.required_matches(None));
269 }
270
271 #[test]
272 fn device_name_matcher() {
273 let device = FakeDeviceId;
274 let positive_matcher = InterfaceMatcher::Name(FakeDeviceId::FAKE_NAME.into());
275 let negative_matcher =
276 InterfaceMatcher::Name(format!("DONTMATCH-{}", FakeDeviceId::FAKE_NAME));
277 assert!(positive_matcher.matches(&device));
278 assert!(!negative_matcher.matches(&device));
279 }
280
281 #[ip_test(I)]
282 fn subnet_matcher<I: Ip + TestIpExt>() {
283 let matcher = SubnetMatcher(I::TEST_ADDRS.subnet);
284 assert!(matcher.matches(&I::TEST_ADDRS.local_ip));
285 assert!(!matcher.matches(&I::get_other_remote_ip_address(1)));
286 }
287}