1use heapless::LinearMap;
5
6use crate::config::IFACE_NEIGHBOR_CACHE_COUNT;
7use crate::time::{Duration, Instant};
8use crate::wire::{HardwareAddress, IpAddress};
9
10#[derive(Debug, Clone, Copy)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub struct Neighbor {
17 hardware_addr: HardwareAddress,
18 expires_at: Instant,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24pub(crate) enum Answer {
25 Found(HardwareAddress),
27 NotFound,
29 RateLimited,
32}
33
34impl Answer {
35 pub(crate) fn found(&self) -> bool {
37 match self {
38 Answer::Found(_) => true,
39 _ => false,
40 }
41 }
42}
43
44#[derive(Debug)]
46pub struct Cache {
47 storage: LinearMap<IpAddress, Neighbor, IFACE_NEIGHBOR_CACHE_COUNT>,
48 silent_until: Instant,
49}
50
51impl Cache {
52 pub(crate) const SILENT_TIME: Duration = Duration::from_millis(1_000);
54
55 pub(crate) const ENTRY_LIFETIME: Duration = Duration::from_millis(60_000);
57
58 pub fn new() -> Self {
60 Self {
61 storage: LinearMap::new(),
62 silent_until: Instant::from_millis(0),
63 }
64 }
65
66 pub fn reset_expiry_if_existing(
67 &mut self,
68 protocol_addr: IpAddress,
69 source_hardware_addr: HardwareAddress,
70 timestamp: Instant,
71 ) {
72 if let Some(Neighbor {
73 expires_at,
74 hardware_addr,
75 }) = self.storage.get_mut(&protocol_addr)
76 {
77 if source_hardware_addr == *hardware_addr {
78 *expires_at = timestamp + Self::ENTRY_LIFETIME;
79 }
80 }
81 }
82
83 pub fn fill(
84 &mut self,
85 protocol_addr: IpAddress,
86 hardware_addr: HardwareAddress,
87 timestamp: Instant,
88 ) {
89 debug_assert!(protocol_addr.is_unicast());
90 debug_assert!(hardware_addr.is_unicast());
91
92 let expires_at = timestamp + Self::ENTRY_LIFETIME;
93 self.fill_with_expiration(protocol_addr, hardware_addr, expires_at);
94 }
95
96 pub fn fill_with_expiration(
97 &mut self,
98 protocol_addr: IpAddress,
99 hardware_addr: HardwareAddress,
100 expires_at: Instant,
101 ) {
102 debug_assert!(protocol_addr.is_unicast());
103 debug_assert!(hardware_addr.is_unicast());
104
105 let neighbor = Neighbor {
106 expires_at,
107 hardware_addr,
108 };
109 match self.storage.insert(protocol_addr, neighbor) {
110 Ok(Some(old_neighbor)) => {
111 if old_neighbor.hardware_addr != hardware_addr {
112 net_trace!(
113 "replaced {} => {} (was {})",
114 protocol_addr,
115 hardware_addr,
116 old_neighbor.hardware_addr
117 );
118 }
119 }
120 Ok(None) => {
121 net_trace!("filled {} => {} (was empty)", protocol_addr, hardware_addr);
122 }
123 Err((protocol_addr, neighbor)) => {
124 let old_protocol_addr = *self
126 .storage
127 .iter()
128 .min_by_key(|(_, neighbor)| neighbor.expires_at)
129 .expect("empty neighbor cache storage")
130 .0;
131
132 let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap();
133 match self.storage.insert(protocol_addr, neighbor) {
134 Ok(None) => {
135 net_trace!(
136 "filled {} => {} (evicted {} => {})",
137 protocol_addr,
138 hardware_addr,
139 old_protocol_addr,
140 _old_neighbor.hardware_addr
141 );
142 }
143 _ => unreachable!(),
145 }
146 }
147 }
148 }
149
150 pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
151 assert!(protocol_addr.is_unicast());
152
153 if let Some(&Neighbor {
154 expires_at,
155 hardware_addr,
156 }) = self.storage.get(protocol_addr)
157 {
158 if timestamp < expires_at {
159 return Answer::Found(hardware_addr);
160 }
161 }
162
163 if timestamp < self.silent_until {
164 Answer::RateLimited
165 } else {
166 Answer::NotFound
167 }
168 }
169
170 pub(crate) fn limit_rate(&mut self, timestamp: Instant) {
171 self.silent_until = timestamp + Self::SILENT_TIME;
172 }
173
174 pub(crate) fn flush(&mut self) {
175 self.storage.clear()
176 }
177}
178
179#[cfg(feature = "medium-ethernet")]
180#[cfg(test)]
181mod test {
182 use super::*;
183 #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
184 use crate::wire::ipv4::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
185 #[cfg(feature = "proto-ipv6")]
186 use crate::wire::ipv6::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
187
188 use crate::wire::EthernetAddress;
189
190 const HADDR_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 1]));
191 const HADDR_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 2]));
192 const HADDR_C: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 3]));
193 const HADDR_D: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 4]));
194
195 #[test]
196 fn test_fill() {
197 let mut cache = Cache::new();
198
199 assert!(!cache
200 .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0))
201 .found());
202 assert!(!cache
203 .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0))
204 .found());
205
206 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0));
207 assert_eq!(
208 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
209 Answer::Found(HADDR_A)
210 );
211 assert!(!cache
212 .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0))
213 .found());
214 assert!(!cache
215 .lookup(
216 &MOCK_IP_ADDR_1.into(),
217 Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
218 )
219 .found(),);
220
221 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0));
222 assert!(!cache
223 .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0))
224 .found());
225 }
226
227 #[test]
228 fn test_expire() {
229 let mut cache = Cache::new();
230
231 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0));
232 assert_eq!(
233 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
234 Answer::Found(HADDR_A)
235 );
236 assert!(!cache
237 .lookup(
238 &MOCK_IP_ADDR_1.into(),
239 Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
240 )
241 .found(),);
242 }
243
244 #[test]
245 fn test_replace() {
246 let mut cache = Cache::new();
247
248 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0));
249 assert_eq!(
250 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
251 Answer::Found(HADDR_A)
252 );
253 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_B, Instant::from_millis(0));
254 assert_eq!(
255 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
256 Answer::Found(HADDR_B)
257 );
258 }
259
260 #[test]
261 fn test_evict() {
262 let mut cache = Cache::new();
263
264 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(100));
265 cache.fill(MOCK_IP_ADDR_2.into(), HADDR_B, Instant::from_millis(50));
266 cache.fill(MOCK_IP_ADDR_3.into(), HADDR_C, Instant::from_millis(200));
267 assert_eq!(
268 cache.lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(1000)),
269 Answer::Found(HADDR_B)
270 );
271 assert!(!cache
272 .lookup(&MOCK_IP_ADDR_4.into(), Instant::from_millis(1000))
273 .found());
274
275 cache.fill(MOCK_IP_ADDR_4.into(), HADDR_D, Instant::from_millis(300));
276 assert!(!cache
277 .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(1000))
278 .found());
279 assert_eq!(
280 cache.lookup(&MOCK_IP_ADDR_4.into(), Instant::from_millis(1000)),
281 Answer::Found(HADDR_D)
282 );
283 }
284
285 #[test]
286 fn test_hush() {
287 let mut cache = Cache::new();
288
289 assert_eq!(
290 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
291 Answer::NotFound
292 );
293
294 cache.limit_rate(Instant::from_millis(0));
295 assert_eq!(
296 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(100)),
297 Answer::RateLimited
298 );
299 assert_eq!(
300 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(2000)),
301 Answer::NotFound
302 );
303 }
304
305 #[test]
306 fn test_flush() {
307 let mut cache = Cache::new();
308
309 cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0));
310 assert_eq!(
311 cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)),
312 Answer::Found(HADDR_A)
313 );
314 assert!(!cache
315 .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0))
316 .found());
317
318 cache.flush();
319 assert!(!cache
320 .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0))
321 .found());
322 assert!(!cache
323 .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0))
324 .found());
325 }
326}