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 =
176 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
177 pub const ADDR_1B: Ipv6Address =
178 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
179 pub const ADDR_1C: Ipv6Address =
180 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
181 pub fn cidr_1() -> Ipv6Cidr {
182 Ipv6Cidr::new(
183 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]),
184 64,
185 )
186 }
187
188 pub const ADDR_2A: Ipv6Address =
189 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
190 pub const ADDR_2B: Ipv6Address =
191 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
192 pub fn cidr_2() -> Ipv6Cidr {
193 Ipv6Cidr::new(
194 Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]),
195 64,
196 )
197 }
198 }
199
200 #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
201 mod mock {
202 use super::super::*;
203 pub const ADDR_1A: Ipv4Address = Ipv4Address([192, 0, 2, 1]);
204 pub const ADDR_1B: Ipv4Address = Ipv4Address([192, 0, 2, 13]);
205 pub const ADDR_1C: Ipv4Address = Ipv4Address([192, 0, 2, 42]);
206 pub fn cidr_1() -> Ipv4Cidr {
207 Ipv4Cidr::new(Ipv4Address([192, 0, 2, 0]), 24)
208 }
209
210 pub const ADDR_2A: Ipv4Address = Ipv4Address([198, 51, 100, 1]);
211 pub const ADDR_2B: Ipv4Address = Ipv4Address([198, 51, 100, 21]);
212 pub fn cidr_2() -> Ipv4Cidr {
213 Ipv4Cidr::new(Ipv4Address([198, 51, 100, 0]), 24)
214 }
215 }
216
217 use self::mock::*;
218
219 #[test]
220 fn test_fill() {
221 let mut routes = Routes::new();
222
223 assert_eq!(
224 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
225 None
226 );
227 assert_eq!(
228 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
229 None
230 );
231 assert_eq!(
232 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
233 None
234 );
235 assert_eq!(
236 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
237 None
238 );
239 assert_eq!(
240 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
241 None
242 );
243
244 let route = Route {
245 cidr: cidr_1().into(),
246 via_router: ADDR_1A.into(),
247 preferred_until: None,
248 expires_at: None,
249 };
250 routes.update(|storage| {
251 storage.push(route).unwrap();
252 });
253
254 assert_eq!(
255 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
256 Some(ADDR_1A.into())
257 );
258 assert_eq!(
259 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
260 Some(ADDR_1A.into())
261 );
262 assert_eq!(
263 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
264 Some(ADDR_1A.into())
265 );
266 assert_eq!(
267 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
268 None
269 );
270 assert_eq!(
271 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
272 None
273 );
274
275 let route2 = Route {
276 cidr: cidr_2().into(),
277 via_router: ADDR_2A.into(),
278 preferred_until: Some(Instant::from_millis(10)),
279 expires_at: Some(Instant::from_millis(10)),
280 };
281 routes.update(|storage| {
282 storage.push(route2).unwrap();
283 });
284
285 assert_eq!(
286 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
287 Some(ADDR_1A.into())
288 );
289 assert_eq!(
290 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
291 Some(ADDR_1A.into())
292 );
293 assert_eq!(
294 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
295 Some(ADDR_1A.into())
296 );
297 assert_eq!(
298 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
299 Some(ADDR_2A.into())
300 );
301 assert_eq!(
302 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
303 Some(ADDR_2A.into())
304 );
305
306 assert_eq!(
307 routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)),
308 Some(ADDR_1A.into())
309 );
310 assert_eq!(
311 routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)),
312 Some(ADDR_1A.into())
313 );
314 assert_eq!(
315 routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)),
316 Some(ADDR_1A.into())
317 );
318 assert_eq!(
319 routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)),
320 Some(ADDR_2A.into())
321 );
322 assert_eq!(
323 routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)),
324 Some(ADDR_2A.into())
325 );
326 }
327}