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 fill(
67 &mut self,
68 protocol_addr: IpAddress,
69 hardware_addr: HardwareAddress,
70 timestamp: Instant,
71 ) {
72 debug_assert!(protocol_addr.is_unicast());
73 debug_assert!(hardware_addr.is_unicast());
74
75 let expires_at = timestamp + Self::ENTRY_LIFETIME;
76 self.fill_with_expiration(protocol_addr, hardware_addr, expires_at);
77 }
78
79 pub fn fill_with_expiration(
80 &mut self,
81 protocol_addr: IpAddress,
82 hardware_addr: HardwareAddress,
83 expires_at: Instant,
84 ) {
85 debug_assert!(protocol_addr.is_unicast());
86 debug_assert!(hardware_addr.is_unicast());
87
88 let neighbor = Neighbor {
89 expires_at,
90 hardware_addr,
91 };
92 match self.storage.insert(protocol_addr, neighbor) {
93 Ok(Some(old_neighbor)) => {
94 if old_neighbor.hardware_addr != hardware_addr {
95 net_trace!(
96 "replaced {} => {} (was {})",
97 protocol_addr,
98 hardware_addr,
99 old_neighbor.hardware_addr
100 );
101 }
102 }
103 Ok(None) => {
104 net_trace!("filled {} => {} (was empty)", protocol_addr, hardware_addr);
105 }
106 Err((protocol_addr, neighbor)) => {
107 let old_protocol_addr = *self
109 .storage
110 .iter()
111 .min_by_key(|(_, neighbor)| neighbor.expires_at)
112 .expect("empty neighbor cache storage")
113 .0;
114
115 let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap();
116 match self.storage.insert(protocol_addr, neighbor) {
117 Ok(None) => {
118 net_trace!(
119 "filled {} => {} (evicted {} => {})",
120 protocol_addr,
121 hardware_addr,
122 old_protocol_addr,
123 _old_neighbor.hardware_addr
124 );
125 }
126 _ => unreachable!(),
128 }
129 }
130 }
131 }
132
133 pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
134 assert!(protocol_addr.is_unicast());
135
136 if let Some(&Neighbor {
137 expires_at,
138 hardware_addr,
139 }) = self.storage.get(protocol_addr)
140 {
141 if timestamp < expires_at {
142 return Answer::Found(hardware_addr);
143 }
144 }
145
146 if timestamp < self.silent_until {
147 Answer::RateLimited
148 } else {
149 Answer::NotFound
150 }
151 }
152
153 pub(crate) fn limit_rate(&mut self, timestamp: Instant) {
154 self.silent_until = timestamp + Self::SILENT_TIME;
155 }
156
157 pub(crate) fn flush(&mut self) {
158 self.storage.clear()
159 }
160}
161
162#[cfg(feature = "medium-ethernet")]
163#[cfg(test)]
164mod test {
165 use super::*;
166 use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
167
168 use crate::wire::EthernetAddress;
169
170 const HADDR_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 1]));
171 const HADDR_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 2]));
172 const HADDR_C: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 3]));
173 const HADDR_D: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 4]));
174
175 #[test]
176 fn test_fill() {
177 let mut cache = Cache::new();
178
179 assert!(!cache
180 .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
181 .found());
182 assert!(!cache
183 .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
184 .found());
185
186 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
187 assert_eq!(
188 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
189 Answer::Found(HADDR_A)
190 );
191 assert!(!cache
192 .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
193 .found());
194 assert!(!cache
195 .lookup(
196 &MOCK_IP_ADDR_1,
197 Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
198 )
199 .found(),);
200
201 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
202 assert!(!cache
203 .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
204 .found());
205 }
206
207 #[test]
208 fn test_expire() {
209 let mut cache = Cache::new();
210
211 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
212 assert_eq!(
213 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
214 Answer::Found(HADDR_A)
215 );
216 assert!(!cache
217 .lookup(
218 &MOCK_IP_ADDR_1,
219 Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
220 )
221 .found(),);
222 }
223
224 #[test]
225 fn test_replace() {
226 let mut cache = Cache::new();
227
228 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
229 assert_eq!(
230 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
231 Answer::Found(HADDR_A)
232 );
233 cache.fill(MOCK_IP_ADDR_1, HADDR_B, Instant::from_millis(0));
234 assert_eq!(
235 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
236 Answer::Found(HADDR_B)
237 );
238 }
239
240 #[test]
241 fn test_evict() {
242 let mut cache = Cache::new();
243
244 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(100));
245 cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
246 cache.fill(MOCK_IP_ADDR_3, HADDR_C, Instant::from_millis(200));
247 assert_eq!(
248 cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)),
249 Answer::Found(HADDR_B)
250 );
251 assert!(!cache
252 .lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000))
253 .found());
254
255 cache.fill(MOCK_IP_ADDR_4, HADDR_D, Instant::from_millis(300));
256 assert!(!cache
257 .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000))
258 .found());
259 assert_eq!(
260 cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)),
261 Answer::Found(HADDR_D)
262 );
263 }
264
265 #[test]
266 fn test_hush() {
267 let mut cache = Cache::new();
268
269 assert_eq!(
270 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
271 Answer::NotFound
272 );
273
274 cache.limit_rate(Instant::from_millis(0));
275 assert_eq!(
276 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(100)),
277 Answer::RateLimited
278 );
279 assert_eq!(
280 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(2000)),
281 Answer::NotFound
282 );
283 }
284
285 #[test]
286 fn test_flush() {
287 let mut cache = Cache::new();
288
289 cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
290 assert_eq!(
291 cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
292 Answer::Found(HADDR_A)
293 );
294 assert!(!cache
295 .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
296 .found());
297
298 cache.flush();
299 assert!(!cache
300 .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
301 .found());
302 assert!(!cache
303 .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
304 .found());
305 }
306}