Skip to main content

ts_bart/table/
mod.rs

1//! Facilities for complete routing tables.
2
3use core::net::IpAddr;
4
5mod simple;
6mod split_stack;
7
8pub use simple::SimpleTable;
9pub use split_stack::SplitStackTable;
10
11use crate::RouteModification;
12
13/// A dynamically-dispatched function that can be used to modify a table entry
14/// in-place.
15pub type DynModifyFn<'a, T> = &'a mut dyn FnMut(Option<&mut T>) -> RouteModification<T>;
16
17/// Abstracts routing table operations.
18pub trait RoutingTable {
19    /// The value stored in each route.
20    type Value: 'static;
21
22    /// Report whether `ip` is covered by a route in the table.
23    ///
24    /// # Examples
25    ///
26    /// ```rust
27    /// # use ts_bart::{Table, RoutingTable};
28    /// let mut table = Table::default();
29    /// let pfx = "1.2.3.0/24".parse().unwrap();
30    ///
31    /// table.insert(pfx, 12);
32    /// assert!(table.contains("1.2.3.4".parse().unwrap()));
33    /// assert!(!table.contains("1.2.4.4".parse().unwrap()));
34    fn contains(&self, ip: IpAddr) -> bool;
35
36    /// Insert a route into the table at `prefix`.
37    ///
38    /// # Examples
39    ///
40    /// ```rust
41    /// # use ts_bart::{Table, RoutingTable};
42    /// let mut table = Table::default();
43    /// let pfx = "0.0.0.0/0".parse().unwrap();
44    ///
45    /// assert_eq!(None, table.insert(pfx, 12));
46    /// // Table has the value now
47    /// assert_eq!(Some(&12), table.lookup_prefix_exact(pfx));
48    /// // Repeated insert returns the removed value
49    /// assert_eq!(Some(12), table.insert(pfx, 13));
50    /// ```
51    fn insert(&mut self, prefix: ipnet::IpNet, val: Self::Value) -> Option<Self::Value>;
52
53    /// Remove the route from the table with the given `prefix`.
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// # use ts_bart::{Table, RoutingTable};
59    /// let mut table = Table::default();
60    /// let pfx = "0.0.0.0/0".parse().unwrap();
61    ///
62    /// // remove without a route returns `None`
63    /// assert_eq!(None, table.remove(pfx));
64    ///
65    /// // insert then remove returns the value that was inserted
66    /// assert_eq!(None, table.insert(pfx, 12));
67    /// assert_eq!(Some(12), table.remove(pfx));
68    /// ```
69    fn remove(&mut self, prefix: ipnet::IpNet) -> Option<Self::Value>;
70
71    /// Wipe the whole table.
72    fn clear(&mut self);
73
74    // This non-obvious API is designed this way to make this trait object-safe.
75    // `FnOnce` can only be taken by value, hence it can only be passed as an
76    // `impl Trait`, which are object-unsafe. So instead we accept &mut dyn FnMut
77    // and promise to not call it more than once. That allows us to provide
78    // RoutingTableExt::modify with the intended interface. The #[doc(hidden)]
79    // is just a hacky way to suggest to consumers to use that more-accessible
80    // interface.
81    #[doc(hidden)]
82    fn modify_impl(
83        &mut self,
84        prefix: ipnet::IpNet,
85        modify: DynModifyFn<Self::Value>,
86    ) -> Option<Self::Value>;
87
88    /// Lookup the route that most closely covers `ip`.
89    ///
90    /// # Examples
91    ///
92    /// ```rust
93    /// # use ts_bart::{Table, RoutingTable};
94    /// let mut table = Table::default();
95    ///
96    /// table.insert("1.2.0.0/16".parse().unwrap(), "value");
97    /// assert_eq!(Some(&"value"), table.lookup("1.2.3.4".parse().unwrap()));
98    /// ```
99    fn lookup(&self, ip: IpAddr) -> Option<&Self::Value>;
100
101    /// Lookup all matches that cover `ip`.
102    ///
103    /// The iterator yields prefixes in reverse length order (most specific to least).
104    fn lookup_all(&self, ip: IpAddr) -> crate::iptrie::LookupIter<'_, Self::Value>;
105
106    /// Lookup a route that exactly matches `prefix` (supernets do not match).
107    ///
108    /// # Examples
109    ///
110    /// ```rust
111    /// # use ts_bart::{Table, RoutingTable};
112    /// let mut table = Table::default();
113    /// let pfx = "1.2.0.0/16".parse().unwrap();
114    ///
115    /// table.insert(pfx, "route");
116    ///
117    /// // Exact prefix matches
118    /// assert_eq!(Some(&"route"), table.lookup_prefix_exact(pfx));
119    /// // Subnet does not
120    /// assert_eq!(
121    ///     None,
122    ///     table.lookup_prefix_exact("1.2.3.0/24".parse().unwrap())
123    /// );
124    /// ```
125    fn lookup_prefix_exact(&self, prefix: ipnet::IpNet) -> Option<&Self::Value>;
126
127    /// Lookup the route that most closely covers `prefix`.
128    ///
129    /// This represents a slight optimization over
130    /// [`lookup_prefix_lpm`][Self::lookup_prefix_lpm] if the matched prefix
131    /// isn't needed.
132    ///
133    /// # Examples
134    ///
135    /// ```rust
136    /// # use ts_bart::{Table, RoutingTable};
137    /// let mut table = Table::default();
138    /// let pfx = "1.2.0.0/16".parse().unwrap();
139    ///
140    /// table.insert(pfx, 1234);
141    ///
142    /// // subnet lookup gets only the value in the table
143    /// assert_eq!(
144    ///     Some(&1234),
145    ///     table.lookup_prefix("1.2.3.4/30".parse().unwrap())
146    /// );
147    /// ```
148    fn lookup_prefix(&self, prefix: ipnet::IpNet) -> Option<&Self::Value>;
149
150    /// Lookup the route that most closely covers `prefix`, and return that
151    /// matching prefix.
152    ///
153    /// # Examples
154    ///
155    /// ```rust
156    /// # use ts_bart::{Table, RoutingTable};
157    /// let mut table = Table::default();
158    /// let pfx = "1.2.0.0/16".parse().unwrap();
159    ///
160    /// table.insert(pfx, true);
161    ///
162    /// // subnet lookup returns the matching prefix and route value in the table
163    /// assert_eq!(
164    ///     Some((pfx, &true)),
165    ///     table.lookup_prefix_lpm("1.2.12.143/32".parse().unwrap())
166    /// );
167    /// ```
168    fn lookup_prefix_lpm(&self, prefix: ipnet::IpNet) -> Option<(ipnet::IpNet, &Self::Value)>;
169
170    /// Report the number of routes stored in the table.
171    ///
172    /// # Examples
173    ///
174    /// ```rust
175    /// # use ts_bart::{Table, RoutingTable};
176    /// let mut table = Table::default();
177    /// assert_eq!(0, table.size());
178    ///
179    /// table.insert("0.0.0.0/0".parse().unwrap(), true);
180    /// assert_eq!(1, table.size());
181    /// ```
182    fn size(&self) -> usize;
183}
184
185static_assertions::assert_obj_safe!(RoutingTable<Value = ()>);
186
187impl<T> RoutingTable for &mut T
188where
189    T: RoutingTable,
190{
191    type Value = T::Value;
192
193    fn insert(&mut self, prefix: ipnet::IpNet, val: Self::Value) -> Option<Self::Value> {
194        (*self).insert(prefix, val)
195    }
196
197    fn remove(&mut self, prefix: ipnet::IpNet) -> Option<Self::Value> {
198        (*self).remove(prefix)
199    }
200
201    fn clear(&mut self) {
202        (*self).clear()
203    }
204
205    fn modify_impl(
206        &mut self,
207        prefix: ipnet::IpNet,
208        modify: DynModifyFn<Self::Value>,
209    ) -> Option<Self::Value> {
210        (*self).modify_impl(prefix, modify)
211    }
212
213    fn contains(&self, ip: IpAddr) -> bool {
214        (**self).contains(ip)
215    }
216
217    fn lookup(&self, ip: IpAddr) -> Option<&Self::Value> {
218        (**self).lookup(ip)
219    }
220
221    fn lookup_all(&self, ip: IpAddr) -> crate::iptrie::LookupIter<'_, Self::Value> {
222        (**self).lookup_all(ip)
223    }
224
225    fn lookup_prefix_exact(&self, prefix: ipnet::IpNet) -> Option<&Self::Value> {
226        (**self).lookup_prefix_exact(prefix)
227    }
228
229    fn lookup_prefix(&self, prefix: ipnet::IpNet) -> Option<&Self::Value> {
230        (**self).lookup_prefix(prefix)
231    }
232
233    fn lookup_prefix_lpm(&self, prefix: ipnet::IpNet) -> Option<(ipnet::IpNet, &Self::Value)> {
234        (**self).lookup_prefix_lpm(prefix)
235    }
236
237    fn size(&self) -> usize {
238        (**self).size()
239    }
240}
241
242mod private {
243    pub trait Sealed {}
244}
245
246/// Automatic extension methods for implementors of [`RoutingTable`].
247pub trait RoutingTableExt: RoutingTable + private::Sealed {
248    /// Modify the route at `prefix` using the `modify` closure.
249    ///
250    /// The closure may return:
251    ///
252    /// - [`RouteModification::Insert`]: a route will be inserted with the
253    ///   provided value (or replaced if it already exists).
254    /// - [`RouteModification::Remove`]: the route will be removed (if it
255    ///   exists).
256    /// - [`RouteModification::Noop`]: the table will not be modified. The
257    ///   closure may modify the value stored in the route (if present).
258    ///
259    /// If the route was replaced or removed, the return value contains the
260    /// value that was previously stored.
261    ///
262    /// Prefer to use [`insert`][RoutingTable::insert] or
263    /// [`remove`][RoutingTable::remove] if possible; this function is
264    /// intended as a more-efficient alternative to
265    /// [`lookup_prefix_exact`][RoutingTable::lookup_prefix_exact]-then-mutate.
266    ///
267    /// # Examples
268    ///
269    /// ```rust
270    /// # use ts_bart::{SimpleTable, RouteModification, RoutingTable, RoutingTableExt};
271    /// let mut table = SimpleTable::default();
272    /// let result = table.modify("0.0.0.0/0".parse().unwrap(), |node| {
273    ///     assert_eq!(None, node);
274    ///     RouteModification::Insert(4)
275    /// });
276    /// assert_eq!(None, result);
277    /// assert_eq!(Some(&4), table.lookup("1.2.3.4".parse().unwrap()));
278    /// ```
279    #[inline]
280    fn modify(
281        &mut self,
282        prefix: ipnet::IpNet,
283        modify: impl FnOnce(Option<&mut Self::Value>) -> RouteModification<Self::Value>,
284    ) -> Option<Self::Value> {
285        let mut modify = Some(modify);
286        self.modify_impl(prefix, &mut move |val| {
287            modify
288                .take()
289                .expect("modify implementation called closure more than once")(val)
290        })
291    }
292}
293
294impl<T> private::Sealed for T where T: RoutingTable {}
295impl<T> RoutingTableExt for T where T: RoutingTable {}