ndn_discovery/hello/medium.rs
1//! `LinkMedium` trait — abstraction over link-layer differences for discovery.
2//!
3//! UDP and Ethernet neighbor discovery share the same SWIM/hello/probe state
4//! machine but differ in address types, face creation, and packet signing.
5//! Implementing `LinkMedium` provides those customisation points while
6//! [`HelloProtocol<T>`](super::protocol::HelloProtocol) supplies the
7//! common logic.
8
9use std::collections::{HashMap, VecDeque};
10use std::str::FromStr;
11use std::sync::atomic::AtomicU32;
12use std::sync::{Arc, Mutex, RwLock};
13use std::time::Instant;
14
15use bytes::Bytes;
16use ndn_packet::Name;
17use ndn_transport::FaceId;
18
19use crate::config::DiscoveryConfig;
20use crate::strategy::{NeighborProbeStrategy, build_strategy};
21use crate::{DiffEntry, DiscoveryContext, HelloPayload, InboundMeta, NeighborEntry, ProtocolId};
22
23// ─── Shared constants ────────────────────────────────────────────────────────
24
25pub const HELLO_PREFIX_STR: &str = "/ndn/local/nd/hello";
26pub const HELLO_PREFIX_DEPTH: usize = 4;
27pub(crate) const MAX_DIFF_ENTRIES: usize = 16;
28
29// ─── HelloState ──────────────────────────────────────────────────────────────
30
31/// Mutable state shared by all `HelloProtocol<T>` instances.
32///
33/// Contains nonce-keyed maps for outstanding hellos, SWIM probes, relay
34/// bookkeeping, and the recent neighbor-diff queue piggybacked onto outgoing
35/// hello Data packets.
36#[derive(Default)]
37pub struct HelloState {
38 /// Nonce → send_time for outstanding hello probes.
39 pub pending_probes: HashMap<u32, Instant>,
40 /// Recent neighbor additions/removals for SWIM gossip piggyback.
41 pub recent_diffs: VecDeque<DiffEntry>,
42 /// SWIM direct probes: nonce → (sent_at, target_name).
43 pub swim_probes: HashMap<u32, (Instant, Name)>,
44 /// Relay state: relay_nonce → (origin_face, original_interest_name).
45 pub relay_probes: HashMap<u32, (FaceId, Name)>,
46}
47
48impl HelloState {
49 pub fn new() -> Self {
50 Self::default()
51 }
52}
53
54// ─── HelloCore ───────────────────────────────────────────────────────────────
55
56/// Shared (non-link-specific) fields used by `HelloProtocol<T>`.
57///
58/// Exposed to `LinkMedium` implementations so they can access the node name,
59/// config, strategy, and mutable state when building packets or handling
60/// inbound messages.
61///
62/// The `config` field is held behind an `Arc<RwLock<>>` so that the management
63/// handler can update Tier 2 parameters (hello intervals, timeouts, fanouts)
64/// at runtime without restarting the protocol.
65pub struct HelloCore {
66 pub node_name: Name,
67 pub hello_prefix: Name,
68 pub claimed: Vec<Name>,
69 pub nonce_counter: AtomicU32,
70 /// Live-mutable discovery configuration.
71 ///
72 /// Clone the `Arc` via [`HelloCore::config_handle`] to share the same
73 /// config instance with the management handler.
74 pub config: Arc<RwLock<DiscoveryConfig>>,
75 pub strategy: Mutex<Box<dyn NeighborProbeStrategy>>,
76 pub served_prefixes: Mutex<Vec<Name>>,
77 pub state: Mutex<HelloState>,
78}
79
80impl HelloCore {
81 pub fn new(node_name: Name, config: DiscoveryConfig) -> Self {
82 Self::new_shared(node_name, Arc::new(RwLock::new(config)))
83 }
84
85 /// Create with a pre-existing shared config handle.
86 ///
87 /// Use this when the management handler needs to mutate the same config
88 /// instance that the protocol reads from.
89 pub fn new_shared(node_name: Name, config: Arc<RwLock<DiscoveryConfig>>) -> Self {
90 let hello_prefix = Name::from_str(HELLO_PREFIX_STR).expect("static prefix is valid");
91 let mut claimed = vec![hello_prefix.clone()];
92 let (swim_fanout, strategy) = {
93 let cfg = config.read().unwrap();
94 let fanout = cfg.swim_indirect_fanout;
95 let strategy = build_strategy(&cfg);
96 (fanout, strategy)
97 };
98 if swim_fanout > 0 {
99 claimed.push(crate::scope::probe_direct().clone());
100 claimed.push(crate::scope::probe_via().clone());
101 }
102 Self {
103 node_name,
104 hello_prefix,
105 claimed,
106 nonce_counter: AtomicU32::new(1),
107 strategy: Mutex::new(strategy),
108 served_prefixes: Mutex::new(Vec::new()),
109 config,
110 state: Mutex::new(HelloState::new()),
111 }
112 }
113
114 /// Return a cloneable handle to the shared config for use by the management handler.
115 pub fn config_handle(&self) -> Arc<RwLock<DiscoveryConfig>> {
116 Arc::clone(&self.config)
117 }
118}
119
120// ─── LinkMedium trait ────────────────────────────────────────────────────────
121
122/// Abstraction over link-layer differences between discovery protocols.
123///
124/// Implementations provide the link-specific operations (face creation,
125/// address extraction, packet signing) while the shared SWIM/hello/probe
126/// state machine lives in [`HelloProtocol<T>`].
127///
128/// [`HelloProtocol<T>`]: super::hello_protocol::HelloProtocol
129pub trait LinkMedium: Send + Sync + 'static {
130 /// Protocol identifier (e.g. `"udp-nd"`, `"ether-nd"`).
131 fn protocol_id(&self) -> ProtocolId;
132
133 /// Build the hello Data reply for the given Interest name.
134 ///
135 /// UDP signs with Ed25519; Ethernet uses an unsigned placeholder.
136 fn build_hello_data(&self, core: &HelloCore, interest_name: &Name) -> Bytes;
137
138 /// Handle a hello Interest (link-specific dispatch).
139 ///
140 /// Extracts the source address from `meta`, performs any link-specific
141 /// actions (e.g. passive detection for new MACs), builds and sends the
142 /// reply via `ctx.send_on`. Returns `true` if the Interest was consumed.
143 fn handle_hello_interest(
144 &self,
145 raw: &Bytes,
146 incoming_face: FaceId,
147 meta: &InboundMeta,
148 core: &HelloCore,
149 ctx: &dyn DiscoveryContext,
150 ) -> bool;
151
152 /// Verify signature (if applicable), extract source address, and ensure
153 /// a unicast face to the peer exists.
154 ///
155 /// Called by `HelloProtocol::handle_hello_data` after the shared code has
156 /// parsed the hello Data, extracted the nonce, and decoded the
157 /// `HelloPayload`.
158 ///
159 /// Returns `(responder_name, optional_face_id)` on success, or `None`
160 /// to silently drop the packet.
161 fn verify_and_ensure_peer(
162 &self,
163 raw: &Bytes,
164 payload: &HelloPayload,
165 meta: &InboundMeta,
166 core: &HelloCore,
167 ctx: &dyn DiscoveryContext,
168 ) -> Option<(Name, Option<FaceId>)>;
169
170 /// Send a packet on all multicast face(s).
171 fn send_multicast(&self, ctx: &dyn DiscoveryContext, pkt: Bytes);
172
173 /// Whether `face_id` is one of this medium's multicast faces.
174 fn is_multicast_face(&self, face_id: FaceId) -> bool;
175
176 /// Handle face-down event (clean up link-specific state).
177 fn on_face_down(&self, face_id: FaceId, state: &mut HelloState, ctx: &dyn DiscoveryContext);
178
179 /// Clean up link-specific state when a peer is being removed
180 /// (reached miss_limit in the liveness state machine).
181 fn on_peer_removed(&self, entry: &NeighborEntry, state: &mut HelloState);
182}