1use crate::ifaddr::InterfaceAddressId;
2use crate::interface::InterfaceId;
3use crate::neighbor::NeighborId;
4use crate::route::RouteId;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum ChangeKind {
10 Added,
11 Removed,
12 Changed,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[non_exhaustive]
22pub enum EventDomain {
23 Route,
24 Interface,
25 Neighbor,
26 Address,
27 All,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub struct EventFilter {
33 routes: bool,
34 interfaces: bool,
35 neighbors: bool,
36 addresses: bool,
37 route_ids: Option<Vec<RouteId>>,
38 interface_ids: Option<Vec<InterfaceId>>,
39 neighbor_ids: Option<Vec<NeighborId>>,
40 address_ids: Option<Vec<InterfaceAddressId>>,
41}
42
43impl EventFilter {
44 pub const ALL: Self = Self {
45 routes: true,
46 interfaces: true,
47 neighbors: true,
48 addresses: true,
49 route_ids: None,
50 interface_ids: None,
51 neighbor_ids: None,
52 address_ids: None,
53 };
54 pub const fn none() -> Self {
55 Self {
56 routes: false,
57 interfaces: false,
58 neighbors: false,
59 addresses: false,
60 route_ids: None,
61 interface_ids: None,
62 neighbor_ids: None,
63 address_ids: None,
64 }
65 }
66 pub const fn routes(mut self) -> Self {
67 self.routes = true;
68 self
69 }
70 pub const fn interfaces(mut self) -> Self {
71 self.interfaces = true;
72 self
73 }
74 pub const fn neighbors(mut self) -> Self {
75 self.neighbors = true;
76 self
77 }
78 pub const fn addresses(mut self) -> Self {
79 self.addresses = true;
80 self
81 }
82
83 pub fn route(mut self, id: RouteId) -> Self {
86 self.routes = true;
87 add_selector(&mut self.route_ids, id);
88 self
89 }
90 pub fn interface(mut self, id: InterfaceId) -> Self {
92 self.interfaces = true;
93 add_selector(&mut self.interface_ids, id);
94 self
95 }
96 pub fn neighbor(mut self, id: NeighborId) -> Self {
98 self.neighbors = true;
99 add_selector(&mut self.neighbor_ids, id);
100 self
101 }
102 pub fn address(mut self, id: InterfaceAddressId) -> Self {
104 self.addresses = true;
105 add_selector(&mut self.address_ids, id);
106 self
107 }
108
109 pub const fn is_empty(&self) -> bool {
111 !self.routes && !self.interfaces && !self.neighbors && !self.addresses
112 }
113
114 pub fn matches(&self, event: Event) -> bool {
118 match event {
119 Event::Route { id, .. } => self.routes && selected(&self.route_ids, id),
120 Event::Interface { id, .. } => self.interfaces && selected(&self.interface_ids, id),
121 Event::Neighbor { id, .. } => self.neighbors && selected(&self.neighbor_ids, id),
122 Event::Address { id, .. } => self.addresses && selected(&self.address_ids, id),
123 Event::ResyncRequired { domain } => match domain {
124 EventDomain::Route => self.routes,
125 EventDomain::Interface => self.interfaces,
126 EventDomain::Neighbor => self.neighbors,
127 EventDomain::Address => self.addresses,
128 EventDomain::All => {
129 self.routes || self.interfaces || self.neighbors || self.addresses
130 }
131 },
132 }
133 }
134}
135
136fn add_selector<T: PartialEq>(selectors: &mut Option<Vec<T>>, value: T) {
137 let selectors = selectors.get_or_insert_with(Vec::new);
138 if !selectors.contains(&value) {
139 selectors.push(value);
140 }
141}
142
143fn selected<T: PartialEq>(selectors: &Option<Vec<T>>, value: T) -> bool {
144 selectors
145 .as_ref()
146 .is_none_or(|selectors| selectors.contains(&value))
147}
148impl Default for EventFilter {
149 fn default() -> Self {
150 Self::ALL
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
178#[non_exhaustive]
179pub enum Event {
180 Route {
181 id: RouteId,
182 kind: ChangeKind,
183 },
184 Interface {
185 id: InterfaceId,
186 kind: ChangeKind,
187 },
188 Neighbor {
189 id: NeighborId,
190 kind: ChangeKind,
191 },
192 Address {
193 id: InterfaceAddressId,
194 kind: ChangeKind,
195 },
196 ResyncRequired {
199 domain: EventDomain,
200 },
201}
202impl Event {
203 pub const fn resync_all() -> Self {
204 Self::ResyncRequired {
205 domain: EventDomain::All,
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn events_carry_id_and_kind() {
216 let event = Event::Route {
217 id: RouteId::new(1),
218 kind: ChangeKind::Added,
219 };
220 assert_eq!(
221 event,
222 Event::Route {
223 id: RouteId::new(1),
224 kind: ChangeKind::Added,
225 }
226 );
227 assert_ne!(
228 event,
229 Event::Route {
230 id: RouteId::new(1),
231 kind: ChangeKind::Removed,
232 }
233 );
234 }
235
236 #[test]
237 fn object_selectors_compose_without_leaking_other_objects() {
238 let filter = EventFilter::none()
239 .route(RouteId::new(1))
240 .route(RouteId::new(2));
241 assert!(filter.matches(Event::Route {
242 id: RouteId::new(1),
243 kind: ChangeKind::Changed,
244 }));
245 assert!(filter.matches(Event::Route {
246 id: RouteId::new(2),
247 kind: ChangeKind::Changed,
248 }));
249 assert!(!filter.matches(Event::Route {
250 id: RouteId::new(3),
251 kind: ChangeKind::Changed,
252 }));
253 }
254
255 #[test]
256 fn resync_is_selected_by_domain_even_for_object_filter() {
257 let filter = EventFilter::none().route(RouteId::new(1));
258 assert!(filter.matches(Event::ResyncRequired {
259 domain: EventDomain::Route,
260 }));
261 assert!(!filter.matches(Event::ResyncRequired {
262 domain: EventDomain::Address,
263 }));
264 }
265
266 #[test]
267 fn domain_builders_default_and_empty_state_cover_every_domain() {
268 let empty = EventFilter::none();
269 assert!(empty.is_empty());
270 assert!(!empty.matches(Event::Route {
271 id: RouteId::new(1),
272 kind: ChangeKind::Added,
273 }));
274
275 let domains = EventFilter::none()
276 .routes()
277 .interfaces()
278 .neighbors()
279 .addresses();
280 assert!(!domains.is_empty());
281 assert!(domains.matches(Event::Route {
282 id: RouteId::new(1),
283 kind: ChangeKind::Added,
284 }));
285 assert!(domains.matches(Event::Interface {
286 id: InterfaceId::new(1),
287 kind: ChangeKind::Removed,
288 }));
289 assert!(domains.matches(Event::Neighbor {
290 id: NeighborId::new(1),
291 kind: ChangeKind::Changed,
292 }));
293 assert!(domains.matches(Event::Address {
294 id: InterfaceAddressId::new(1),
295 kind: ChangeKind::Added,
296 }));
297 assert_eq!(EventFilter::default(), EventFilter::ALL);
298 }
299
300 #[test]
301 fn object_selectors_cover_every_domain_and_deduplicate() {
302 let filter = EventFilter::none()
303 .route(RouteId::new(1))
304 .route(RouteId::new(1))
305 .interface(InterfaceId::new(2))
306 .neighbor(NeighborId::new(3))
307 .address(InterfaceAddressId::new(4));
308
309 assert!(filter.matches(Event::Route {
310 id: RouteId::new(1),
311 kind: ChangeKind::Added,
312 }));
313 assert!(!filter.matches(Event::Route {
314 id: RouteId::new(2),
315 kind: ChangeKind::Added,
316 }));
317 assert!(filter.matches(Event::Interface {
318 id: InterfaceId::new(2),
319 kind: ChangeKind::Added,
320 }));
321 assert!(!filter.matches(Event::Interface {
322 id: InterfaceId::new(3),
323 kind: ChangeKind::Added,
324 }));
325 assert!(filter.matches(Event::Neighbor {
326 id: NeighborId::new(3),
327 kind: ChangeKind::Added,
328 }));
329 assert!(!filter.matches(Event::Neighbor {
330 id: NeighborId::new(4),
331 kind: ChangeKind::Added,
332 }));
333 assert!(filter.matches(Event::Address {
334 id: InterfaceAddressId::new(4),
335 kind: ChangeKind::Added,
336 }));
337 assert!(!filter.matches(Event::Address {
338 id: InterfaceAddressId::new(5),
339 kind: ChangeKind::Added,
340 }));
341 }
342
343 #[test]
344 fn resync_all_is_selected_only_when_a_domain_is_selected() {
345 assert_eq!(
346 Event::resync_all(),
347 Event::ResyncRequired {
348 domain: EventDomain::All
349 }
350 );
351 assert!(!EventFilter::none().matches(Event::resync_all()));
352 assert!(EventFilter::none().routes().matches(Event::resync_all()));
353 }
354
355 #[test]
356 fn resync_domain_matching_covers_interface_and_neighbor() {
357 assert!(
358 EventFilter::none()
359 .interfaces()
360 .matches(Event::ResyncRequired {
361 domain: EventDomain::Interface,
362 })
363 );
364 assert!(
365 EventFilter::none()
366 .neighbors()
367 .matches(Event::ResyncRequired {
368 domain: EventDomain::Neighbor,
369 })
370 );
371 }
372}