1use heapless::Vec;
2
3use crate::config::IFACE_MAX_ROUTE_COUNT;
4use crate::time::Instant;
5use crate::wire::{IpAddress, IpCidr};
6#[cfg(feature = "proto-ipv4")]
7use crate::wire::{Ipv4Address, Ipv4Cidr};
8#[cfg(feature = "proto-ipv6")]
9use crate::wire::{Ipv6Address, Ipv6Cidr};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct RouteTableFull;
14
15impl core::fmt::Display for RouteTableFull {
16 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17 write!(f, "Route table full")
18 }
19}
20
21#[cfg(feature = "std")]
22impl std::error::Error for RouteTableFull {}
23
24#[derive(Debug, Clone, Copy)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub struct Route {
28 pub cidr: IpCidr,
29 pub via_router: IpAddress,
30 pub preferred_until: Option<Instant>,
32 pub expires_at: Option<Instant>,
34}
35
36#[cfg(feature = "proto-ipv4")]
37const IPV4_DEFAULT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(0, 0, 0, 0), 0));
38#[cfg(feature = "proto-ipv6")]
39const IPV6_DEFAULT: IpCidr =
40 IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0), 0));
41
42impl Route {
43 #[cfg(feature = "proto-ipv4")]
45 pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
46 Route {
47 cidr: IPV4_DEFAULT,
48 via_router: gateway.into(),
49 preferred_until: None,
50 expires_at: None,
51 }
52 }
53
54 #[cfg(feature = "proto-ipv6")]
56 pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
57 Route {
58 cidr: IPV6_DEFAULT,
59 via_router: gateway.into(),
60 preferred_until: None,
61 expires_at: None,
62 }
63 }
64}
65
66#[derive(Debug)]
68pub struct Routes {
69 storage: Vec<Route, IFACE_MAX_ROUTE_COUNT>,
70}
71
72impl Routes {
73 pub fn new() -> Self {
75 Self {
76 storage: Vec::new(),
77 }
78 }
79
80 pub fn update<F: FnOnce(&mut Vec<Route, IFACE_MAX_ROUTE_COUNT>)>(&mut self, f: F) {
82 f(&mut self.storage);
83 }
84
85 #[cfg(feature = "proto-ipv4")]
89 pub fn add_default_ipv4_route(
90 &mut self,
91 gateway: Ipv4Address,
92 ) -> Result<Option<Route>, RouteTableFull> {
93 let old = self.remove_default_ipv4_route();
94 self.storage
95 .push(Route::new_ipv4_gateway(gateway))
96 .map_err(|_| RouteTableFull)?;
97 Ok(old)
98 }
99
100 #[cfg(feature = "proto-ipv6")]
104 pub fn add_default_ipv6_route(
105 &mut self,
106 gateway: Ipv6Address,
107 ) -> Result<Option<Route>, RouteTableFull> {
108 let old = self.remove_default_ipv6_route();
109 self.storage
110 .push(Route::new_ipv6_gateway(gateway))
111 .map_err(|_| RouteTableFull)?;
112 Ok(old)
113 }
114
115 #[cfg(feature = "proto-ipv4")]
119 pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
120 if let Some((i, _)) = self
121 .storage
122 .iter()
123 .enumerate()
124 .find(|(_, r)| r.cidr == IPV4_DEFAULT)
125 {
126 Some(self.storage.remove(i))
127 } else {
128 None
129 }
130 }
131
132 #[cfg(feature = "proto-ipv6")]
136 pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
137 if let Some((i, _)) = self
138 .storage
139 .iter()
140 .enumerate()
141 .find(|(_, r)| r.cidr == IPV6_DEFAULT)
142 {
143 Some(self.storage.remove(i))
144 } else {
145 None
146 }
147 }
148
149 pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
150 assert!(addr.is_unicast());
151
152 self.storage
153 .iter()
154 .filter(|route| {
156 if let Some(expires_at) = route.expires_at {
157 if timestamp > expires_at {
158 return false;
159 }
160 }
161 route.cidr.contains_addr(addr)
162 })
163 .max_by_key(|route| route.cidr.prefix_len())
165 .map(|route| route.via_router)
166 }
167}
168
169#[cfg(test)]
170mod test {
171 use super::*;
172 #[cfg(feature = "proto-ipv6")]
173 mod mock {
174 use super::super::*;
175 pub const ADDR_1A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 1);
176 pub const ADDR_1B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 13);
177 pub const ADDR_1C: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 42);
178 pub fn cidr_1() -> Ipv6Cidr {
179 Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 0), 64)
180 }
181
182 pub const ADDR_2A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 1);
183 pub const ADDR_2B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 21);
184 pub fn cidr_2() -> Ipv6Cidr {
185 Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 0), 64)
186 }
187 }
188
189 #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
190 mod mock {
191 use super::super::*;
192 pub const ADDR_1A: Ipv4Address = Ipv4Address::new(192, 0, 2, 1);
193 pub const ADDR_1B: Ipv4Address = Ipv4Address::new(192, 0, 2, 13);
194 pub const ADDR_1C: Ipv4Address = Ipv4Address::new(192, 0, 2, 42);
195 pub fn cidr_1() -> Ipv4Cidr {
196 Ipv4Cidr::new(Ipv4Address::new(192, 0, 2, 0), 24)
197 }
198
199 pub const ADDR_2A: Ipv4Address = Ipv4Address::new(198, 51, 100, 1);
200 pub const ADDR_2B: Ipv4Address = Ipv4Address::new(198, 51, 100, 21);
201 pub fn cidr_2() -> Ipv4Cidr {
202 Ipv4Cidr::new(Ipv4Address::new(198, 51, 100, 0), 24)
203 }
204 }
205
206 use self::mock::*;
207
208 #[test]
209 fn test_fill() {
210 let mut routes = Routes::new();
211
212 assert_eq!(
213 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
214 None
215 );
216 assert_eq!(
217 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
218 None
219 );
220 assert_eq!(
221 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
222 None
223 );
224 assert_eq!(
225 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
226 None
227 );
228 assert_eq!(
229 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
230 None
231 );
232
233 let route = Route {
234 cidr: cidr_1().into(),
235 via_router: ADDR_1A.into(),
236 preferred_until: None,
237 expires_at: None,
238 };
239 routes.update(|storage| {
240 storage.push(route).unwrap();
241 });
242
243 assert_eq!(
244 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
245 Some(ADDR_1A.into())
246 );
247 assert_eq!(
248 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
249 Some(ADDR_1A.into())
250 );
251 assert_eq!(
252 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
253 Some(ADDR_1A.into())
254 );
255 assert_eq!(
256 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
257 None
258 );
259 assert_eq!(
260 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
261 None
262 );
263
264 let route2 = Route {
265 cidr: cidr_2().into(),
266 via_router: ADDR_2A.into(),
267 preferred_until: Some(Instant::from_millis(10)),
268 expires_at: Some(Instant::from_millis(10)),
269 };
270 routes.update(|storage| {
271 storage.push(route2).unwrap();
272 });
273
274 assert_eq!(
275 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
276 Some(ADDR_1A.into())
277 );
278 assert_eq!(
279 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
280 Some(ADDR_1A.into())
281 );
282 assert_eq!(
283 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
284 Some(ADDR_1A.into())
285 );
286 assert_eq!(
287 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
288 Some(ADDR_2A.into())
289 );
290 assert_eq!(
291 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
292 Some(ADDR_2A.into())
293 );
294
295 assert_eq!(
296 routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)),
297 Some(ADDR_1A.into())
298 );
299 assert_eq!(
300 routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)),
301 Some(ADDR_1A.into())
302 );
303 assert_eq!(
304 routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)),
305 Some(ADDR_1A.into())
306 );
307 assert_eq!(
308 routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)),
309 Some(ADDR_2A.into())
310 );
311 assert_eq!(
312 routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)),
313 Some(ADDR_2A.into())
314 );
315 }
316}