p4rs/
table.rs

1// Copyright 2022 Oxide Computer Company
2
3use std::collections::HashSet;
4use std::fmt::Write;
5use std::net::IpAddr;
6
7use num::bigint::BigUint;
8use num::ToPrimitive;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
12pub struct BigUintKey {
13    pub value: BigUint,
14    pub width: usize,
15}
16
17// TODO transition from BigUint to BitVec<u8, Msb0>, this requires being able to
18// do a number of mathematical operations on BitVec<u8, Msb0>.
19#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
20pub enum Key {
21    Exact(BigUintKey),
22    Range(BigUintKey, BigUintKey),
23    Ternary(Ternary),
24    Lpm(Prefix),
25}
26
27impl Default for Key {
28    fn default() -> Self {
29        Self::Ternary(Ternary::default())
30    }
31}
32
33impl Key {
34    pub fn to_bytes(&self) -> Vec<u8> {
35        match self {
36            Key::Exact(x) => {
37                let mut buf = x.value.to_bytes_le();
38
39                // A value serialized from a BigUint may be less than the width of a
40                // field. For example a 16-bit field with with a value of 47 will come
41                // back in 8 bits from BigUint serialization.
42                buf.resize(x.width, 0);
43                buf
44            }
45            Key::Range(a, z) => {
46                let mut buf_a = a.value.to_bytes_le();
47                let mut buf_z = z.value.to_bytes_le();
48
49                buf_a.resize(a.width, 0);
50                buf_z.resize(z.width, 0);
51                buf_a.extend_from_slice(&buf_z);
52                buf_a
53            }
54            Key::Ternary(t) => match t {
55                Ternary::DontCare => {
56                    let mut buf = Vec::new();
57                    buf.clear();
58                    buf
59                }
60                Ternary::Value(v) => {
61                    let mut buf = v.value.to_bytes_le();
62                    buf.resize(v.width, 0);
63                    buf
64                }
65                Ternary::Masked(v, m, w) => {
66                    let mut buf_a = v.to_bytes_le();
67                    let mut buf_b = m.to_bytes_le();
68                    buf_a.resize(*w, 0);
69                    buf_b.resize(*w, 0);
70                    buf_a.extend_from_slice(&buf_b);
71                    buf_a
72                }
73            },
74            Key::Lpm(p) => {
75                let mut v: Vec<u8> = match p.addr {
76                    IpAddr::V4(a) => a.octets().into(),
77                    IpAddr::V6(a) => a.octets().into(),
78                };
79                v.push(p.len);
80                v
81            }
82        }
83    }
84}
85
86#[derive(
87    Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize, Default,
88)]
89pub enum Ternary {
90    #[default]
91    DontCare,
92    Value(BigUintKey),
93    Masked(BigUint, BigUint, usize),
94}
95
96#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
97pub struct Prefix {
98    pub addr: IpAddr,
99    pub len: u8,
100}
101
102pub struct Table<const D: usize, A: Clone> {
103    pub entries: HashSet<TableEntry<D, A>>,
104}
105
106impl<const D: usize, A: Clone> Default for Table<D, A> {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl<const D: usize, A: Clone> Table<D, A> {
113    pub fn new() -> Self {
114        Self {
115            entries: HashSet::new(),
116        }
117    }
118
119    pub fn match_selector(
120        &self,
121        keyset: &[BigUint; D],
122    ) -> Vec<TableEntry<D, A>> {
123        let mut result = Vec::new();
124        for entry in &self.entries {
125            if keyset_matches(keyset, &entry.key) {
126                result.push(entry.clone());
127            }
128        }
129        sort_entries(result)
130    }
131
132    pub fn dump(&self) -> String {
133        let mut s = String::new();
134        for e in &self.entries {
135            writeln!(s, "{:?}", e.key).unwrap();
136        }
137        s
138    }
139}
140
141// Sort a vector of match result entries. Do not apply this sort function to
142// general vectors of entries, it only works for matching results. Specifically
143// the entries are pruned to longest prefix matches.
144pub fn sort_entries<const D: usize, A: Clone>(
145    mut entries: Vec<TableEntry<D, A>>,
146) -> Vec<TableEntry<D, A>> {
147    if entries.is_empty() {
148        return entries;
149    }
150
151    // First determine how to sort. All the entries have the same keyset
152    // structure, so it's sufficient to iterate over the keys of the first
153    // entry. The basic logic we follow here is
154    //
155    // - If we find an lpm key sort on the prefix lenght of that dimension.
156    // - Otherwise simply sort on priority.
157    //
158    // Notes:
159    //
160    // It's assumed that multiple lpm keys do not exist in a single keyset.
161    // This is an implicit assumption in BVM2
162    //
163    //      https://github.com/p4lang/behavioral-model/issues/698
164    //
165    for (i, k) in entries[0].key.iter().enumerate() {
166        match k {
167            Key::Lpm(_) => {
168                let mut entries = prune_entries_by_lpm(i, &entries);
169                sort_entries_by_priority(&mut entries);
170                return entries;
171            }
172            _ => continue,
173        }
174    }
175
176    sort_entries_by_priority(&mut entries);
177    entries
178}
179
180// TODO - the data structures here are quite dumb. The point of this table
181// implemntation is not efficiency, it's a simple bruteish apprach that is easy
182// to move around until we nail down the, tbh, rather undefined (in terms of p4
183// spec)semantics of what the relative priorities between match types in a
184// common keyset are.
185pub fn prune_entries_by_lpm<const D: usize, A: Clone>(
186    d: usize,
187    entries: &Vec<TableEntry<D, A>>,
188) -> Vec<TableEntry<D, A>> {
189    let mut longest_prefix = 0u8;
190
191    for e in entries {
192        if let Key::Lpm(x) = &e.key[d] {
193            if x.len > longest_prefix {
194                longest_prefix = x.len
195            }
196        }
197    }
198
199    let mut result = Vec::new();
200    for e in entries {
201        if let Key::Lpm(x) = &e.key[d] {
202            if x.len == longest_prefix {
203                result.push(e.clone())
204            }
205        }
206    }
207
208    result
209}
210
211pub fn sort_entries_by_priority<const D: usize, A: Clone>(
212    entries: &mut [TableEntry<D, A>],
213) {
214    entries
215        .sort_by(|a, b| -> std::cmp::Ordering { b.priority.cmp(&a.priority) });
216}
217
218pub fn key_matches(selector: &BigUint, key: &Key) -> bool {
219    match key {
220        Key::Exact(x) => {
221            let hit = selector == &x.value;
222            if !hit {
223                let dump = format!("{:x} != {:x}", selector, x.value);
224                crate::p4rs_provider::match_miss!(|| &dump);
225            }
226            hit
227        }
228        Key::Range(begin, end) => {
229            let hit = selector >= &begin.value && selector <= &end.value;
230            if !hit {
231                let dump = format!(
232                    "begin={} end={} sel={}",
233                    begin.value, end.value, selector
234                );
235                crate::p4rs_provider::match_miss!(|| &dump);
236            }
237            hit
238        }
239        Key::Ternary(t) => match t {
240            Ternary::DontCare => true,
241            Ternary::Value(x) => selector == &x.value,
242            Ternary::Masked(x, m, _) => selector & m == x & m,
243        },
244        Key::Lpm(p) => match p.addr {
245            IpAddr::V6(addr) => {
246                assert!(p.len <= 128);
247                let key: u128 = addr.into();
248                let mask = if p.len == 128 {
249                    u128::MAX
250                } else if p.len == 0 {
251                    0u128
252                } else {
253                    ((1u128 << p.len) - 1) << (128 - p.len)
254                };
255                //let mask = mask.to_be();
256                let selector_v6 = selector.to_u128().unwrap();
257                let hit = selector_v6 & mask == key & mask;
258                if !hit {
259                    let dump = format!(
260                        "{:x} & {:x} == {:x} & {:x} | {:x} == {:x}",
261                        selector_v6,
262                        mask,
263                        key,
264                        mask,
265                        selector_v6 & mask,
266                        key & mask
267                    );
268                    //println!("{}", dump);
269                    crate::p4rs_provider::match_miss!(|| &dump);
270                }
271                hit
272            }
273            IpAddr::V4(addr) => {
274                assert!(p.len <= 32);
275                let key: u32 = addr.into();
276                let mask = if p.len == 32 {
277                    u32::MAX
278                } else if p.len == 0 {
279                    0
280                } else {
281                    ((1u32 << p.len) - 1) << (32 - p.len)
282                };
283                let selector_v4: u32 = selector.to_u32().unwrap();
284                let hit = selector_v4 & mask == key & mask;
285                if !hit {
286                    let dump = format!(
287                        "{:x} & {:x} == {:x} & {:x} | {:x} = {:x}",
288                        selector_v4,
289                        mask,
290                        key,
291                        mask,
292                        selector_v4 & mask,
293                        key & mask
294                    );
295                    crate::p4rs_provider::match_miss!(|| &dump);
296                }
297                hit
298            }
299        },
300    }
301}
302
303pub fn keyset_matches<const D: usize>(
304    selector: &[BigUint; D],
305    key: &[Key; D],
306) -> bool {
307    for i in 0..D {
308        if !key_matches(&selector[i], &key[i]) {
309            return false;
310        }
311    }
312    true
313}
314
315#[derive(Clone)]
316pub struct TableEntry<const D: usize, A: Clone> {
317    pub key: [Key; D],
318    pub action: A,
319    pub priority: u32,
320    pub name: String,
321
322    // the following are not used operationally, strictly for observability as
323    // the closure contained in `A` is hard to get at.
324    pub action_id: String,
325    pub parameter_data: Vec<u8>,
326}
327
328// TODO: Cannot hash on just the key, this does not work for multipath.
329impl<const D: usize, A: Clone> std::hash::Hash for TableEntry<D, A> {
330    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
331        self.key.hash(state);
332    }
333}
334
335impl<const D: usize, A: Clone> std::cmp::PartialEq for TableEntry<D, A> {
336    fn eq(&self, other: &Self) -> bool {
337        self.key == other.key
338    }
339}
340
341impl<const D: usize, A: Clone> std::fmt::Debug for TableEntry<D, A> {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        f.debug_struct(&format!("TableEntry<{}>", D))
344            .field("key", &self.key)
345            .field("priority", &self.priority)
346            .field("name", &self.name)
347            .finish()
348    }
349}
350
351impl<const D: usize, A: Clone> std::cmp::Eq for TableEntry<D, A> {}
352
353#[cfg(test)]
354mod tests {
355
356    use super::*;
357    use std::net::Ipv6Addr;
358    use std::sync::Arc;
359
360    fn contains_entry<const D: usize, A: Clone>(
361        entries: &Vec<TableEntry<D, A>>,
362        name: &str,
363    ) -> bool {
364        for e in entries {
365            if e.name.as_str() == name {
366                return true;
367            }
368        }
369        false
370    }
371
372    fn tk(
373        name: &str,
374        addr: Ternary,
375        ingress: Ternary,
376        icmp: Ternary,
377        priority: u32,
378    ) -> TableEntry<3, ()> {
379        TableEntry::<3, ()> {
380            key: [
381                Key::Ternary(addr),
382                Key::Ternary(ingress),
383                Key::Ternary(icmp),
384            ],
385            priority,
386            name: name.into(),
387            action: (),
388            action_id: String::new(),
389            parameter_data: Vec::new(),
390        }
391    }
392
393    #[test]
394    /// A few tests on the following table.
395    ///
396    /// +--------+-------------+--------------+---------+
397    /// | Action | switch addr | ingress port | is icmp |
398    /// +--------+-------------+--------------+---------+
399    /// | a0     | true        | _            | true    |
400    /// | a1     | true        | _            | false   |
401    /// | a2     | _           | 2            | _       |
402    /// | a3     | _           | 4            | _       |
403    /// | a4     | _           | 7            | _       |
404    /// | a5     | _           | 19           | _       |
405    /// | a6     | _           | 33           | _       |
406    /// | a7     | _           | 47           | _       |
407    /// +--------+-------------+--------------+---------+
408    fn match_ternary_1() {
409        let table = Table::<3, ()> {
410            entries: HashSet::from([
411                tk(
412                    "a0",
413                    Ternary::Value(BigUintKey {
414                        value: 1u8.into(),
415                        width: 1,
416                    }),
417                    Ternary::DontCare,
418                    Ternary::Value(BigUintKey {
419                        value: 1u8.into(),
420                        width: 1,
421                    }),
422                    10,
423                ),
424                tk(
425                    "a1",
426                    Ternary::Value(BigUintKey {
427                        value: 1u8.into(),
428                        width: 1,
429                    }),
430                    Ternary::DontCare,
431                    Ternary::Value(BigUintKey {
432                        value: 0u8.into(),
433                        width: 1,
434                    }),
435                    1,
436                ),
437                tk(
438                    "a2",
439                    Ternary::DontCare,
440                    Ternary::Value(BigUintKey {
441                        value: 2u16.into(),
442                        width: 2,
443                    }),
444                    Ternary::DontCare,
445                    1,
446                ),
447                tk(
448                    "a3",
449                    Ternary::DontCare,
450                    Ternary::Value(BigUintKey {
451                        value: 4u16.into(),
452                        width: 2,
453                    }),
454                    Ternary::DontCare,
455                    1,
456                ),
457                tk(
458                    "a4",
459                    Ternary::DontCare,
460                    Ternary::Value(BigUintKey {
461                        value: 7u16.into(),
462                        width: 2,
463                    }),
464                    Ternary::DontCare,
465                    1,
466                ),
467                tk(
468                    "a5",
469                    Ternary::DontCare,
470                    Ternary::Value(BigUintKey {
471                        value: 19u16.into(),
472                        width: 2,
473                    }),
474                    Ternary::DontCare,
475                    1,
476                ),
477                tk(
478                    "a6",
479                    Ternary::DontCare,
480                    Ternary::Value(BigUintKey {
481                        value: 33u16.into(),
482                        width: 2,
483                    }),
484                    Ternary::DontCare,
485                    1,
486                ),
487                tk(
488                    "a7",
489                    Ternary::DontCare,
490                    Ternary::Value(BigUintKey {
491                        value: 47u16.into(),
492                        width: 2,
493                    }),
494                    Ternary::DontCare,
495                    1,
496                ),
497            ]),
498        };
499
500        //println!("M1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
501        let selector =
502            [BigUint::from(1u8), BigUint::from(99u16), BigUint::from(1u8)];
503        let matches = table.match_selector(&selector);
504        //println!("{:#?}", matches);
505        assert_eq!(matches.len(), 1);
506        assert!(contains_entry(&matches, "a0"));
507
508        //println!("M2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
509        let selector =
510            [BigUint::from(1u8), BigUint::from(47u16), BigUint::from(1u8)];
511        let matches = table.match_selector(&selector);
512        //println!("{:#?}", matches);
513        assert_eq!(matches.len(), 2);
514        assert!(contains_entry(&matches, "a0"));
515        assert!(contains_entry(&matches, "a7"));
516        // check priority
517        assert_eq!(matches[0].name.as_str(), "a0");
518    }
519
520    fn lpm(name: &str, addr: &str, len: u8) -> TableEntry<1, ()> {
521        let addr: IpAddr = addr.parse().unwrap();
522        TableEntry::<1, ()> {
523            key: [Key::Lpm(Prefix { addr, len })],
524            priority: 1,
525            name: name.into(),
526            action: (),
527            action_id: String::new(),
528            parameter_data: Vec::new(),
529        }
530    }
531
532    #[test]
533    /// A few tests on the following table.
534    ///
535    /// +--------+--------------------------+
536    /// | Action | Prefix                   |
537    /// +--------+--------------------------+
538    /// | a0     | fd00:4700::/24           |
539    /// | a1     | fd00:4701::/32           |
540    /// | a2     | fd00:4702::/32           |
541    /// | a3     | fd00:4701:0001::/48      |
542    /// | a4     | fd00:4701:0002::/48      |
543    /// | a5     | fd00:4702:0001::/48      |
544    /// | a6     | fd00:4702:0002::/48      |
545    /// | a7     | fd00:4701:0001:0001::/64 |
546    /// | a8     | fd00:4701:0001:0002::/64 |
547    /// | a9     | fd00:4702:0001:0001::/64 |
548    /// | a10    | fd00:4702:0001:0002::/64 |
549    /// | a11    | fd00:4701:0002:0001::/64 |
550    /// | a12    | fd00:4701:0002:0002::/64 |
551    /// | a13    | fd00:4702:0002:0001::/64 |
552    /// | a14    | fd00:4702:0002:0002::/64 |
553    /// | a15    | fd00:1701::/32           |
554    /// +--------+----------------+---------+
555    fn match_lpm_1() {
556        let mut table = Table::<1, ()>::new();
557        table.entries.insert(lpm("a0", "fd00:4700::", 24));
558        table.entries.insert(lpm("a1", "fd00:4701::", 32));
559        table.entries.insert(lpm("a2", "fd00:4702::", 32));
560        table.entries.insert(lpm("a3", "fd00:4701:0001::", 48));
561        table.entries.insert(lpm("a4", "fd00:4701:0002::", 48));
562        table.entries.insert(lpm("a5", "fd00:4702:0001::", 48));
563        table.entries.insert(lpm("a6", "fd00:4702:0002::", 48));
564        table.entries.insert(lpm("a7", "fd00:4701:0001:0001::", 64));
565        table.entries.insert(lpm("a8", "fd00:4701:0001:0002::", 64));
566        table.entries.insert(lpm("a9", "fd00:4702:0001:0001::", 64));
567        table
568            .entries
569            .insert(lpm("a10", "fd00:4702:0001:0002::", 64));
570        table
571            .entries
572            .insert(lpm("a11", "fd00:4701:0002:0001::", 64));
573        table
574            .entries
575            .insert(lpm("a12", "fd00:4701:0002:0002::", 64));
576        table
577            .entries
578            .insert(lpm("a13", "fd00:4702:0002:0001::", 64));
579        table
580            .entries
581            .insert(lpm("a14", "fd00:4702:0002:0002::", 64));
582        table.entries.insert(lpm("a15", "fd00:1701::", 32));
583
584        let addr: Ipv6Addr = "fd00:4700::1".parse().unwrap();
585        let selector = [BigUint::from(u128::from_be_bytes(addr.octets()))];
586        let matches = table.match_selector(&selector);
587        //println!("{:#?}", matches);
588        assert_eq!(matches.len(), 1);
589        assert!(contains_entry(&matches, "a0"));
590
591        let addr: Ipv6Addr = "fd00:4800::1".parse().unwrap();
592        let selector = [BigUint::from(u128::from_be_bytes(addr.octets()))];
593        let matches = table.match_selector(&selector);
594        assert_eq!(matches.len(), 0);
595        //println!("{:#?}", matches);
596
597        let addr: Ipv6Addr = "fd00:4702:0002:0002::1".parse().unwrap();
598        let selector = [BigUint::from(u128::from_be_bytes(addr.octets()))];
599        let matches = table.match_selector(&selector);
600        //println!("{:#?}", matches);
601        assert_eq!(matches.len(), 1); // only one longest prefix match
602        assert!(contains_entry(&matches, "a14"));
603        // longest prefix first
604        assert_eq!(matches[0].name.as_str(), "a14");
605    }
606
607    fn tlpm(
608        name: &str,
609        addr: &str,
610        len: u8,
611        zone: Ternary,
612        priority: u32,
613    ) -> TableEntry<2, ()> {
614        TableEntry::<2, ()> {
615            key: [
616                Key::Lpm(Prefix {
617                    addr: addr.parse().unwrap(),
618                    len,
619                }),
620                Key::Ternary(zone),
621            ],
622            priority,
623            name: name.into(),
624            action: (),
625            action_id: String::new(),
626            parameter_data: Vec::new(),
627        }
628    }
629
630    #[test]
631    fn match_lpm_ternary_1() {
632        let table = Table::<2, ()> {
633            entries: HashSet::from([
634                tlpm("a0", "fd00:1::", 64, Ternary::DontCare, 1),
635                tlpm(
636                    "a1",
637                    "fd00:1::",
638                    64,
639                    Ternary::Value(BigUintKey {
640                        value: 1u16.into(),
641                        width: 2,
642                    }),
643                    10,
644                ),
645                tlpm(
646                    "a2",
647                    "fd00:1::",
648                    64,
649                    Ternary::Value(BigUintKey {
650                        value: 2u16.into(),
651                        width: 2,
652                    }),
653                    10,
654                ),
655                tlpm(
656                    "a3",
657                    "fd00:1::",
658                    64,
659                    Ternary::Value(BigUintKey {
660                        value: 3u16.into(),
661                        width: 2,
662                    }),
663                    10,
664                ),
665            ]),
666        };
667
668        let dst: Ipv6Addr = "fd00:1::1".parse().unwrap();
669        let selector = [
670            BigUint::from(u128::from_le_bytes(dst.octets())),
671            BigUint::from(0u16),
672        ];
673        let matches = table.match_selector(&selector);
674        println!("zone-0: {:#?}", matches);
675        let selector = [
676            BigUint::from(u128::from_le_bytes(dst.octets())),
677            BigUint::from(2u16),
678        ];
679        let matches = table.match_selector(&selector);
680        println!("zone-2: {:#?}", matches);
681    }
682
683    fn lpre(
684        name: &str,
685        addr: &str,
686        len: u8,
687        zone: Ternary,
688        range: (u32, u32),
689        tag: u64,
690        priority: u32,
691    ) -> TableEntry<4, ()> {
692        TableEntry::<4, ()> {
693            key: [
694                Key::Lpm(Prefix {
695                    addr: addr.parse().unwrap(),
696                    len,
697                }),
698                Key::Ternary(zone),
699                Key::Range(
700                    BigUintKey {
701                        value: range.0.into(),
702                        width: 4,
703                    },
704                    BigUintKey {
705                        value: range.1.into(),
706                        width: 4,
707                    },
708                ),
709                Key::Exact(BigUintKey {
710                    value: tag.into(),
711                    width: 8,
712                }),
713            ],
714            priority,
715            name: name.into(),
716            action: (),
717            action_id: String::new(),
718            parameter_data: Vec::new(),
719        }
720    }
721
722    struct ActionData {
723        value: u64,
724    }
725
726    #[test]
727    fn match_lpm_ternary_range() {
728        let table = Table::<4, ()> {
729            entries: HashSet::from([
730                lpre("a0", "fd00:1::", 64, Ternary::DontCare, (80, 80), 100, 1),
731                lpre(
732                    "a1",
733                    "fd00:1::",
734                    64,
735                    Ternary::DontCare,
736                    (443, 443),
737                    100,
738                    1,
739                ),
740                lpre("a2", "fd00:1::", 64, Ternary::DontCare, (80, 80), 200, 1),
741                lpre(
742                    "a3",
743                    "fd00:1::",
744                    64,
745                    Ternary::DontCare,
746                    (443, 443),
747                    200,
748                    1,
749                ),
750                lpre(
751                    "a4",
752                    "fd00:1::",
753                    64,
754                    Ternary::Value(BigUintKey {
755                        value: 99u16.into(),
756                        width: 2,
757                    }),
758                    (443, 443),
759                    200,
760                    10,
761                ),
762            ]),
763        };
764        let dst: Ipv6Addr = "fd00:1::1".parse().unwrap();
765        let selector = [
766            BigUint::from(u128::from_le_bytes(dst.octets())),
767            BigUint::from(0u16),
768            BigUint::from(80u32),
769            BigUint::from(100u64),
770        ];
771        let matches = table.match_selector(&selector);
772        println!("m1: {:#?}", matches);
773
774        let selector = [
775            BigUint::from(u128::from_le_bytes(dst.octets())),
776            BigUint::from(0u16),
777            BigUint::from(443u32),
778            BigUint::from(200u64),
779        ];
780        let matches = table.match_selector(&selector);
781        println!("m2: {:#?}", matches);
782
783        let selector = [
784            BigUint::from(u128::from_le_bytes(dst.octets())),
785            BigUint::from(99u16),
786            BigUint::from(443u32),
787            BigUint::from(200u64),
788        ];
789        let matches = table.match_selector(&selector);
790        println!("m3: {:#?}", matches);
791
792        let selector = [
793            BigUint::from(u128::from_le_bytes(dst.octets())),
794            BigUint::from(99u16),
795            BigUint::from(80u32),
796            BigUint::from(200u64),
797        ];
798        let matches = table.match_selector(&selector);
799        println!("m4: {:#?}", matches);
800    }
801
802    #[test]
803    fn match_with_action() {
804        let mut data = ActionData { value: 47 };
805
806        let table = Table::<1, Arc<dyn Fn(&mut ActionData)>> {
807            entries: HashSet::from([
808                TableEntry::<1, Arc<dyn Fn(&mut ActionData)>> {
809                    key: [Key::Exact(BigUintKey {
810                        value: 1u8.into(),
811                        width: 1,
812                    })],
813                    priority: 0,
814                    name: "a0".into(),
815                    action: Arc::new(|a: &mut ActionData| {
816                        a.value += 10;
817                    }),
818                    action_id: String::new(),
819                    parameter_data: Vec::new(),
820                },
821                TableEntry::<1, Arc<dyn Fn(&mut ActionData)>> {
822                    key: [Key::Exact(BigUintKey {
823                        value: 2u8.into(),
824                        width: 1,
825                    })],
826                    priority: 0,
827                    name: "a1".into(),
828                    action: Arc::new(|a: &mut ActionData| {
829                        a.value -= 10;
830                    }),
831                    action_id: String::new(),
832                    parameter_data: Vec::new(),
833                },
834            ]),
835        };
836
837        let selector = [BigUint::from(1u8)];
838        let matches = table.match_selector(&selector);
839        println!("m4: {:#?}", matches);
840        assert_eq!(matches.len(), 1);
841        (matches[0].action)(&mut data);
842        assert_eq!(data.value, 57);
843    }
844}