1use std::ops::{Deref, DerefMut};
41use std::time::{Duration, Instant};
42
43use bytes::Bytes;
44use ndn_faces::l2::{NamedEtherFace, RadioFaceMetadata};
45use ndn_transport::{FaceId, MacAddr};
46
47use crate::hello::medium::{HELLO_PREFIX_DEPTH, HelloCore, HelloState, LinkMedium};
48use crate::wire::{parse_raw_interest, write_name_tlv, write_nni};
49use crate::{
50 DiscoveryConfig, DiscoveryContext, DiscoveryProfile, DiscoveryProtocol, HelloPayload,
51 HelloProtocol, InboundMeta, LinkAddr, NeighborEntry, NeighborUpdate, ProtocolId,
52};
53use ndn_packet::{Name, tlv_type};
54use ndn_tlv::TlvWriter;
55use tracing::{debug, warn};
56
57const PROTOCOL: ProtocolId = ProtocolId("ether-nd");
58
59pub struct EtherMedium {
65 multicast_face_id: FaceId,
67 iface: String,
69 #[allow(dead_code)]
71 local_mac: MacAddr,
72}
73
74pub struct EtherNeighborDiscovery(HelloProtocol<EtherMedium>);
76
77impl Deref for EtherNeighborDiscovery {
78 type Target = HelloProtocol<EtherMedium>;
79 fn deref(&self) -> &Self::Target {
80 &self.0
81 }
82}
83
84impl DerefMut for EtherNeighborDiscovery {
85 fn deref_mut(&mut self) -> &mut Self::Target {
86 &mut self.0
87 }
88}
89
90impl DiscoveryProtocol for EtherNeighborDiscovery {
91 fn protocol_id(&self) -> ProtocolId {
92 self.0.protocol_id()
93 }
94 fn claimed_prefixes(&self) -> &[Name] {
95 self.0.claimed_prefixes()
96 }
97 fn tick_interval(&self) -> Duration {
98 self.0.tick_interval()
99 }
100 fn on_face_up(&self, face_id: FaceId, ctx: &dyn DiscoveryContext) {
101 self.0.on_face_up(face_id, ctx)
102 }
103 fn on_face_down(&self, face_id: FaceId, ctx: &dyn DiscoveryContext) {
104 self.0.on_face_down(face_id, ctx)
105 }
106 fn on_inbound(
107 &self,
108 raw: &Bytes,
109 incoming_face: FaceId,
110 meta: &InboundMeta,
111 ctx: &dyn DiscoveryContext,
112 ) -> bool {
113 self.0.on_inbound(raw, incoming_face, meta, ctx)
114 }
115 fn on_tick(&self, now: Instant, ctx: &dyn DiscoveryContext) {
116 self.0.on_tick(now, ctx)
117 }
118}
119
120impl EtherNeighborDiscovery {
121 pub fn new(
123 multicast_face_id: FaceId,
124 iface: impl Into<String>,
125 node_name: Name,
126 local_mac: MacAddr,
127 ) -> Self {
128 Self::new_with_config(
129 multicast_face_id,
130 iface,
131 node_name,
132 local_mac,
133 DiscoveryConfig::for_profile(&DiscoveryProfile::Lan),
134 )
135 }
136
137 pub fn new_with_config(
139 multicast_face_id: FaceId,
140 iface: impl Into<String>,
141 node_name: Name,
142 local_mac: MacAddr,
143 config: DiscoveryConfig,
144 ) -> Self {
145 let medium = EtherMedium {
146 multicast_face_id,
147 iface: iface.into(),
148 local_mac,
149 };
150 Self(HelloProtocol::create(medium, node_name, config))
151 }
152
153 pub fn from_profile(
155 multicast_face_id: FaceId,
156 iface: impl Into<String>,
157 node_name: Name,
158 local_mac: MacAddr,
159 profile: &DiscoveryProfile,
160 ) -> Self {
161 Self::new_with_config(
162 multicast_face_id,
163 iface,
164 node_name,
165 local_mac,
166 DiscoveryConfig::for_profile(profile),
167 )
168 }
169}
170
171impl EtherMedium {
172 fn ensure_peer(
173 &self,
174 ctx: &dyn DiscoveryContext,
175 peer_name: &Name,
176 peer_mac: MacAddr,
177 ) -> Option<FaceId> {
178 let existing = ctx.neighbors().face_for_peer(&peer_mac, &self.iface);
179
180 let face_id = if let Some(fid) = existing {
181 fid
182 } else {
183 let fid = ctx.alloc_face_id();
184 match NamedEtherFace::new(
185 fid,
186 peer_name.clone(),
187 peer_mac,
188 self.iface.clone(),
189 RadioFaceMetadata::default(),
190 ) {
191 Ok(face) => {
192 let registered = ctx.add_face(std::sync::Arc::new(face));
193 debug!("EtherND: created unicast face {registered:?} -> {peer_name}");
194 registered
195 }
196 Err(e) => {
197 warn!("EtherND: failed to create unicast face to {peer_name}: {e}");
198 return None;
199 }
200 }
201 };
202
203 if ctx.neighbors().get(peer_name).is_none() {
204 ctx.update_neighbor(NeighborUpdate::Upsert(NeighborEntry::new(
205 peer_name.clone(),
206 )));
207 }
208
209 ctx.update_neighbor(NeighborUpdate::AddFace {
210 name: peer_name.clone(),
211 face_id,
212 mac: peer_mac,
213 iface: self.iface.clone(),
214 });
215
216 ctx.add_fib_entry(peer_name, face_id, 0, PROTOCOL);
217 Some(face_id)
218 }
219}
220
221impl LinkMedium for EtherMedium {
222 fn protocol_id(&self) -> ProtocolId {
223 PROTOCOL
224 }
225
226 fn build_hello_data(&self, core: &HelloCore, interest_name: &Name) -> Bytes {
227 let mut payload = crate::HelloPayload::new(core.node_name.clone());
228
229 if core.config.read().unwrap().prefix_announcement == crate::PrefixAnnouncementMode::InHello
230 {
231 let sp = core.served_prefixes.lock().unwrap();
232 payload.served_prefixes = sp.clone();
233 }
234
235 {
236 let st = core.state.lock().unwrap();
237 if !st.recent_diffs.is_empty() {
238 payload.neighbor_diffs.push(crate::NeighborDiff {
239 entries: st.recent_diffs.iter().cloned().collect(),
240 });
241 }
242 }
243
244 let content = payload.encode();
245 let freshness_ms = core
246 .config
247 .read()
248 .unwrap()
249 .hello_interval_base
250 .as_millis()
251 .min(u32::MAX as u128) as u64
252 * 2;
253
254 let mut w = TlvWriter::new();
255 w.write_nested(tlv_type::DATA, |w: &mut TlvWriter| {
256 write_name_tlv(w, interest_name);
257 w.write_nested(tlv_type::META_INFO, |w: &mut TlvWriter| {
258 write_nni(w, tlv_type::FRESHNESS_PERIOD, freshness_ms);
259 });
260 w.write_tlv(tlv_type::CONTENT, &content);
261 w.write_nested(tlv_type::SIGNATURE_INFO, |w: &mut TlvWriter| {
262 w.write_tlv(tlv_type::SIGNATURE_TYPE, &[0u8]);
263 });
264 w.write_tlv(tlv_type::SIGNATURE_VALUE, &[0u8; 32]);
265 });
266 w.finish()
267 }
268
269 fn handle_hello_interest(
270 &self,
271 raw: &Bytes,
272 _incoming_face: FaceId,
273 meta: &InboundMeta,
274 core: &HelloCore,
275 ctx: &dyn DiscoveryContext,
276 ) -> bool {
277 let parsed = match parse_raw_interest(raw) {
278 Some(p) => p,
279 None => return false,
280 };
281
282 let name = &parsed.name;
283 if !name.has_prefix(&core.hello_prefix) {
284 return false;
285 }
286 if name.components().len() != HELLO_PREFIX_DEPTH + 1 {
287 return false;
288 }
289
290 let sender_mac = match &meta.source {
292 Some(LinkAddr::Ether(mac)) => *mac,
293 _ => {
294 debug!("EtherND: hello Interest has no source MAC in meta — ignoring");
295 return true;
296 }
297 };
298
299 let is_new = ctx
301 .neighbors()
302 .face_for_peer(&sender_mac, &self.iface)
303 .is_none();
304 if is_new {
305 core.strategy
306 .lock()
307 .unwrap()
308 .trigger(crate::TriggerEvent::PassiveDetection);
309 }
310
311 let reply = self.build_hello_data(core, name);
312 ctx.send_on(self.multicast_face_id, reply);
313
314 debug!(
315 "EtherND: received hello Interest from {:?}, sent Data reply",
316 sender_mac
317 );
318 true
319 }
320
321 fn verify_and_ensure_peer(
322 &self,
323 _raw: &Bytes,
324 payload: &HelloPayload,
325 meta: &InboundMeta,
326 _core: &HelloCore,
327 ctx: &dyn DiscoveryContext,
328 ) -> Option<(Name, Option<FaceId>)> {
329 let responder_name = payload.node_name.clone();
330
331 let responder_mac = match &meta.source {
332 Some(LinkAddr::Ether(mac)) => *mac,
333 _ => {
334 debug!("EtherND: hello Data has no source MAC in meta — ignoring");
335 return None;
336 }
337 };
338
339 let peer_face_id = self.ensure_peer(ctx, &responder_name, responder_mac);
340 Some((responder_name, peer_face_id))
341 }
342
343 fn send_multicast(&self, ctx: &dyn DiscoveryContext, pkt: Bytes) {
344 ctx.send_on(self.multicast_face_id, pkt);
345 }
346
347 fn is_multicast_face(&self, face_id: FaceId) -> bool {
348 face_id == self.multicast_face_id
349 }
350
351 fn on_face_down(&self, _face_id: FaceId, _state: &mut HelloState, _ctx: &dyn DiscoveryContext) {
352 }
354
355 fn on_peer_removed(&self, _entry: &NeighborEntry, _state: &mut HelloState) {
356 }
358}
359
360#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::wire::parse_raw_data;
366 use std::str::FromStr;
367
368 fn make_nd() -> EtherNeighborDiscovery {
369 EtherNeighborDiscovery::new(
370 FaceId(1),
371 "eth0",
372 Name::from_str("/ndn/test/node").unwrap(),
373 MacAddr::new([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
374 )
375 }
376
377 #[test]
378 fn hello_interest_format() {
379 let nd = make_nd();
380 let nonce: u32 = 0xDEAD_BEEF;
381 let pkt = nd.build_hello_interest(nonce);
382
383 let parsed = parse_raw_interest(&pkt).unwrap();
384 let comps = parsed.name.components();
385
386 assert_eq!(
387 comps.len(),
388 HELLO_PREFIX_DEPTH + 1,
389 "unexpected component count: {}",
390 comps.len()
391 );
392
393 let last = &comps[HELLO_PREFIX_DEPTH];
394 let decoded_nonce = u32::from_be_bytes(last.value[..4].try_into().unwrap());
395 assert_eq!(decoded_nonce, nonce);
396
397 assert!(
398 parsed.app_params.is_none(),
399 "Interest must have no AppParams"
400 );
401 }
402
403 #[test]
404 fn hello_data_carries_hello_payload() {
405 let nd = make_nd();
406 let interest_name = Name::from_str("/ndn/local/nd/hello/DEADBEEF").unwrap();
407 let pkt = nd.medium.build_hello_data(&nd.core, &interest_name);
408
409 let parsed = parse_raw_data(&pkt).unwrap();
410 assert_eq!(parsed.name, interest_name);
411
412 let content = parsed.content.unwrap();
413 let payload = HelloPayload::decode(&content).unwrap();
414 assert_eq!(payload.node_name, nd.core.node_name);
415 }
416
417 #[test]
418 fn in_hello_served_prefixes_encoded() {
419 let nd = make_nd();
420 nd.set_served_prefixes(vec![
421 Name::from_str("/ndn/edu/test").unwrap(),
422 Name::from_str("/ndn/edu/test2").unwrap(),
423 ]);
424
425 let interest_name = Name::from_str("/ndn/local/nd/hello/DEADBEEF").unwrap();
426 let pkt = nd.medium.build_hello_data(&nd.core, &interest_name);
427
428 let parsed = parse_raw_data(&pkt).unwrap();
429 let payload = HelloPayload::decode(&parsed.content.unwrap()).unwrap();
430 assert_eq!(payload.served_prefixes.len(), 2);
431 assert_eq!(
432 payload.served_prefixes[0],
433 Name::from_str("/ndn/edu/test").unwrap()
434 );
435 }
436
437 #[test]
438 fn neighbor_diffs_piggybacked() {
439 let nd = make_nd();
440 {
441 let mut st = nd.core.state.lock().unwrap();
442 st.recent_diffs.push_back(crate::DiffEntry::Add(
443 Name::from_str("/ndn/peer/alpha").unwrap(),
444 ));
445 }
446 let interest_name = Name::from_str("/ndn/local/nd/hello/1").unwrap();
447 let pkt = nd.medium.build_hello_data(&nd.core, &interest_name);
448 let parsed = parse_raw_data(&pkt).unwrap();
449 let payload = HelloPayload::decode(&parsed.content.unwrap()).unwrap();
450 assert_eq!(payload.neighbor_diffs.len(), 1);
451 assert_eq!(payload.neighbor_diffs[0].entries.len(), 1);
452 }
453
454 #[test]
455 fn protocol_id_and_prefix() {
456 let nd = make_nd();
457 assert_eq!(nd.medium.protocol_id(), PROTOCOL);
458 assert_eq!(nd.core.claimed.len(), 1);
459 assert_eq!(
460 nd.core.claimed[0],
461 Name::from_str(crate::hello::medium::HELLO_PREFIX_STR).unwrap()
462 );
463 }
464
465 #[test]
466 fn from_profile_sets_config() {
467 let nd = EtherNeighborDiscovery::from_profile(
468 FaceId(1),
469 "wlan0",
470 Name::from_str("/ndn/test/node").unwrap(),
471 MacAddr::new([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
472 &DiscoveryProfile::HighMobility,
473 );
474 assert!(nd.core.config.read().unwrap().hello_interval_base < Duration::from_millis(100));
475 }
476}