umsh_node/
node.rs

1use alloc::boxed::Box;
2use alloc::rc::Rc;
3use alloc::vec::Vec;
4use core::cell::RefCell;
5use core::num::NonZeroU32;
6
7use umsh_core::{NodeHint, PublicKey, RouterHint};
8use umsh_mac::{CachedRoute, LocalIdentityId, SendOptions};
9
10#[cfg(feature = "software-crypto")]
11use crate::channel::Channel;
12use crate::dispatch::EventDispatcher;
13use crate::identity_responder::{
14    IdentityRequestContext, IdentityResponder, IdentityResponsePlan, NodeIdentityProfile,
15    RespondDecision, default_respond_policy,
16};
17use crate::mac::MacBackend;
18use crate::peer::PeerConnection;
19#[cfg(feature = "software-crypto")]
20use crate::pfs::{PfsSessionManager, PfsState};
21use crate::receive::ReceivedPacketRef;
22use crate::ticket::{SendProgressTicket, SendToken};
23use crate::transport::Transport;
24use crate::{AppEncodeError, OwnedMacCommand};
25
26/// Per-node shared membership state. All cloned `LocalNode` handles and
27/// their `BoundChannel`s share the same instance via `Rc<RefCell<...>>`.
28pub(crate) struct NodeMembership {
29    #[cfg(feature = "software-crypto")]
30    pub channels: Vec<ChannelMembershipEntry>,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub(crate) struct SubscriptionHandle(NonZeroU32);
35
36/// Owned subscription guard.
37///
38/// Dropping the value automatically unregisters the callback.
39pub struct Subscription {
40    cancel: Rc<RefCell<Option<Box<dyn FnMut() -> bool>>>>,
41}
42
43impl Subscription {
44    pub(crate) fn new(cancel: impl FnMut() -> bool + 'static) -> Self {
45        Self {
46            cancel: Rc::new(RefCell::new(Some(Box::new(cancel)))),
47        }
48    }
49
50    /// Unregister immediately instead of waiting for drop.
51    pub fn unsubscribe(self) -> bool {
52        Self::run_cancel(&self.cancel)
53    }
54
55    fn run_cancel(cancel: &Rc<RefCell<Option<Box<dyn FnMut() -> bool>>>>) -> bool {
56        let Some(mut cancel) = cancel.borrow_mut().take() else {
57            return false;
58        };
59        cancel()
60    }
61}
62
63impl Drop for Subscription {
64    fn drop(&mut self) {
65        let _ = Self::run_cancel(&self.cancel);
66    }
67}
68
69pub(crate) struct HandlerTable<T> {
70    slots: Vec<Option<T>>,
71}
72
73impl<T> Default for HandlerTable<T> {
74    fn default() -> Self {
75        Self { slots: Vec::new() }
76    }
77}
78
79impl<T> HandlerTable<T> {
80    pub(crate) fn insert(&mut self, handler: T) -> SubscriptionHandle {
81        if let Some((index, slot)) = self
82            .slots
83            .iter_mut()
84            .enumerate()
85            .find(|(_, slot)| slot.is_none())
86        {
87            *slot = Some(handler);
88            return SubscriptionHandle(NonZeroU32::new((index + 1) as u32).unwrap());
89        }
90        self.slots.push(Some(handler));
91        SubscriptionHandle(NonZeroU32::new(self.slots.len() as u32).unwrap())
92    }
93
94    pub(crate) fn remove(&mut self, handle: SubscriptionHandle) -> bool {
95        let index = handle.0.get() as usize - 1;
96        let Some(slot) = self.slots.get_mut(index) else {
97            return false;
98        };
99        slot.take().is_some()
100    }
101
102    fn any_mut(&mut self, mut f: impl FnMut(&mut T) -> bool) -> bool {
103        for slot in &mut self.slots {
104            let Some(handler) = slot.as_mut() else {
105                continue;
106            };
107            if f(handler) {
108                return true;
109            }
110        }
111        false
112    }
113
114    fn for_each_mut(&mut self, mut f: impl FnMut(&mut T)) {
115        for slot in &mut self.slots {
116            let Some(handler) = slot.as_mut() else {
117                continue;
118            };
119            f(handler);
120        }
121    }
122}
123
124pub(crate) struct PendingPing {
125    pub nonce: u16,
126    pub peer: PublicKey,
127    pub sent_at_ms: u64,
128    pub deadline_ms: u64,
129}
130
131/// Measurements attached to an authenticated echo response.
132///
133/// RSSI, SNR, and LQI describe the final radio hop into this node. Route
134/// hints are the authenticated trace-route entries accumulated by repeaters;
135/// endpoints are not included in that list.
136#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct PongMetadata {
138    pub round_trip_ms: u64,
139    pub hop_count: Option<u8>,
140    pub route_hints: Vec<RouterHint>,
141    pub rssi_dbm: Option<i16>,
142    pub snr_centibels: Option<i16>,
143    pub lqi: Option<u8>,
144}
145
146pub(crate) struct PeerSubscriptions {
147    peer: PublicKey,
148    pub(crate) receive_handlers: HandlerTable<Box<dyn FnMut(&ReceivedPacketRef<'_>) -> bool>>,
149    pub(crate) ack_received_handlers: HandlerTable<Box<dyn FnMut(SendToken)>>,
150    pub(crate) ack_timeout_handlers: HandlerTable<Box<dyn FnMut(SendToken)>>,
151    pub(crate) pfs_established_handlers: HandlerTable<Box<dyn FnMut()>>,
152    pub(crate) pfs_ended_handlers: HandlerTable<Box<dyn FnMut()>>,
153    pub(crate) pong_handlers: HandlerTable<Box<dyn FnMut(u64)>>,
154    pub(crate) ping_timeout_handlers: HandlerTable<Box<dyn FnMut()>>,
155}
156
157impl PeerSubscriptions {
158    fn new(peer: PublicKey) -> Self {
159        Self {
160            peer,
161            receive_handlers: HandlerTable::default(),
162            ack_received_handlers: HandlerTable::default(),
163            ack_timeout_handlers: HandlerTable::default(),
164            pfs_established_handlers: HandlerTable::default(),
165            pfs_ended_handlers: HandlerTable::default(),
166            pong_handlers: HandlerTable::default(),
167            ping_timeout_handlers: HandlerTable::default(),
168        }
169    }
170}
171
172pub(crate) struct LocalNodeState {
173    receive_handlers: HandlerTable<Box<dyn FnMut(&ReceivedPacketRef<'_>) -> bool>>,
174    node_discovered_handlers: HandlerTable<Box<dyn FnMut(PublicKey, Option<&str>)>>,
175    beacon_handlers: HandlerTable<Box<dyn FnMut(NodeHint, Option<PublicKey>)>>,
176    mac_command_handlers: HandlerTable<Box<dyn FnMut(PublicKey, &OwnedMacCommand)>>,
177    transmitted_handlers: HandlerTable<Box<dyn FnMut(&[u8])>>,
178    ack_received_handlers: HandlerTable<Box<dyn FnMut(PublicKey, SendToken)>>,
179    ack_timeout_handlers: HandlerTable<Box<dyn FnMut(PublicKey, SendToken)>>,
180    pfs_established_handlers: HandlerTable<Box<dyn FnMut(PublicKey)>>,
181    pfs_ended_handlers: HandlerTable<Box<dyn FnMut(PublicKey)>>,
182    pfs_failed_handlers: HandlerTable<Box<dyn FnMut(PublicKey, PfsFailure)>>,
183    pong_handlers: HandlerTable<Box<dyn FnMut(PublicKey, u64)>>,
184    pong_metadata_handlers: HandlerTable<Box<dyn FnMut(PublicKey, &PongMetadata)>>,
185    ping_timeout_handlers: HandlerTable<Box<dyn FnMut(PublicKey)>>,
186    pending_pings: Vec<PendingPing>,
187    peer_subscriptions: Vec<PeerSubscriptions>,
188    identity_responder: Option<IdentityResponder>,
189    #[cfg(feature = "software-crypto")]
190    pfs: PfsSessionManager,
191}
192
193impl LocalNodeState {
194    pub(crate) fn new() -> Self {
195        Self {
196            receive_handlers: HandlerTable::default(),
197            node_discovered_handlers: HandlerTable::default(),
198            beacon_handlers: HandlerTable::default(),
199            mac_command_handlers: HandlerTable::default(),
200            transmitted_handlers: HandlerTable::default(),
201            ack_received_handlers: HandlerTable::default(),
202            ack_timeout_handlers: HandlerTable::default(),
203            pfs_established_handlers: HandlerTable::default(),
204            pfs_ended_handlers: HandlerTable::default(),
205            pfs_failed_handlers: HandlerTable::default(),
206            pong_handlers: HandlerTable::default(),
207            pong_metadata_handlers: HandlerTable::default(),
208            ping_timeout_handlers: HandlerTable::default(),
209            pending_pings: Vec::new(),
210            peer_subscriptions: Vec::new(),
211            identity_responder: None,
212            #[cfg(feature = "software-crypto")]
213            pfs: PfsSessionManager::new(),
214        }
215    }
216
217    pub(crate) fn peer_subscriptions_mut(&mut self, peer: PublicKey) -> &mut PeerSubscriptions {
218        if let Some(index) = self
219            .peer_subscriptions
220            .iter()
221            .position(|entry| entry.peer == peer)
222        {
223            return &mut self.peer_subscriptions[index];
224        }
225        self.peer_subscriptions.push(PeerSubscriptions::new(peer));
226        self.peer_subscriptions
227            .last_mut()
228            .expect("peer subscriptions just inserted")
229    }
230
231    pub(crate) fn find_peer_subscriptions_mut(
232        &mut self,
233        peer: PublicKey,
234    ) -> Option<&mut PeerSubscriptions> {
235        self.peer_subscriptions
236            .iter_mut()
237            .find(|entry| entry.peer == peer)
238    }
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242#[cfg(feature = "software-crypto")]
243pub enum PfsStatus {
244    Inactive,
245    Requested,
246    Active {
247        local_ephemeral_id: LocalIdentityId,
248        peer_ephemeral: PublicKey,
249        expires_ms: u64,
250    },
251}
252
253#[derive(Clone, Copy, Debug, PartialEq, Eq)]
254pub(crate) enum PfsLifecycle {
255    Established(PublicKey),
256    Ended(PublicKey),
257}
258
259/// Why a local PFS negotiation step failed. Surfaced to applications via
260/// [`LocalNode::on_pfs_failed`] so a failed/stalled negotiation reports a
261/// reason instead of silently doing nothing.
262#[derive(Clone, Copy, Debug, PartialEq, Eq)]
263pub enum PfsFailure {
264    /// No free identity or peer slot to activate the ephemeral session —
265    /// e.g. the MAC `IDENTITIES`/peer table is exhausted, or more concurrent
266    /// PFS sessions were requested than there are ephemeral identity slots.
267    Capacity,
268    /// A PFS response or teardown referenced a session that does not exist.
269    SessionMissing,
270    /// Crypto failure while deriving the ephemeral session keys.
271    Crypto,
272    /// Failed to transmit a PFS control frame to the peer.
273    Send,
274    /// A sent PFS request was not answered before its deadline (the peer never
275    /// responded, or its response was lost).
276    Timeout,
277    /// Any other node-layer failure during PFS processing.
278    Other,
279}
280
281#[cfg(feature = "software-crypto")]
282pub(crate) struct ChannelMembershipEntry {
283    pub channel: Channel,
284    /// Monotonically increasing per this (node, channel) pair.
285    /// Bumped on leave; BoundChannel snapshots this at creation.
286    pub generation: u64,
287    /// False after leave(); entry kept until re-joined or GC'd.
288    pub active: bool,
289}
290
291impl NodeMembership {
292    pub fn new() -> Self {
293        Self {
294            #[cfg(feature = "software-crypto")]
295            channels: Vec::new(),
296        }
297    }
298}
299
300/// Errors produced by node-layer operations.
301#[derive(Clone, PartialEq, Eq)]
302pub enum NodeError<M: MacBackend> {
303    /// Underlying MAC-layer failure.
304    Mac(crate::mac::MacBackendError<M::SendError, M::CapacityError>),
305    /// The node has left this channel since the handle was created.
306    ChannelLeft,
307    /// The peer is not registered.
308    PeerMissing,
309    /// Control-payload encode failure.
310    AppEncode(AppEncodeError),
311    /// The referenced PFS session was missing.
312    #[cfg(feature = "software-crypto")]
313    PfsSessionMissing,
314    /// The PFS session table is full.
315    #[cfg(feature = "software-crypto")]
316    PfsSessionTableFull,
317    /// Crypto failure during PFS processing.
318    #[cfg(feature = "software-crypto")]
319    Crypto(umsh_crypto::CryptoError),
320}
321
322impl<M: MacBackend> NodeError<M> {
323    /// Coarse classification of this error for surfacing PFS failures to
324    /// applications (the concrete error type is generic over `M`, so callers
325    /// that just want to report a failure use this instead).
326    pub(crate) fn pfs_failure(&self) -> PfsFailure {
327        use crate::mac::MacBackendError;
328        match self {
329            NodeError::Mac(MacBackendError::Capacity(_)) => PfsFailure::Capacity,
330            NodeError::Mac(MacBackendError::Send(_)) => PfsFailure::Send,
331            #[cfg(feature = "software-crypto")]
332            NodeError::PfsSessionTableFull => PfsFailure::Capacity,
333            #[cfg(feature = "software-crypto")]
334            NodeError::PfsSessionMissing => PfsFailure::SessionMissing,
335            #[cfg(feature = "software-crypto")]
336            NodeError::Crypto(_) => PfsFailure::Crypto,
337            _ => PfsFailure::Other,
338        }
339    }
340}
341
342impl<M> core::fmt::Debug for NodeError<M>
343where
344    M: MacBackend,
345    M::SendError: core::fmt::Debug,
346    M::CapacityError: core::fmt::Debug,
347{
348    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349        match self {
350            Self::Mac(e) => f.debug_tuple("Mac").field(e).finish(),
351            Self::ChannelLeft => f.write_str("ChannelLeft"),
352            Self::PeerMissing => f.write_str("PeerMissing"),
353            Self::AppEncode(e) => f.debug_tuple("AppEncode").field(e).finish(),
354            #[cfg(feature = "software-crypto")]
355            Self::PfsSessionMissing => f.write_str("PfsSessionMissing"),
356            #[cfg(feature = "software-crypto")]
357            Self::PfsSessionTableFull => f.write_str("PfsSessionTableFull"),
358            #[cfg(feature = "software-crypto")]
359            Self::Crypto(e) => f.debug_tuple("Crypto").field(e).finish(),
360        }
361    }
362}
363
364impl<M: MacBackend> From<crate::mac::MacBackendError<M::SendError, M::CapacityError>>
365    for NodeError<M>
366{
367    fn from(e: crate::mac::MacBackendError<M::SendError, M::CapacityError>) -> Self {
368        Self::Mac(e)
369    }
370}
371
372impl<M: MacBackend> From<AppEncodeError> for NodeError<M> {
373    fn from(e: AppEncodeError) -> Self {
374        Self::AppEncode(e)
375    }
376}
377
378#[cfg(feature = "software-crypto")]
379impl<M: MacBackend> From<umsh_crypto::CryptoError> for NodeError<M> {
380    fn from(e: umsh_crypto::CryptoError) -> Self {
381        Self::Crypto(e)
382    }
383}
384
385/// Per-identity application handle.
386///
387/// `LocalNode` owns its channel membership set via shared interior state.
388/// Different `LocalNode` instances (for different identities) may join
389/// different channel sets.
390#[derive(Clone)]
391pub struct LocalNode<M: MacBackend> {
392    identity_id: LocalIdentityId,
393    mac: M,
394    dispatcher: Rc<RefCell<EventDispatcher>>,
395    #[allow(dead_code)] // Used by channel methods (software-crypto feature)
396    membership: Rc<RefCell<NodeMembership>>,
397    state: Rc<RefCell<LocalNodeState>>,
398}
399
400impl<M: MacBackend> LocalNode<M> {
401    /// Create a new local node.
402    pub(crate) fn new(
403        identity_id: LocalIdentityId,
404        mac: M,
405        dispatcher: Rc<RefCell<EventDispatcher>>,
406        membership: Rc<RefCell<NodeMembership>>,
407        state: Rc<RefCell<LocalNodeState>>,
408    ) -> Self {
409        Self {
410            identity_id,
411            mac,
412            dispatcher,
413            membership,
414            state,
415        }
416    }
417
418    /// The identity slot this node operates on.
419    pub fn identity_id(&self) -> LocalIdentityId {
420        self.identity_id
421    }
422
423    /// Create a peer connection (registers peer in MAC if new).
424    pub async fn peer(&self, key: PublicKey) -> Result<PeerConnection<Self>, NodeError<M>> {
425        self.mac.add_peer(key).await?;
426        Ok(PeerConnection::new(self.clone(), key))
427    }
428
429    /// Remove a peer from the MAC and drop this node's per-peer bookkeeping:
430    /// outstanding pings and per-peer subscription tables. Returns whether
431    /// the peer was registered in the MAC. Idempotent — removing an unknown
432    /// peer still clears any node-layer residue and reports `false`.
433    pub async fn remove_peer(&self, key: &PublicKey) -> bool {
434        let removed = self.mac.remove_peer(key).await;
435        let mut state = self.state.borrow_mut();
436        state.pending_pings.retain(|ping| ping.peer != *key);
437        state.peer_subscriptions.retain(|entry| entry.peer != *key);
438        removed
439    }
440
441    /// Return the live TX frame counter for this node's identity, if available.
442    pub async fn frame_counter(&self) -> Option<u32> {
443        self.mac.frame_counter(self.identity_id).await
444    }
445
446    /// Return the persisted TX frame-counter boundary for this node's identity.
447    pub async fn persisted_frame_counter(&self) -> Option<u32> {
448        self.mac.persisted_frame_counter(self.identity_id).await
449    }
450
451    /// Invoke `f` for every peer currently registered in the MAC-layer peer registry.
452    ///
453    /// Covers all known peers, not just those with an active crypto session.
454    pub async fn for_each_peer(&self, f: &mut dyn FnMut(PublicKey)) {
455        self.mac.for_each_peer(f).await
456    }
457
458    /// Invoke `f` for each peer with established crypto state, passing the
459    /// peer's public key, last-accepted RX counter, and persisted RX boundary.
460    pub async fn for_each_peer_counter(&self, f: &mut dyn FnMut(PublicKey, u32, u32)) {
461        self.mac.for_each_peer_counter(self.identity_id, f).await
462    }
463
464    /// Return the route the MAC has cached for `peer`, if any.
465    ///
466    /// A cached route is what the next send to this peer will use, so this is
467    /// the state to inspect when traffic takes an unexpected path.
468    pub async fn peer_route(&self, peer: &PublicKey) -> Option<CachedRoute> {
469        self.mac.peer_route(peer).await
470    }
471
472    /// Forget the route cached for `peer`, returning whether one was held.
473    ///
474    /// Sends fall back to the default delivery mode until an inbound packet
475    /// teaches a route again; the peer and its crypto state are untouched.
476    pub async fn clear_peer_route(&self, peer: &PublicKey) -> bool {
477        self.mac.clear_peer_route(peer).await
478    }
479
480    fn add_receive_handler<F>(&self, handler: F) -> SubscriptionHandle
481    where
482        F: FnMut(&ReceivedPacketRef<'_>) -> bool + 'static,
483    {
484        self.state
485            .borrow_mut()
486            .receive_handlers
487            .insert(Box::new(handler))
488    }
489
490    pub fn on_receive<F>(&self, handler: F) -> Subscription
491    where
492        F: FnMut(&ReceivedPacketRef<'_>) -> bool + 'static,
493    {
494        let handle = self.add_receive_handler(handler);
495        let state = self.state.clone();
496        Subscription::new(move || state.borrow_mut().receive_handlers.remove(handle))
497    }
498
499    fn add_node_discovered_handler<F>(&self, handler: F) -> SubscriptionHandle
500    where
501        F: FnMut(PublicKey, Option<&str>) + 'static,
502    {
503        self.state
504            .borrow_mut()
505            .node_discovered_handlers
506            .insert(Box::new(handler))
507    }
508
509    pub fn on_node_discovered<F>(&self, handler: F) -> Subscription
510    where
511        F: FnMut(PublicKey, Option<&str>) + 'static,
512    {
513        let handle = self.add_node_discovered_handler(handler);
514        let state = self.state.clone();
515        Subscription::new(move || state.borrow_mut().node_discovered_handlers.remove(handle))
516    }
517
518    fn add_beacon_handler<F>(&self, handler: F) -> SubscriptionHandle
519    where
520        F: FnMut(NodeHint, Option<PublicKey>) + 'static,
521    {
522        self.state
523            .borrow_mut()
524            .beacon_handlers
525            .insert(Box::new(handler))
526    }
527
528    pub fn on_beacon<F>(&self, handler: F) -> Subscription
529    where
530        F: FnMut(NodeHint, Option<PublicKey>) + 'static,
531    {
532        let handle = self.add_beacon_handler(handler);
533        let state = self.state.clone();
534        Subscription::new(move || state.borrow_mut().beacon_handlers.remove(handle))
535    }
536
537    fn add_mac_command_handler<F>(&self, handler: F) -> SubscriptionHandle
538    where
539        F: FnMut(PublicKey, &OwnedMacCommand) + 'static,
540    {
541        self.state
542            .borrow_mut()
543            .mac_command_handlers
544            .insert(Box::new(handler))
545    }
546
547    pub fn on_mac_command<F>(&self, handler: F) -> Subscription
548    where
549        F: FnMut(PublicKey, &OwnedMacCommand) + 'static,
550    {
551        let handle = self.add_mac_command_handler(handler);
552        let state = self.state.clone();
553        Subscription::new(move || state.borrow_mut().mac_command_handlers.remove(handle))
554    }
555
556    /// Enable the built-in Identity Request responder with a custom respond
557    /// policy.
558    ///
559    /// The node will answer a matching [Identity Request](crate::mac_command)
560    /// with an authenticated unicast identity response (see
561    /// [`identity_responder`](crate::identity_responder)). `policy` is the
562    /// registerable discriminator invoked for each selected request; return
563    /// [`RespondDecision::Ignore`] to decline. Replaces any previously enabled
564    /// responder.
565    pub fn enable_identity_responder<P>(&self, profile: NodeIdentityProfile, policy: P)
566    where
567        P: FnMut(&IdentityRequestContext<'_>) -> RespondDecision + 'static,
568    {
569        self.state.borrow_mut().identity_responder = Some(IdentityResponder {
570            profile,
571            policy: Box::new(policy),
572        });
573    }
574
575    /// Enable the Identity Request responder with the
576    /// [`default_respond_policy`](crate::identity_responder::default_respond_policy).
577    pub fn enable_identity_responder_default(&self, profile: NodeIdentityProfile) {
578        self.enable_identity_responder(profile, default_respond_policy);
579    }
580
581    /// Disable the Identity Request responder, if enabled.
582    pub fn disable_identity_responder(&self) {
583        self.state.borrow_mut().identity_responder = None;
584    }
585
586    /// Update the live fields of the enabled responder's identity profile
587    /// (e.g. refresh `location`/`altitude_m` from a GPS fix). No-op if the
588    /// responder is not enabled.
589    pub fn update_identity_profile(&self, f: impl FnOnce(&mut NodeIdentityProfile)) {
590        if let Some(responder) = self.state.borrow_mut().identity_responder.as_mut() {
591            f(&mut responder.profile);
592        }
593    }
594
595    /// Read the enabled responder's identity profile, returning `None`
596    /// if the responder is not enabled.
597    ///
598    /// The point of reading it rather than reconstructing it is that
599    /// there is one statement of what this node is, and every framing of
600    /// it — the Identity Request reply, a standalone signed
601    /// advertisement, a local-control read — has to be that same
602    /// statement.
603    pub fn with_identity_profile<R>(&self, f: impl FnOnce(&NodeIdentityProfile) -> R) -> Option<R> {
604        self.state
605            .borrow()
606            .identity_responder
607            .as_ref()
608            .map(|responder| f(&responder.profile))
609    }
610
611    /// Evaluate an incoming Identity Request against the enabled responder,
612    /// producing a reply plan the async pump can execute. Returns `None` when
613    /// no responder is enabled, the request does not select this node, or the
614    /// policy declines.
615    pub(crate) fn evaluate_identity_request(
616        &self,
617        packet: &ReceivedPacketRef<'_>,
618        from: PublicKey,
619        options: &[u8],
620    ) -> Option<IdentityResponsePlan> {
621        // Flood management for solicitations that can reach many nodes: a
622        // broadcast (or multicast) Identity Request must not carry a routing
623        // constraint (a Route option is fine only when empty) — a steered
624        // request is still on its way to the neighbourhood it meant to ask.
625        //
626        // A request that no FILTER_NODE_HINT narrows may additionally be
627        // answered by every node it reaches, so it must also arrive
628        // unrepeated, FHOPS absent or fully zero; flood routing such a request
629        // multiplies replies across the mesh. A hint-filtered request names
630        // one answering node and so may travel as far as it likes.
631        let filters = crate::mac_command::IdentityRequestFilters::new(options);
632        if matches!(
633            packet.packet_family(),
634            crate::PacketFamily::Broadcast | crate::PacketFamily::Multicast
635        ) {
636            if packet.source_route().is_some_and(|route| !route.is_empty()) {
637                return None;
638            }
639            if !filters.hint_filtered() && packet.flood_hops().is_some_and(|hops| hops.0 != 0) {
640                return None;
641            }
642        }
643        let ctx = IdentityRequestContext {
644            from_key: from,
645            from_hint: packet.from_hint(),
646            source_authenticated: packet.source_authenticated(),
647            has_full_source: packet.has_full_source(),
648            channel: packet.channel().map(|c| c.id()),
649            family: packet.packet_family(),
650            filters,
651            rssi: packet.rssi(),
652            snr: packet.snr(),
653        };
654        self.state
655            .borrow_mut()
656            .identity_responder
657            .as_mut()?
658            .evaluate(&ctx)
659    }
660
661    /// Send a resolved Identity Request reply as an authenticated unicast.
662    ///
663    /// Uses the node's long-term identity (not a PFS ephemeral). Relies on the
664    /// crypto state the MAC already resolved for the requester — permanent or
665    /// transient — and never promotes/pins the peer. Failures are dropped: the
666    /// requester can always ask again.
667    /// The widest random hold applied to a reply to a broadcast/multicast
668    /// solicitation, per the Identity Request flood-management rules.
669    const IDENTITY_RESPONSE_MAX_DELAY_MS: u16 = 30_000;
670    const IDENTITY_RESPONSE_MIN_DELAY_MS: u16 = 500;
671
672    pub(crate) async fn send_identity_response(&self, plan: IdentityResponsePlan) {
673        let mut options = SendOptions::default();
674        if plan.full_source {
675            options = options.with_full_source();
676        }
677        if plan.no_flood {
678            // The solicitation named no single node, so it was allowed one hop
679            // and no more; the reply carries no FHOPS field so no repeater can
680            // flood it back across the mesh.
681            options = options.no_flood();
682        }
683        if plan.delayed {
684            // Every node the solicitation selected is answering the same
685            // frame; a random hold spreads the replies across the window.
686            // Channel-activity failures then follow the MAC's normal bounded
687            // CCA backoff-and-retry.
688            let mut jitter = [0u8; 2];
689            self.mac.fill_random(&mut jitter).await;
690            let delay = u16::from_be_bytes(jitter)
691                % (Self::IDENTITY_RESPONSE_MAX_DELAY_MS - Self::IDENTITY_RESPONSE_MIN_DELAY_MS + 1)
692                + Self::IDENTITY_RESPONSE_MIN_DELAY_MS;
693            options = options.with_tx_delay_ms(delay);
694        }
695        // A broadcast solicitation's source is not auto-registered on
696        // receive, so the requester may be a complete stranger. Take a
697        // transient slot for them — never a pinned one — so the unicast
698        // below has somewhere to go.
699        let _ = self.mac.ensure_transient_peer(&plan.to).await;
700        let _ = self
701            .mac
702            .send_unicast(self.identity_id, &plan.to, &plan.framed, &options)
703            .await;
704    }
705
706    fn add_transmitted_handler<F>(&self, handler: F) -> SubscriptionHandle
707    where
708        F: FnMut(&[u8]) + 'static,
709    {
710        self.state
711            .borrow_mut()
712            .transmitted_handlers
713            .insert(Box::new(handler))
714    }
715
716    /// Subscribe to raw on-wire bytes of every frame successfully handed to the radio.
717    pub fn on_transmitted<F>(&self, handler: F) -> Subscription
718    where
719        F: FnMut(&[u8]) + 'static,
720    {
721        let handle = self.add_transmitted_handler(handler);
722        let state = self.state.clone();
723        Subscription::new(move || state.borrow_mut().transmitted_handlers.remove(handle))
724    }
725
726    fn add_ack_received_handler<F>(&self, handler: F) -> SubscriptionHandle
727    where
728        F: FnMut(PublicKey, SendToken) + 'static,
729    {
730        self.state
731            .borrow_mut()
732            .ack_received_handlers
733            .insert(Box::new(handler))
734    }
735
736    pub fn on_ack_received<F>(&self, handler: F) -> Subscription
737    where
738        F: FnMut(PublicKey, SendToken) + 'static,
739    {
740        let handle = self.add_ack_received_handler(handler);
741        let state = self.state.clone();
742        Subscription::new(move || state.borrow_mut().ack_received_handlers.remove(handle))
743    }
744
745    fn add_ack_timeout_handler<F>(&self, handler: F) -> SubscriptionHandle
746    where
747        F: FnMut(PublicKey, SendToken) + 'static,
748    {
749        self.state
750            .borrow_mut()
751            .ack_timeout_handlers
752            .insert(Box::new(handler))
753    }
754
755    pub fn on_ack_timeout<F>(&self, handler: F) -> Subscription
756    where
757        F: FnMut(PublicKey, SendToken) + 'static,
758    {
759        let handle = self.add_ack_timeout_handler(handler);
760        let state = self.state.clone();
761        Subscription::new(move || state.borrow_mut().ack_timeout_handlers.remove(handle))
762    }
763
764    fn add_pfs_established_handler<F>(&self, handler: F) -> SubscriptionHandle
765    where
766        F: FnMut(PublicKey) + 'static,
767    {
768        self.state
769            .borrow_mut()
770            .pfs_established_handlers
771            .insert(Box::new(handler))
772    }
773
774    pub fn on_pfs_established<F>(&self, handler: F) -> Subscription
775    where
776        F: FnMut(PublicKey) + 'static,
777    {
778        let handle = self.add_pfs_established_handler(handler);
779        let state = self.state.clone();
780        Subscription::new(move || state.borrow_mut().pfs_established_handlers.remove(handle))
781    }
782
783    fn add_pfs_ended_handler<F>(&self, handler: F) -> SubscriptionHandle
784    where
785        F: FnMut(PublicKey) + 'static,
786    {
787        self.state
788            .borrow_mut()
789            .pfs_ended_handlers
790            .insert(Box::new(handler))
791    }
792
793    pub fn on_pfs_ended<F>(&self, handler: F) -> Subscription
794    where
795        F: FnMut(PublicKey) + 'static,
796    {
797        let handle = self.add_pfs_ended_handler(handler);
798        let state = self.state.clone();
799        Subscription::new(move || state.borrow_mut().pfs_ended_handlers.remove(handle))
800    }
801
802    fn add_pfs_failed_handler<F>(&self, handler: F) -> SubscriptionHandle
803    where
804        F: FnMut(PublicKey, PfsFailure) + 'static,
805    {
806        self.state
807            .borrow_mut()
808            .pfs_failed_handlers
809            .insert(Box::new(handler))
810    }
811
812    /// Subscribe to PFS negotiation failures for any peer. The handler is
813    /// invoked with the peer's long-term key and a coarse [`PfsFailure`]
814    /// reason whenever a local PFS step (accepting a request, completing a
815    /// response, or tearing down) fails — so a stalled negotiation reports a
816    /// reason instead of silently doing nothing.
817    pub fn on_pfs_failed<F>(&self, handler: F) -> Subscription
818    where
819        F: FnMut(PublicKey, PfsFailure) + 'static,
820    {
821        let handle = self.add_pfs_failed_handler(handler);
822        let state = self.state.clone();
823        Subscription::new(move || state.borrow_mut().pfs_failed_handlers.remove(handle))
824    }
825
826    fn add_pong_handler<F>(&self, handler: F) -> SubscriptionHandle
827    where
828        F: FnMut(PublicKey, u64) + 'static,
829    {
830        self.state
831            .borrow_mut()
832            .pong_handlers
833            .insert(Box::new(handler))
834    }
835
836    pub fn on_pong<F>(&self, handler: F) -> Subscription
837    where
838        F: FnMut(PublicKey, u64) + 'static,
839    {
840        let handle = self.add_pong_handler(handler);
841        let state = self.state.clone();
842        Subscription::new(move || state.borrow_mut().pong_handlers.remove(handle))
843    }
844
845    pub fn on_pong_with_metadata<F>(&self, handler: F) -> Subscription
846    where
847        F: FnMut(PublicKey, &PongMetadata) + 'static,
848    {
849        let handle = self
850            .state
851            .borrow_mut()
852            .pong_metadata_handlers
853            .insert(Box::new(handler));
854        let state = self.state.clone();
855        Subscription::new(move || state.borrow_mut().pong_metadata_handlers.remove(handle))
856    }
857
858    fn add_ping_timeout_handler<F>(&self, handler: F) -> SubscriptionHandle
859    where
860        F: FnMut(PublicKey) + 'static,
861    {
862        self.state
863            .borrow_mut()
864            .ping_timeout_handlers
865            .insert(Box::new(handler))
866    }
867
868    pub fn on_ping_timeout<F>(&self, handler: F) -> Subscription
869    where
870        F: FnMut(PublicKey) + 'static,
871    {
872        let handle = self.add_ping_timeout_handler(handler);
873        let state = self.state.clone();
874        Subscription::new(move || state.borrow_mut().ping_timeout_handlers.remove(handle))
875    }
876
877    pub(crate) fn record_ping(
878        &self,
879        nonce: u16,
880        peer: PublicKey,
881        sent_at_ms: u64,
882        deadline_ms: u64,
883    ) {
884        self.state.borrow_mut().pending_pings.push(PendingPing {
885            nonce,
886            peer,
887            sent_at_ms,
888            deadline_ms,
889        });
890    }
891
892    pub(crate) async fn now_ms(&self) -> u64 {
893        self.mac.now_ms().await
894    }
895
896    pub(crate) async fn fill_random(&self, dest: &mut [u8]) {
897        self.mac.fill_random(dest).await
898    }
899
900    /// Called when an EchoResponse arrives. Matches against pending pings and fires pong handlers.
901    pub(crate) fn match_pong(
902        &self,
903        from: PublicKey,
904        data: &[u8],
905        packet: &ReceivedPacketRef<'_>,
906        now_ms: u64,
907    ) {
908        if data.len() < 2 {
909            return;
910        }
911        let nonce = u16::from_be_bytes([data[0], data[1]]);
912        let mut state = self.state.borrow_mut();
913        let idx = state
914            .pending_pings
915            .iter()
916            .position(|p| p.nonce == nonce && p.peer == from);
917        if let Some(idx) = idx {
918            let ping = state.pending_pings.swap_remove(idx);
919            let rtt_ms = now_ms.saturating_sub(ping.sent_at_ms);
920            let route_hints = packet.trace_route_hops().collect::<Vec<_>>();
921            let hop_count = packet
922                .flood_hops()
923                .map(|hops| hops.accumulated().saturating_add(1))
924                .or_else(|| {
925                    packet.trace_route().is_some().then(|| {
926                        u8::try_from(route_hints.len())
927                            .unwrap_or(u8::MAX)
928                            .saturating_add(1)
929                    })
930                });
931            let metadata = PongMetadata {
932                round_trip_ms: rtt_ms,
933                hop_count,
934                route_hints,
935                rssi_dbm: packet.rssi(),
936                snr_centibels: packet.snr().map(|snr| snr.as_centibels()),
937                lqi: packet.lqi().map(core::num::NonZeroU8::get),
938            };
939            if let Some(entry) = state.peer_subscriptions.iter_mut().find(|e| e.peer == from) {
940                entry.pong_handlers.for_each_mut(|h| h(rtt_ms));
941            }
942            state.pong_handlers.for_each_mut(|h| h(from, rtt_ms));
943            state
944                .pong_metadata_handlers
945                .for_each_mut(|handler| handler(from, &metadata));
946        }
947    }
948
949    /// Called periodically by the host timeout service. Fires timeout handlers for expired pings.
950    pub(crate) fn expire_pings(&self, now_ms: u64) {
951        let mut state = self.state.borrow_mut();
952        let mut i = 0;
953        while i < state.pending_pings.len() {
954            if now_ms >= state.pending_pings[i].deadline_ms {
955                let ping = state.pending_pings.swap_remove(i);
956                if let Some(entry) = state
957                    .peer_subscriptions
958                    .iter_mut()
959                    .find(|e| e.peer == ping.peer)
960                {
961                    entry.ping_timeout_handlers.for_each_mut(|h| h());
962                }
963                state.ping_timeout_handlers.for_each_mut(|h| h(ping.peer));
964            } else {
965                i += 1;
966            }
967        }
968    }
969
970    #[cfg(feature = "software-crypto")]
971    pub async fn request_pfs(
972        &self,
973        peer: &PublicKey,
974        duration_minutes: u16,
975        options: &SendOptions,
976    ) -> Result<SendProgressTicket, NodeError<M>> {
977        let receipt = self
978            .state
979            .borrow_mut()
980            .pfs
981            .request_session(&self.mac, self.identity_id, peer, duration_minutes, options)
982            .await?;
983        Ok(self.register_ack_send(self.identity_id, receipt))
984    }
985
986    #[cfg(feature = "software-crypto")]
987    pub async fn end_pfs(
988        &self,
989        peer: &PublicKey,
990        options: &SendOptions,
991    ) -> Result<(), NodeError<M>> {
992        let _ = self
993            .state
994            .borrow_mut()
995            .pfs
996            .end_session(&self.mac, self.identity_id, peer, true, options)
997            .await?;
998        Ok(())
999    }
1000
1001    #[cfg(feature = "software-crypto")]
1002    pub async fn pfs_status(&self, peer: &PublicKey) -> Result<PfsStatus, NodeError<M>> {
1003        let now_ms = self.mac.now_ms().await;
1004        let state = self.state.borrow();
1005        if let Some(session) = state
1006            .pfs
1007            .sessions()
1008            .iter()
1009            .find(|session| session.peer_long_term == *peer)
1010        {
1011            return Ok(match session.state {
1012                PfsState::Requested => PfsStatus::Requested,
1013                PfsState::Active => PfsStatus::Active {
1014                    local_ephemeral_id: session.local_ephemeral_id,
1015                    peer_ephemeral: session.peer_ephemeral,
1016                    expires_ms: session.expires_ms,
1017                },
1018            });
1019        }
1020        if state.pfs.active_route(peer, now_ms).is_some() {
1021            // Defensive fallback for any future session bookkeeping changes.
1022            return Ok(PfsStatus::Requested);
1023        }
1024        Ok(PfsStatus::Inactive)
1025    }
1026
1027    /// Join a channel. Registers the channel key in the MAC if this is
1028    /// the first node to join it. Returns the bound channel handle.
1029    #[cfg(feature = "software-crypto")]
1030    pub async fn join(&self, channel: &Channel) -> Result<BoundChannel<M>, NodeError<M>> {
1031        let membership = self.membership.borrow_mut();
1032
1033        // Check if already joined.
1034        if let Some(entry) = membership.channels.iter().find(|e| e.channel == *channel) {
1035            if entry.active {
1036                return Ok(BoundChannel {
1037                    node: self.clone(),
1038                    channel: entry.channel.clone(),
1039                    join_generation: entry.generation,
1040                });
1041            }
1042        }
1043
1044        // Either new or re-joining after a leave. Both register with the MAC:
1045        // `leave` unregisters the key, so a dormant entry has no MAC state to
1046        // reuse.
1047        drop(membership);
1048        self.mac.add_private_channel(channel.key().clone()).await?;
1049        let mut membership = self.membership.borrow_mut();
1050
1051        if let Some(entry) = membership
1052            .channels
1053            .iter_mut()
1054            .find(|e| e.channel == *channel)
1055        {
1056            entry.active = true;
1057            entry.generation = entry.generation.wrapping_add(1);
1058            return Ok(BoundChannel {
1059                node: self.clone(),
1060                channel: entry.channel.clone(),
1061                join_generation: entry.generation,
1062            });
1063        }
1064
1065        let generation = 0;
1066        membership.channels.push(ChannelMembershipEntry {
1067            channel: channel.clone(),
1068            generation,
1069            active: true,
1070        });
1071
1072        Ok(BoundChannel {
1073            node: self.clone(),
1074            channel: channel.clone(),
1075            join_generation: generation,
1076        })
1077    }
1078
1079    /// Leave a channel. Marks the membership entry inactive, bumps that
1080    /// entry's generation counter, and unregisters the channel key from the
1081    /// MAC so inbound traffic on it is no longer decrypted.
1082    #[cfg(feature = "software-crypto")]
1083    pub async fn leave(&self, channel: &Channel) -> Result<(), NodeError<M>> {
1084        {
1085            let mut membership = self.membership.borrow_mut();
1086            let Some(entry) = membership
1087                .channels
1088                .iter_mut()
1089                .find(|e| e.channel == *channel && e.active)
1090            else {
1091                return Ok(());
1092            };
1093            entry.active = false;
1094            entry.generation = entry.generation.wrapping_add(1);
1095        }
1096        self.mac.remove_channel(channel.key()).await;
1097        Ok(())
1098    }
1099
1100    /// Get a handle to an already-joined channel.
1101    #[cfg(feature = "software-crypto")]
1102    pub fn bound_channel(&self, channel: &Channel) -> Option<BoundChannel<M>> {
1103        let membership = self.membership.borrow();
1104        membership
1105            .channels
1106            .iter()
1107            .find(|e| e.channel == *channel && e.active)
1108            .map(|entry| BoundChannel {
1109                node: self.clone(),
1110                channel: entry.channel.clone(),
1111                join_generation: entry.generation,
1112            })
1113    }
1114
1115    /// List all joined channels.
1116    #[cfg(feature = "software-crypto")]
1117    pub fn bound_channels(&self) -> Vec<BoundChannel<M>> {
1118        let membership = self.membership.borrow();
1119        membership
1120            .channels
1121            .iter()
1122            .filter(|e| e.active)
1123            .map(|entry| BoundChannel {
1124                node: self.clone(),
1125                channel: entry.channel.clone(),
1126                join_generation: entry.generation,
1127            })
1128            .collect()
1129    }
1130
1131    /// Register an ACK-tracked send with the dispatcher and return a progress ticket.
1132    fn register_ack_send(
1133        &self,
1134        send_identity_id: LocalIdentityId,
1135        receipt: Option<umsh_mac::SendReceipt>,
1136    ) -> SendProgressTicket {
1137        match receipt {
1138            Some(receipt) => {
1139                let token = SendToken::new(send_identity_id, receipt);
1140                let state = self.dispatcher.borrow_mut().register_ticket(token, false);
1141                SendProgressTicket::new(token, state)
1142            }
1143            // Unicast/blind-unicast without ACK requested — no tracking.
1144            None => SendProgressTicket::fire_and_forget(),
1145        }
1146    }
1147
1148    /// Register a non-ACK send (broadcast/multicast) with the dispatcher.
1149    ///
1150    /// The ticket starts unfinished. The dispatcher marks it transmitted and
1151    /// finished when the MAC fires the `Transmitted` event with this receipt.
1152    fn register_non_ack_send(
1153        &self,
1154        send_identity_id: LocalIdentityId,
1155        receipt: umsh_mac::SendReceipt,
1156    ) -> SendProgressTicket {
1157        let token = SendToken::new(send_identity_id, receipt);
1158        let state = self.dispatcher.borrow_mut().register_ticket(token, true);
1159        SendProgressTicket::new(token, state)
1160    }
1161
1162    pub(crate) fn state(&self) -> &Rc<RefCell<LocalNodeState>> {
1163        &self.state
1164    }
1165
1166    #[cfg(feature = "software-crypto")]
1167    pub(crate) fn owns_ephemeral_identity(&self, identity_id: LocalIdentityId) -> bool {
1168        self.state
1169            .borrow()
1170            .pfs
1171            .sessions()
1172            .iter()
1173            .any(|session| session.local_ephemeral_id == identity_id)
1174    }
1175
1176    #[cfg(feature = "software-crypto")]
1177    pub(crate) async fn handle_pfs_command(
1178        &self,
1179        from: &PublicKey,
1180        command: &OwnedMacCommand,
1181        options: &SendOptions,
1182    ) -> Result<Option<PfsLifecycle>, NodeError<M>> {
1183        match *command {
1184            OwnedMacCommand::PfsSessionRequest {
1185                ephemeral_key,
1186                duration_minutes,
1187            } => {
1188                self.state
1189                    .borrow_mut()
1190                    .pfs
1191                    .accept_request(
1192                        &self.mac,
1193                        self.identity_id,
1194                        *from,
1195                        ephemeral_key,
1196                        duration_minutes,
1197                        options,
1198                    )
1199                    .await?;
1200                Ok(Some(PfsLifecycle::Established(*from)))
1201            }
1202            OwnedMacCommand::PfsSessionResponse {
1203                ephemeral_key,
1204                duration_minutes,
1205            } => {
1206                if self
1207                    .state
1208                    .borrow_mut()
1209                    .pfs
1210                    .accept_response(
1211                        &self.mac,
1212                        self.identity_id,
1213                        *from,
1214                        ephemeral_key,
1215                        duration_minutes,
1216                    )
1217                    .await?
1218                {
1219                    Ok(Some(PfsLifecycle::Established(*from)))
1220                } else {
1221                    Ok(None)
1222                }
1223            }
1224            OwnedMacCommand::EndPfsSession => {
1225                let _ = self
1226                    .state
1227                    .borrow_mut()
1228                    .pfs
1229                    .end_session(&self.mac, self.identity_id, from, false, options)
1230                    .await?;
1231                Ok(Some(PfsLifecycle::Ended(*from)))
1232            }
1233            _ => Ok(None),
1234        }
1235    }
1236
1237    pub(crate) fn dispatch_received_packet(&self, packet: &ReceivedPacketRef<'_>) -> bool {
1238        let peer = packet.from_key();
1239        let mut state = self.state.borrow_mut();
1240
1241        if let Some(peer) = peer.map(|peer| canonical_peer(&state, peer)) {
1242            if let Some(entry) = state
1243                .peer_subscriptions
1244                .iter_mut()
1245                .find(|entry| entry.peer == peer)
1246            {
1247                if entry.receive_handlers.any_mut(|handler| handler(packet)) {
1248                    return true;
1249                }
1250            }
1251        }
1252
1253        state.receive_handlers.any_mut(|handler| handler(packet))
1254    }
1255
1256    pub(crate) fn dispatch_node_discovered(&self, key: PublicKey, name: Option<&str>) {
1257        self.state
1258            .borrow_mut()
1259            .node_discovered_handlers
1260            .for_each_mut(|handler| handler(key, name));
1261    }
1262
1263    pub(crate) fn dispatch_beacon(&self, from_hint: NodeHint, from_key: Option<PublicKey>) {
1264        self.state
1265            .borrow_mut()
1266            .beacon_handlers
1267            .for_each_mut(|handler| handler(from_hint, from_key));
1268    }
1269
1270    pub(crate) fn dispatch_mac_command(&self, from: PublicKey, command: &OwnedMacCommand) {
1271        self.state
1272            .borrow_mut()
1273            .mac_command_handlers
1274            .for_each_mut(|handler| handler(from, command));
1275    }
1276
1277    pub(crate) fn dispatch_transmitted(&self, wire_bytes: &[u8]) {
1278        self.state
1279            .borrow_mut()
1280            .transmitted_handlers
1281            .for_each_mut(|handler| handler(wire_bytes));
1282    }
1283
1284    pub(crate) fn dispatch_ack_received(&self, peer: PublicKey, token: SendToken) {
1285        let mut state = self.state.borrow_mut();
1286        let peer = canonical_peer(&state, peer);
1287        if let Some(entry) = state
1288            .peer_subscriptions
1289            .iter_mut()
1290            .find(|entry| entry.peer == peer)
1291        {
1292            entry
1293                .ack_received_handlers
1294                .for_each_mut(|handler| handler(token));
1295        }
1296        state
1297            .ack_received_handlers
1298            .for_each_mut(|handler| handler(peer, token));
1299    }
1300
1301    pub(crate) fn dispatch_ack_timeout(&self, peer: PublicKey, token: SendToken) {
1302        let mut state = self.state.borrow_mut();
1303        let peer = canonical_peer(&state, peer);
1304        if let Some(entry) = state
1305            .peer_subscriptions
1306            .iter_mut()
1307            .find(|entry| entry.peer == peer)
1308        {
1309            entry
1310                .ack_timeout_handlers
1311                .for_each_mut(|handler| handler(token));
1312        }
1313        state
1314            .ack_timeout_handlers
1315            .for_each_mut(|handler| handler(peer, token));
1316    }
1317
1318    pub(crate) fn dispatch_pfs_established(&self, peer: PublicKey) {
1319        let mut state = self.state.borrow_mut();
1320        let peer = canonical_peer(&state, peer);
1321        if let Some(entry) = state
1322            .peer_subscriptions
1323            .iter_mut()
1324            .find(|entry| entry.peer == peer)
1325        {
1326            entry
1327                .pfs_established_handlers
1328                .for_each_mut(|handler| handler());
1329        }
1330        state
1331            .pfs_established_handlers
1332            .for_each_mut(|handler| handler(peer));
1333    }
1334
1335    pub(crate) fn dispatch_pfs_ended(&self, peer: PublicKey) {
1336        let mut state = self.state.borrow_mut();
1337        let peer = canonical_peer(&state, peer);
1338        if let Some(entry) = state
1339            .peer_subscriptions
1340            .iter_mut()
1341            .find(|entry| entry.peer == peer)
1342        {
1343            entry.pfs_ended_handlers.for_each_mut(|handler| handler());
1344        }
1345        state
1346            .pfs_ended_handlers
1347            .for_each_mut(|handler| handler(peer));
1348    }
1349
1350    pub(crate) fn dispatch_pfs_failed(&self, peer: PublicKey, reason: PfsFailure) {
1351        let mut state = self.state.borrow_mut();
1352        let peer = canonical_peer(&state, peer);
1353        state
1354            .pfs_failed_handlers
1355            .for_each_mut(|handler| handler(peer, reason));
1356    }
1357
1358    pub(crate) async fn expire_pfs_sessions(&self) -> Result<Vec<PublicKey>, NodeError<M>> {
1359        #[cfg(feature = "software-crypto")]
1360        {
1361            let now_ms = self.mac.now_ms().await;
1362            return self
1363                .state
1364                .borrow_mut()
1365                .pfs
1366                .expire_sessions(&self.mac, now_ms)
1367                .await;
1368        }
1369        #[cfg(not(feature = "software-crypto"))]
1370        {
1371            Ok(Vec::new())
1372        }
1373    }
1374
1375    /// Drop any sent PFS requests that were not answered before their deadline,
1376    /// returning the peers so the caller can report a [`PfsFailure::Timeout`].
1377    #[cfg(feature = "software-crypto")]
1378    pub(crate) fn expire_pfs_requests(&self, now_ms: u64) -> Vec<PublicKey> {
1379        self.state.borrow_mut().pfs.expire_requests(now_ms)
1380    }
1381}
1382
1383fn canonical_peer(state: &LocalNodeState, peer: PublicKey) -> PublicKey {
1384    #[cfg(feature = "software-crypto")]
1385    {
1386        if let Some(session) = state
1387            .pfs
1388            .sessions()
1389            .iter()
1390            .find(|session| session.state == PfsState::Active && session.peer_ephemeral == peer)
1391        {
1392            return session.peer_long_term;
1393        }
1394    }
1395    peer
1396}
1397
1398impl<M: MacBackend> Transport for LocalNode<M> {
1399    type Error = NodeError<M>;
1400
1401    async fn send(
1402        &self,
1403        to: &PublicKey,
1404        payload: &[u8],
1405        options: &SendOptions,
1406    ) -> Result<SendProgressTicket, Self::Error> {
1407        #[cfg(feature = "software-crypto")]
1408        let (send_identity_id, receipt) = {
1409            let now_ms = self.mac.now_ms().await;
1410            if let Some((local_id, peer_ephemeral)) =
1411                self.state.borrow().pfs.active_route(to, now_ms)
1412            {
1413                let receipt = self
1414                    .mac
1415                    .send_unicast(local_id, &peer_ephemeral, payload, options)
1416                    .await?;
1417                (local_id, receipt)
1418            } else {
1419                let receipt = self
1420                    .mac
1421                    .send_unicast(self.identity_id, to, payload, options)
1422                    .await?;
1423                (self.identity_id, receipt)
1424            }
1425        };
1426        #[cfg(not(feature = "software-crypto"))]
1427        let (send_identity_id, receipt) = (
1428            self.identity_id,
1429            self.mac
1430                .send_unicast(self.identity_id, to, payload, options)
1431                .await?,
1432        );
1433        Ok(self.register_ack_send(send_identity_id, receipt))
1434    }
1435
1436    async fn send_all(
1437        &self,
1438        payload: &[u8],
1439        options: &SendOptions,
1440    ) -> Result<SendProgressTicket, Self::Error> {
1441        let receipt = self
1442            .mac
1443            .send_broadcast(self.identity_id, payload, options)
1444            .await?;
1445        Ok(self.register_non_ack_send(self.identity_id, receipt))
1446    }
1447}
1448
1449/// A channel bound to a specific `LocalNode`. Implements `Transport`.
1450///
1451/// Holds a snapshot of the per-channel membership generation at creation
1452/// time. If the node leaves this channel, operations return
1453/// `NodeError::ChannelLeft`.
1454#[cfg(feature = "software-crypto")]
1455#[derive(Clone)]
1456pub struct BoundChannel<M: MacBackend> {
1457    node: LocalNode<M>,
1458    channel: Channel,
1459    join_generation: u64,
1460}
1461
1462#[cfg(feature = "software-crypto")]
1463impl<M: MacBackend> BoundChannel<M> {
1464    /// The underlying channel descriptor.
1465    pub fn channel(&self) -> &Channel {
1466        &self.channel
1467    }
1468
1469    /// True if the node is still a member of this channel.
1470    pub fn is_active(&self) -> bool {
1471        let membership = self.node.membership.borrow();
1472        membership
1473            .channels
1474            .iter()
1475            .any(|e| e.channel == self.channel && e.active && e.generation == self.join_generation)
1476    }
1477
1478    /// Create a peer connection through this channel.
1479    pub fn peer(&self, key: PublicKey) -> PeerConnection<Self> {
1480        PeerConnection::new(self.clone(), key)
1481    }
1482
1483    /// Check membership is still valid.
1484    fn check_active(&self) -> Result<(), NodeError<M>> {
1485        if self.is_active() {
1486            Ok(())
1487        } else {
1488            Err(NodeError::ChannelLeft)
1489        }
1490    }
1491
1492    /// Return the owning local node for this bound channel.
1493    pub fn node(&self) -> &LocalNode<M> {
1494        &self.node
1495    }
1496}
1497
1498#[cfg(feature = "software-crypto")]
1499impl<M: MacBackend> Transport for BoundChannel<M> {
1500    type Error = NodeError<M>;
1501
1502    async fn send(
1503        &self,
1504        to: &PublicKey,
1505        payload: &[u8],
1506        options: &SendOptions,
1507    ) -> Result<SendProgressTicket, Self::Error> {
1508        self.check_active()?;
1509        let receipt = self
1510            .node
1511            .mac
1512            .send_blind_unicast(
1513                self.node.identity_id,
1514                to,
1515                self.channel.channel_id(),
1516                payload,
1517                options,
1518            )
1519            .await?;
1520        Ok(self.node.register_ack_send(self.node.identity_id, receipt))
1521    }
1522
1523    async fn send_all(
1524        &self,
1525        payload: &[u8],
1526        options: &SendOptions,
1527    ) -> Result<SendProgressTicket, Self::Error> {
1528        self.check_active()?;
1529        let receipt = self
1530            .node
1531            .mac
1532            .send_multicast(
1533                self.node.identity_id,
1534                self.channel.channel_id(),
1535                payload,
1536                options,
1537            )
1538            .await?;
1539        Ok(self
1540            .node
1541            .register_non_ack_send(self.node.identity_id, receipt))
1542    }
1543}