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 {}