1use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10use std::time::Instant;
11
12use ndn_packet::Name;
13use ndn_transport::FaceId;
14use tracing::{debug, info};
15
16use crate::MacAddr;
17
18#[derive(Clone, Debug)]
20pub enum NeighborState {
21 Probing {
23 attempts: u8,
25 last_probe: Instant,
27 },
28 Established {
30 last_seen: Instant,
32 },
33 Stale {
35 miss_count: u8,
37 last_seen: Instant,
39 },
40 Absent,
42}
43
44#[derive(Clone, Debug)]
46pub struct NeighborEntry {
47 pub node_name: Name,
49 pub state: NeighborState,
51 pub faces: Vec<(FaceId, MacAddr, String)>,
56 pub rtt_us: Option<u32>,
58 pub pending_nonce: Option<u32>,
61}
62
63impl NeighborEntry {
64 pub fn new(node_name: Name) -> Self {
65 Self {
66 node_name,
67 state: NeighborState::Probing {
68 attempts: 0,
69 last_probe: Instant::now(),
70 },
71 faces: Vec::new(),
72 rtt_us: None,
73 pending_nonce: None,
74 }
75 }
76
77 pub fn is_reachable(&self) -> bool {
79 matches!(self.state, NeighborState::Established { .. }) && !self.faces.is_empty()
80 }
81
82 pub fn face_for(&self, mac: &MacAddr, iface: &str) -> Option<FaceId> {
84 self.faces
85 .iter()
86 .find(|(_, m, i)| m == mac && i == iface)
87 .map(|(id, _, _)| *id)
88 }
89}
90
91pub enum NeighborUpdate {
93 Upsert(NeighborEntry),
95 SetState { name: Name, state: NeighborState },
97 AddFace {
99 name: Name,
100 face_id: FaceId,
101 mac: MacAddr,
102 iface: String,
103 },
104 RemoveFace { name: Name, face_id: FaceId },
106 UpdateRtt { name: Name, rtt_us: u32 },
108 Remove(Name),
110}
111
112fn state_label(s: &NeighborState) -> &'static str {
113 match s {
114 NeighborState::Probing { .. } => "Probing",
115 NeighborState::Established { .. } => "Established",
116 NeighborState::Stale { .. } => "Stale",
117 NeighborState::Absent => "Absent",
118 }
119}
120
121pub struct NeighborTable {
126 inner: Mutex<HashMap<Name, NeighborEntry>>,
127}
128
129impl NeighborTable {
130 pub fn new() -> Arc<Self> {
131 Arc::new(Self {
132 inner: Mutex::new(HashMap::new()),
133 })
134 }
135
136 pub fn apply(&self, update: NeighborUpdate) {
138 let mut map = self.inner.lock().unwrap();
139 match update {
140 NeighborUpdate::Upsert(entry) => {
141 let is_new = !map.contains_key(&entry.node_name);
142 let label = state_label(&entry.state);
143 let name = entry.node_name.clone();
144 map.insert(name.clone(), entry);
145 if is_new {
146 debug!(peer = %name, state = label, "neighbor added to table");
147 }
148 }
149 NeighborUpdate::SetState { name, state } => {
150 if let Some(entry) = map.get_mut(&name) {
151 let from = state_label(&entry.state);
152 let to = state_label(&state);
153 if from != to {
154 if matches!(state, NeighborState::Established { .. }) {
157 info!(peer = %name, %from, %to, "neighbor established");
158 } else if matches!(state, NeighborState::Stale { .. }) {
159 info!(peer = %name, %from, %to, "neighbor went stale");
160 } else {
161 debug!(peer = %name, %from, %to, "neighbor state →");
162 }
163 }
164 entry.state = state;
165 }
166 }
167 NeighborUpdate::AddFace {
168 name,
169 face_id,
170 mac,
171 iface,
172 } => {
173 if let Some(entry) = map.get_mut(&name) {
174 if entry.face_for(&mac, &iface).is_none() {
176 entry.faces.push((face_id, mac, iface));
177 }
178 }
179 }
180 NeighborUpdate::RemoveFace { name, face_id } => {
181 if let Some(entry) = map.get_mut(&name) {
182 entry.faces.retain(|(id, _, _)| *id != face_id);
183 }
184 }
185 NeighborUpdate::UpdateRtt { name, rtt_us } => {
186 if let Some(entry) = map.get_mut(&name) {
187 entry.rtt_us = Some(match entry.rtt_us {
189 None => rtt_us,
190 Some(prev) => (7 * prev + rtt_us) / 8,
191 });
192 }
193 }
194 NeighborUpdate::Remove(name) => {
195 if map.remove(&name).is_some() {
196 info!(peer = %name, "neighbor removed from table");
197 }
198 }
199 }
200 }
201
202 pub fn get(&self, name: &Name) -> Option<NeighborEntry> {
204 self.inner.lock().unwrap().get(name).cloned()
205 }
206
207 pub fn all(&self) -> Vec<NeighborEntry> {
209 self.inner.lock().unwrap().values().cloned().collect()
210 }
211
212 pub fn face_for_peer(&self, mac: &MacAddr, iface: &str) -> Option<FaceId> {
214 let map = self.inner.lock().unwrap();
215 for entry in map.values() {
216 if let Some(id) = entry.face_for(mac, iface) {
217 return Some(id);
218 }
219 }
220 None
221 }
222}
223
224impl Default for NeighborTable {
225 fn default() -> Self {
226 Self {
227 inner: Mutex::new(HashMap::new()),
228 }
229 }
230}
231
232impl crate::NeighborTableView for NeighborTable {
233 fn get(&self, name: &Name) -> Option<NeighborEntry> {
234 NeighborTable::get(self, name)
235 }
236 fn all(&self) -> Vec<NeighborEntry> {
237 NeighborTable::all(self)
238 }
239 fn face_for_peer(&self, mac: &crate::MacAddr, iface: &str) -> Option<FaceId> {
240 NeighborTable::face_for_peer(self, mac, iface)
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use std::str::FromStr;
248
249 fn name(s: &str) -> Name {
250 Name::from_str(s).unwrap()
251 }
252
253 #[test]
254 fn upsert_and_get() {
255 let table = NeighborTable::new();
256 let n = name("/ndn/test/node");
257 table.apply(NeighborUpdate::Upsert(NeighborEntry::new(n.clone())));
258 assert!(table.get(&n).is_some());
259 }
260
261 #[test]
262 fn remove_entry() {
263 let table = NeighborTable::new();
264 let n = name("/ndn/test/node");
265 table.apply(NeighborUpdate::Upsert(NeighborEntry::new(n.clone())));
266 table.apply(NeighborUpdate::Remove(n.clone()));
267 assert!(table.get(&n).is_none());
268 }
269
270 #[test]
271 fn rtt_ewma() {
272 let table = NeighborTable::new();
273 let n = name("/ndn/test/node");
274 table.apply(NeighborUpdate::Upsert(NeighborEntry::new(n.clone())));
275 table.apply(NeighborUpdate::UpdateRtt {
276 name: n.clone(),
277 rtt_us: 1000,
278 });
279 let e = table.get(&n).unwrap();
280 assert_eq!(e.rtt_us, Some(1000)); table.apply(NeighborUpdate::UpdateRtt {
283 name: n.clone(),
284 rtt_us: 2000,
285 });
286 let e = table.get(&n).unwrap();
287 assert_eq!(e.rtt_us, Some(1125));
289 }
290
291 #[test]
292 fn add_face_deduplicates() {
293 let table = NeighborTable::new();
294 let n = name("/ndn/test/node");
295 let mac = MacAddr::new([0xaa, 0xbb, 0xcc, 0x00, 0x00, 0x01]);
296 table.apply(NeighborUpdate::Upsert(NeighborEntry::new(n.clone())));
297 table.apply(NeighborUpdate::AddFace {
298 name: n.clone(),
299 face_id: FaceId(1),
300 mac,
301 iface: "eth0".into(),
302 });
303 table.apply(NeighborUpdate::AddFace {
304 name: n.clone(),
305 face_id: FaceId(1),
306 mac,
307 iface: "eth0".into(),
308 });
309 let e = table.get(&n).unwrap();
310 assert_eq!(e.faces.len(), 1);
311 }
312
313 #[test]
314 fn face_for_peer_lookup() {
315 let table = NeighborTable::new();
316 let n = name("/ndn/test/node");
317 let mac = MacAddr::new([0xde, 0xad, 0xbe, 0xef, 0x00, 0x01]);
318 table.apply(NeighborUpdate::Upsert(NeighborEntry::new(n.clone())));
319 table.apply(NeighborUpdate::AddFace {
320 name: n.clone(),
321 face_id: FaceId(7),
322 mac,
323 iface: "eth0".into(),
324 });
325 assert_eq!(table.face_for_peer(&mac, "eth0"), Some(FaceId(7)));
326 assert_eq!(table.face_for_peer(&mac, "eth1"), None);
327 }
328}