Skip to main content

ts_bart/iptrie/
mod.rs

1//! Operations for multi-level tries of IP-addressed nodes.
2//!
3//! Implementations assume that they are operating on the root of a trie,
4//! i.e. depth 0.
5//!
6//! Strictly depends on the single-/stride-layer operations as abstracted by
7//! [`StrideOps`].
8
9use core::net::IpAddr;
10
11use crate::{
12    base_index::BaseIndex,
13    node::{Child, PrefixOpsExt, StrideOps},
14};
15
16mod insert;
17mod lookup;
18mod modify;
19mod remove;
20pub mod util;
21
22pub use insert::insert;
23pub use lookup::*;
24pub use modify::{RouteModification, modify};
25pub use remove::remove;
26
27/// The maximum depth of a path through an octet tree.
28pub const MAX_DEPTH: usize = 16;
29
30/// A path through tree octets.
31pub type StridePath = [u8; MAX_DEPTH];
32
33/// Report whether `ip` is covered by a route in the trie rooted at `node`.
34///
35/// # Examples
36///
37/// ```rust
38/// # use ts_bart::{BaseIndex, iptrie::contains, StrideOpsExt, PrefixOpsExt};
39/// // Maps 0.0.0.0/4 => 12
40/// let node = ts_bart::DefaultNode::EMPTY.with_prefix(BaseIndex::from_prefix(0, 4), 12);
41///
42/// assert!(contains(&node, "0.1.2.3".parse().unwrap()));
43/// assert!(!contains(&node, "128.1.2.3".parse().unwrap()));
44/// ```
45pub fn contains<N>(mut node: &N, ip: IpAddr) -> bool
46where
47    N: StrideOps,
48{
49    for &octet in util::ip_octets(&ip) {
50        let index = BaseIndex::from_pfx_7(octet);
51
52        if node.prefix_count() != 0 && node.supersets_prefix(index) {
53            return true;
54        }
55
56        let Some(child) = node.get_child(octet) else {
57            return false;
58        };
59
60        match child {
61            Child::Path(n) => node = n,
62            Child::Fringe(_) => return true,
63            Child::Leaf { prefix, .. } => return prefix.contains(&ip),
64        }
65    }
66
67    false
68}
69
70#[cfg(test)]
71mod test {
72    use core::str::FromStr;
73
74    use super::*;
75    use crate::{Node, test_util::unique_prefixes};
76
77    #[test]
78    fn bart_examples_insert_get_delete() {
79        let mut node = Node::<_>::EMPTY;
80
81        let p1 = ipnet::IpNet::from_str("10.0.0.0/8").unwrap();
82        let p2 = ipnet::IpNet::from_str("10.1.0.0/16").unwrap();
83        let p3 = ipnet::IpNet::from_str("2001:db8::/32").unwrap();
84
85        assert_eq!(None, insert::insert(&mut node, p1, 100usize));
86        assert_eq!(None, insert::insert(&mut node, p2, 200usize));
87        assert_eq!(None, insert::insert(&mut node, p3, 300usize));
88
89        std::println!("post-insert: {node:#?}");
90
91        assert_eq!(Some(200), lookup_prefix_exact(&node, p2).copied());
92        assert_eq!(Some(100), remove::remove(&mut node, p1));
93        assert_eq!(None, lookup_prefix_exact(&node, p1));
94
95        std::println!("post-remove: {node:#?}");
96    }
97
98    fn test_insert_delete(
99        pfxs: &[&str],
100        want_pfxs: usize,
101        want_leaves: usize,
102        want_fringes: usize,
103    ) {
104        let mut node = Node::<_>::EMPTY;
105
106        for &pfx in pfxs {
107            insert::insert(&mut node, crate::pfx!(pfx), ());
108            insert::insert(&mut node, crate::pfx!(pfx), ()); // check idempotency
109        }
110
111        let stats = node.stats();
112
113        assert_eq!(want_pfxs, stats.prefix_count);
114        assert_eq!(want_leaves, stats.leaf_count);
115        assert_eq!(want_fringes, stats.fringe_count);
116
117        for &pfx in pfxs {
118            remove::remove(&mut node, crate::pfx!(pfx));
119            remove::remove(&mut node, crate::pfx!(pfx)); // idempotency
120        }
121
122        let stats = node.stats();
123
124        assert_eq!(0, stats.prefix_count);
125        assert_eq!(0, stats.leaf_count);
126        assert_eq!(0, stats.fringe_count);
127    }
128
129    #[test]
130    fn bart_insert_delete_examples() {
131        // empty
132        test_insert_delete(&[], 0, 0, 0);
133
134        // single prefix
135        test_insert_delete(&["0.0.0.0/0"], 1, 0, 0);
136        test_insert_delete(&["::/0"], 1, 0, 0);
137
138        // single leaf
139        test_insert_delete(&["0.0.0.0/32"], 0, 1, 0);
140        test_insert_delete(&["::/32"], 0, 1, 0);
141
142        // single fringe
143        test_insert_delete(&["0.0.0.0/8"], 0, 0, 1);
144        test_insert_delete(&["::/8"], 0, 0, 1);
145
146        // many prefixes
147        test_insert_delete(
148            &["0.0.0.0/0", "0.0.0.0/1", "0.0.0.0/2", "0.0.0.0/3"],
149            4,
150            0,
151            0,
152        );
153        test_insert_delete(&["::/0", "::/1", "::/2", "::/3"], 4, 0, 0);
154
155        // many prefixes with many leaves
156        test_insert_delete(
157            &[
158                // prefixes
159                "0.0.0.0/0",
160                "0.0.0.0/1",
161                "0.0.0.0/2",
162                "0.0.0.0/3",
163                // leaves
164                "0.0.0.0/9",
165                "1.0.0.0/9",
166                "2.0.0.0/9",
167                "3.0.0.0/9",
168            ],
169            4,
170            4,
171            0,
172        );
173        test_insert_delete(
174            &[
175                "::/0", "::/1", "::/2", "::/3", // prefixes
176                "::/9", "0100::/9", "0200::/9", "0300::/9", // leaves
177            ],
178            4,
179            4,
180            0,
181        );
182
183        // many prefixes with many leaves and fringes
184        test_insert_delete(
185            &[
186                // prefixes
187                "0.0.0.0/0",
188                "0.0.0.0/1",
189                "0.0.0.0/2",
190                "0.0.0.0/3",
191                // leaves
192                "0.0.0.0/9",
193                "1.0.0.0/9",
194                "2.0.0.0/9",
195                "3.0.0.0/9",
196                // fringes
197                "5.0.0.0/8",
198                "6.0.0.0/8",
199                "7.0.0.0/8",
200                "8.0.0.0/8",
201            ],
202            4,
203            4,
204            4,
205        );
206        test_insert_delete(
207            &[
208                "::/0", "::/1", "::/2", "::/3", // prefixes
209                "::/9", "0100::/9", "0200::/9", "0300::/9", // leaves
210                "0400::/8", "0500::/8", "0600::/8", "0700::/8", // fringes
211            ],
212            4,
213            4,
214            4,
215        );
216
217        // deeper-level prefixes, leaves, fringes
218        test_insert_delete(
219            &[
220                // prefixes level 1
221                "0.0.0.0/9",
222                "0.0.0.0/10",
223                // leaf level 1
224                "0.1.0.0/19",
225                // fringes
226                "0.2.0.0/16",
227            ],
228            2,
229            1,
230            1,
231        );
232        test_insert_delete(&["::/9", "::/10", "0010::/19", "0020::/16"], 2, 1, 1);
233
234        // prefixes and fringes through level 2
235        test_insert_delete(
236            &[
237                "0.0.0.0/12", // pfx in level 1
238                "0.0.0.0/16", // fringe in level 1 -> prefix in level 2
239                "0.0.0.0/24", // fringe at level 2
240            ],
241            2,
242            0,
243            1,
244        );
245        test_insert_delete(
246            &[
247                "::/12", // pfx in level 1
248                "::/16", // fringe in level 1 -> prefix in level 2
249                "::/24", // fringe at level 2
250            ],
251            2,
252            0,
253            1,
254        );
255    }
256
257    proptest::proptest! {
258        #[test]
259        fn insert_remove(pfxs in unique_prefixes()) {
260            const PRINT: bool = false;
261            let mut node = Node::<()>::EMPTY;
262
263            for pfx in &pfxs {
264                if PRINT {
265                    std::println!("insert {pfx}");
266                }
267
268                proptest::prop_assert!(insert(&mut node, *pfx, ()).is_none());
269            }
270
271            if PRINT {
272                for (path, desc) in node.descendants(false) {
273                    std::println!("{path:?}: {desc:#?}");
274                }
275            }
276
277            let stats = node.stats();
278            proptest::prop_assert_eq!(pfxs.len(), stats.leaf_count + stats.prefix_count + stats.fringe_count);
279
280            for pfx in &pfxs {
281                if PRINT {
282                    std::println!("remove {pfx}");
283                }
284
285                proptest::prop_assert!(remove(&mut node, *pfx).is_some());
286            }
287
288            proptest::prop_assert!(node.is_empty());
289        }
290    }
291}