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}