Skip to main content

ts_bart/iptrie/
modify.rs

1use crate::{
2    BaseIndex,
3    iptrie::{insert::insert_inner, remove, remove::RemovalInfo, util},
4    node::{Child, StrideOps},
5};
6
7/// A set of actions that can be taken as part of a call to [`modify`].
8#[derive(Debug, Default)]
9pub enum RouteModification<T> {
10    /// Insert the contained value into the route.
11    Insert(T),
12
13    /// Remove the route.
14    Remove,
15
16    /// Take no further action on the route. (May modify the route value
17    /// in-place.)
18    #[default]
19    Noop,
20}
21
22/// Modify the route at `prefix` using the `modify` closure.
23///
24/// The closure may return:
25///
26/// - [`RouteModification::Insert`]: a route will be inserted with the provided
27///   value (or replaced if it already exists).
28/// - [`RouteModification::Remove`]: the route will be removed (if it exists).
29/// - [`RouteModification::Noop`]: the table will not be modified. The closure
30///   may modify the value stored in the route (if present).
31///
32/// If the route was replaced or removed, the return value contains the
33/// value that was previously stored.
34///
35/// Prefer to use [`insert`][crate::iptrie::insert] or
36/// [`remove`][crate::iptrie::remove] if  possible; this function is intended as
37/// a more-efficient alternative to
38/// [`lookup_prefix_exact`][crate::iptrie::lookup_prefix_exact]-then-mutate.
39pub fn modify<N>(
40    node: &mut N,
41    prefix: ipnet::IpNet,
42    f: impl FnOnce(Option<&mut N::T>) -> RouteModification<N::T>,
43) -> Option<N::T>
44where
45    N: StrideOps,
46{
47    let prefix = prefix.trunc();
48    let addr = prefix.addr();
49
50    let octets = util::ip_octets(&addr);
51    let (ret, removal) = modify_inner(node, octets, &prefix, f);
52
53    if let Some(removal) = removal {
54        remove::compress_removal_path(node, removal);
55    }
56
57    ret
58}
59
60pub fn modify_inner<N>(
61    mut node: &mut N,
62    octets: &[u8],
63    prefix: &ipnet::IpNet,
64    f: impl FnOnce(Option<&mut N::T>) -> RouteModification<N::T>,
65) -> (Option<N::T>, Option<RemovalInfo<N::T>>)
66where
67    N: StrideOps,
68{
69    let mut compression_root_depth: usize = 0;
70    let (stride_count, overflow_bits) = util::stride_count_and_overflow(prefix);
71
72    for (depth, octet) in octets.iter().copied().enumerate() {
73        // Last full octet from the prefix, entry is in current node's prefix
74        // table if it exists
75        if depth == stride_count {
76            let idx = BaseIndex::from_prefix(octet, overflow_bits);
77
78            let val = node.get_prefix_exact_mut(idx);
79            return match f(val) {
80                // delete
81                RouteModification::Remove => {
82                    let ret = node.remove_prefix(idx);
83                    let removal_info = remove::try_remove_last_child(
84                        node,
85                        octets,
86                        depth,
87                        prefix.addr().is_ipv4(),
88                        compression_root_depth,
89                    );
90
91                    (ret, removal_info)
92                }
93                // insert
94                RouteModification::Insert(t) => {
95                    let ret = node.insert_prefix(idx, t);
96                    (ret, None)
97                }
98                // no-op or modify-in-place
99                RouteModification::Noop => (None, None),
100            };
101        }
102
103        let Some(child) = node.get_child_mut(octet) else {
104            // No matching child: call `f` to decide if we should insert
105            return match f(None) {
106                RouteModification::Remove | RouteModification::Noop => (None, None),
107                RouteModification::Insert(t) => {
108                    if util::is_fringe(depth, prefix) {
109                        node.insert_child(octet, Child::Fringe(t));
110                    } else {
111                        node.insert_child(
112                            octet,
113                            Child::Leaf {
114                                value: t,
115                                prefix: *prefix,
116                            },
117                        );
118                    };
119
120                    (None, None)
121                }
122            };
123        };
124
125        match child {
126            // have a leaf at this address, but it doesn't match our prefix: our node doesn't exist.
127            // check what `f` wants to do and punt to insert_inner if we need to create.
128            Child::Leaf {
129                prefix: leaf_prefix,
130                ..
131            } if leaf_prefix != *prefix => {
132                return match f(None) {
133                    RouteModification::Noop | RouteModification::Remove => (None, None),
134                    RouteModification::Insert(t) => (insert_inner(node, *prefix, t, depth), None),
135                };
136            }
137            // have a fringe at this address, but it doesn't match our prefix: our node doesn't
138            // exist. check what `f` wants to do and punt to insert_inner if we need to
139            // create.
140            Child::Fringe(..) if !util::is_fringe(depth, prefix) => {
141                return match f(None) {
142                    RouteModification::Noop | RouteModification::Remove => (None, None),
143                    RouteModification::Insert(t) => (insert_inner(node, *prefix, t, depth), None),
144                };
145            }
146
147            // this is the fringe or leaf node that we're supposed to put our value in, if we have
148            // one
149            Child::Fringe(value) | Child::Leaf { value, .. } => {
150                return match f(Some(value)) {
151                    RouteModification::Noop => (None, None),
152                    RouteModification::Insert(t) => {
153                        let insert_val = if util::is_fringe(depth, prefix) {
154                            node.insert_child(octet, Child::Fringe(t))
155                        } else {
156                            node.insert_child(
157                                octet,
158                                Child::Leaf {
159                                    value: t,
160                                    prefix: *prefix,
161                                },
162                            )
163                        };
164
165                        (insert_val.unwrap().into_value(), None)
166                    }
167                    RouteModification::Remove => {
168                        let ret = node.remove_child(octet).unwrap().into_value();
169                        let removal_info = remove::try_remove_last_child(
170                            node,
171                            octets,
172                            depth,
173                            prefix.addr().is_ipv4(),
174                            compression_root_depth,
175                        );
176
177                        (ret, removal_info)
178                    }
179                };
180            }
181            Child::Path(..) => {}
182        }
183
184        // The new current node can't be deleted even if one of its children is, and is
185        // therefore a new candidate compression root.
186        let is_deletable = node.child_count() == 1 && node.prefix_count() == 0;
187        if !is_deletable {
188            compression_root_depth = depth;
189        }
190
191        // the unwraps are safe because we know that the child must be full
192        let root = node.get_child_mut(octet).unwrap();
193        node = root.into_node().unwrap();
194    }
195
196    (None, None)
197}
198
199#[cfg(test)]
200mod test {
201    use super::*;
202    use crate::DefaultNode;
203
204    fn test_insert<T>(
205        node: &mut DefaultNode<T>,
206        val: T,
207        pfx: &str,
208        want_pfxs: usize,
209        want_leaves: usize,
210        want_fringes: usize,
211    ) where
212        T: Copy + PartialEq + core::fmt::Debug + 'static,
213    {
214        assert_eq!(
215            None,
216            modify(node, crate::pfx!(pfx), |entry| {
217                assert_eq!(None, entry);
218                RouteModification::Insert(val)
219            })
220        );
221        assert_eq!(
222            Some(val),
223            modify(node, crate::pfx!(pfx), |entry| {
224                assert_eq!(Some(val), entry.copied());
225                RouteModification::Insert(val)
226            })
227        );
228
229        let stats = node.stats();
230        assert_eq!(
231            (want_pfxs, want_leaves, want_fringes),
232            (stats.prefix_count, stats.leaf_count, stats.fringe_count),
233            "{node:#?}",
234        );
235    }
236
237    fn test_remove<T>(
238        node: &mut DefaultNode<T>,
239        expect: T,
240        pfx: &str,
241        want_pfxs: usize,
242        want_leaves: usize,
243        want_fringes: usize,
244    ) where
245        T: Copy + PartialEq + core::fmt::Debug + 'static,
246    {
247        assert_eq!(
248            Some(expect),
249            modify(node, crate::pfx!(pfx), |entry| {
250                assert_eq!(Some(expect), entry.copied());
251                RouteModification::Remove
252            })
253        );
254        assert_eq!(
255            None,
256            modify(node, crate::pfx!(pfx), |entry| {
257                assert_eq!(None, entry.copied());
258                RouteModification::Remove
259            })
260        );
261
262        let stats = node.stats();
263        assert_eq!(
264            (want_pfxs, want_leaves, want_fringes),
265            (stats.prefix_count, stats.leaf_count, stats.fringe_count),
266            "{node:#?}",
267        );
268    }
269
270    #[test]
271    fn basic() {
272        let node = &mut DefaultNode::EMPTY.clone();
273        assert!(node.is_empty());
274
275        test_insert(node, 3, "0.0.0.0/0", 1, 0, 0);
276        test_insert(node, 4, "0.0.0.0/1", 2, 0, 0);
277        test_insert(node, 5, "0.0.0.0/8", 2, 0, 1);
278        test_insert(node, 6, "1.0.32.0/23", 2, 1, 1);
279        test_insert(node, 7, "2.0.0.0/8", 2, 1, 2);
280
281        modify(node, crate::pfx!("2.0.0.0/8"), |val| {
282            assert_eq!(Some(&mut 7), val);
283            *val.unwrap() = 9;
284            RouteModification::Noop
285        });
286
287        test_remove(node, 9, "2.0.0.0/8", 2, 1, 1);
288        test_remove(node, 3, "0.0.0.0/0", 1, 1, 1);
289        test_remove(node, 5, "0.0.0.0/8", 1, 1, 0);
290        test_remove(node, 6, "1.0.32.0/23", 1, 0, 0);
291        test_remove(node, 4, "0.0.0.0/1", 0, 0, 0);
292
293        assert!(node.is_empty());
294    }
295
296    #[test]
297    fn nonmatching_fringe_exists() {
298        let node = &mut DefaultNode::EMPTY.clone();
299
300        test_insert(node, 3, "0.0.0.0/8", 0, 0, 1);
301        test_insert(node, 4, "0.1.2.0/22", 1, 1, 0);
302    }
303
304    #[test]
305    fn nonmatching_leaf_exists() {
306        let node = &mut DefaultNode::EMPTY.clone();
307
308        test_insert(node, 3, "0.2.0.0/15", 0, 1, 0);
309        test_insert(node, 4, "0.1.2.0/22", 1, 1, 0);
310    }
311}