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