umsh_mac/
handle.rs

1use core::cell::RefCell;
2
3use rand::Rng;
4use umsh_core::{ChannelId, ChannelKey, PublicKey};
5use umsh_hal::{Clock, CounterStore};
6
7use crate::{
8    CapacityError, DEFAULT_ACKS, DEFAULT_CHANNELS, DEFAULT_DUP, DEFAULT_FRAME, DEFAULT_IDENTITIES,
9    DEFAULT_PEERS, DEFAULT_TX, Platform,
10    coordinator::{CounterPersistenceError, LocalIdentityId, Mac, MacError, SendError},
11    peers::PeerId,
12    send::{SendOptions, SendReceipt},
13};
14
15/// Error returned when a `MacHandle` operation cannot access the shared coordinator.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum MacHandleError<E> {
18    /// Another caller is already borrowing the shared coordinator.
19    Busy,
20    /// The underlying coordinator operation failed.
21    Inner(E),
22}
23
24/// Lightweight, cloneable handle for queuing MAC operations against shared state.
25///
26/// The handle borrows a `RefCell` that owns the underlying coordinator, which
27/// keeps queuing and configuration operations lightweight while the main MAC run
28/// loop continues to own radio receive/transmit progression.
29pub struct MacHandle<
30    'a,
31    P: Platform,
32    const IDENTITIES: usize = DEFAULT_IDENTITIES,
33    const PEERS: usize = DEFAULT_PEERS,
34    const CHANNELS: usize = DEFAULT_CHANNELS,
35    const ACKS: usize = DEFAULT_ACKS,
36    const TX: usize = DEFAULT_TX,
37    const FRAME: usize = DEFAULT_FRAME,
38    const DUP: usize = DEFAULT_DUP,
39> {
40    mac: &'a RefCell<Mac<P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>>,
41}
42
43impl<
44    'a,
45    P: Platform,
46    const IDENTITIES: usize,
47    const PEERS: usize,
48    const CHANNELS: usize,
49    const ACKS: usize,
50    const TX: usize,
51    const FRAME: usize,
52    const DUP: usize,
53> Copy for MacHandle<'a, P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>
54{
55}
56
57impl<
58    'a,
59    P: Platform,
60    const IDENTITIES: usize,
61    const PEERS: usize,
62    const CHANNELS: usize,
63    const ACKS: usize,
64    const TX: usize,
65    const FRAME: usize,
66    const DUP: usize,
67> Clone for MacHandle<'a, P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>
68{
69    fn clone(&self) -> Self {
70        *self
71    }
72}
73
74impl<
75    'a,
76    P: Platform,
77    const IDENTITIES: usize,
78    const PEERS: usize,
79    const CHANNELS: usize,
80    const ACKS: usize,
81    const TX: usize,
82    const FRAME: usize,
83    const DUP: usize,
84> MacHandle<'a, P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>
85{
86    /// Creates a cloneable handle backed by shared coordinator state.
87    pub fn new(
88        mac: &'a RefCell<Mac<P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>>,
89    ) -> Self {
90        Self { mac }
91    }
92
93    /// Registers a local identity with the shared coordinator.
94    pub fn add_identity(
95        &self,
96        identity: P::Identity,
97    ) -> Result<LocalIdentityId, MacHandleError<CapacityError>> {
98        self.with_mac(|mac| mac.add_identity(identity))
99    }
100
101    /// Load the persisted frame-counter boundary for one identity.
102    pub async fn load_persisted_counter(
103        &self,
104        id: LocalIdentityId,
105    ) -> Result<
106        u32,
107        MacHandleError<CounterPersistenceError<<P::CounterStore as CounterStore>::Error>>,
108    > {
109        let mut mac = self
110            .mac
111            .try_borrow_mut()
112            .map_err(|_| MacHandleError::Busy)?;
113        mac.load_persisted_counter(id)
114            .await
115            .map_err(MacHandleError::Inner)
116    }
117
118    /// Persist all currently scheduled frame-counter reservations.
119    pub async fn service_counter_persistence(
120        &self,
121    ) -> Result<usize, MacHandleError<<P::CounterStore as CounterStore>::Error>> {
122        let mut mac = self
123            .mac
124            .try_borrow_mut()
125            .map_err(|_| MacHandleError::Busy)?;
126        mac.service_counter_persistence()
127            .await
128            .map_err(MacHandleError::Inner)
129    }
130
131    /// Registers or refreshes a remote peer in the shared registry.
132    pub fn add_peer(&self, key: PublicKey) -> Result<PeerId, MacHandleError<CapacityError>> {
133        self.with_mac(|mac| mac.add_peer(key))
134    }
135
136    /// Adds or updates a shared channel and derives its multicast keys.
137    pub fn add_channel(&self, key: ChannelKey) -> Result<(), MacHandleError<CapacityError>> {
138        self.with_mac(|mac| mac.add_channel(key))
139    }
140
141    /// Adds or updates a named channel using the coordinator's channel-key derivation.
142    pub fn add_named_channel(&self, name: &str) -> Result<(), MacHandleError<CapacityError>> {
143        self.with_mac(|mac| mac.add_named_channel(name))
144    }
145
146    /// Return whether inbound secure packets carrying a full source key may auto-register peers.
147    pub fn auto_register_full_key_peers(
148        &self,
149    ) -> Result<bool, MacHandleError<core::convert::Infallible>> {
150        self.with_mac(|mac| Ok(mac.auto_register_full_key_peers()))
151    }
152
153    /// Enable or disable inbound full-key peer auto-registration.
154    pub fn set_auto_register_full_key_peers(
155        &self,
156        enabled: bool,
157    ) -> Result<(), MacHandleError<core::convert::Infallible>> {
158        self.with_mac(|mac| {
159            mac.set_auto_register_full_key_peers(enabled);
160            Ok(())
161        })
162    }
163
164    /// Installs pairwise transport keys for one local identity and remote peer.
165    ///
166    /// This is a crate-internal method. External callers should use the
167    /// `unsafe-advanced` feature or go through the node-layer PFS session manager.
168    #[cfg(any(feature = "unsafe-advanced", test))]
169    pub(crate) fn install_pairwise_keys(
170        &self,
171        identity_id: LocalIdentityId,
172        peer_id: PeerId,
173        pairwise_keys: umsh_crypto::PairwiseKeys,
174    ) -> Result<Option<crate::peers::PeerCryptoState>, MacHandleError<SendError>> {
175        self.with_mac(|mac| mac.install_pairwise_keys(identity_id, peer_id, pairwise_keys))
176    }
177
178    /// Installs pairwise transport keys for one local identity and remote peer.
179    ///
180    /// # Safety (logical)
181    /// Installing wrong keys will silently corrupt the session. This method
182    /// is deliberately gated behind the `unsafe-advanced` feature. Prefer
183    /// going through the node-layer PFS session manager instead.
184    #[cfg(feature = "unsafe-advanced")]
185    pub fn install_pairwise_keys_advanced(
186        &self,
187        identity_id: LocalIdentityId,
188        peer_id: PeerId,
189        pairwise_keys: umsh_crypto::PairwiseKeys,
190    ) -> Result<Option<crate::peers::PeerCryptoState>, MacHandleError<SendError>> {
191        self.install_pairwise_keys(identity_id, peer_id, pairwise_keys)
192    }
193
194    /// Enqueues a broadcast frame for transmission.
195    ///
196    /// Returns a [`SendReceipt`] for tracking transmission progress.
197    pub async fn send_broadcast(
198        &self,
199        from: LocalIdentityId,
200        payload: &[u8],
201        options: &SendOptions,
202    ) -> Result<SendReceipt, MacHandleError<SendError>> {
203        let mut mac = self
204            .mac
205            .try_borrow_mut()
206            .map_err(|_| MacHandleError::Busy)?;
207        mac.send_broadcast(from, payload, options)
208            .await
209            .map_err(MacHandleError::Inner)
210    }
211
212    /// Enqueues a multicast frame for transmission.
213    ///
214    /// Returns a [`SendReceipt`] for tracking transmission progress.
215    pub async fn send_multicast(
216        &self,
217        from: LocalIdentityId,
218        channel: &ChannelId,
219        payload: &[u8],
220        options: &SendOptions,
221    ) -> Result<SendReceipt, MacHandleError<SendError>> {
222        let mut mac = self
223            .mac
224            .try_borrow_mut()
225            .map_err(|_| MacHandleError::Busy)?;
226        mac.send_multicast(from, channel, payload, options)
227            .await
228            .map_err(MacHandleError::Inner)
229    }
230
231    /// Enqueues a unicast frame for transmission.
232    ///
233    /// Returns a [`SendReceipt`] when `options.ack_requested` is enabled.
234    pub async fn send_unicast(
235        &self,
236        from: LocalIdentityId,
237        dst: &PublicKey,
238        payload: &[u8],
239        options: &SendOptions,
240    ) -> Result<Option<SendReceipt>, MacHandleError<SendError>> {
241        let mut mac = self
242            .mac
243            .try_borrow_mut()
244            .map_err(|_| MacHandleError::Busy)?;
245        mac.send_unicast(from, dst, payload, options)
246            .await
247            .map_err(MacHandleError::Inner)
248    }
249
250    /// Enqueues a blind-unicast frame for transmission.
251    ///
252    /// Returns a [`SendReceipt`] when `options.ack_requested` is enabled.
253    pub async fn send_blind_unicast(
254        &self,
255        from: LocalIdentityId,
256        dst: &PublicKey,
257        channel: &ChannelId,
258        payload: &[u8],
259        options: &SendOptions,
260    ) -> Result<Option<SendReceipt>, MacHandleError<SendError>> {
261        let mut mac = self
262            .mac
263            .try_borrow_mut()
264            .map_err(|_| MacHandleError::Busy)?;
265        mac.send_blind_unicast(from, dst, channel, payload, options)
266            .await
267            .map_err(MacHandleError::Inner)
268    }
269
270    /// Drive the shared MAC until one wake cycle completes and invoke `on_event` for emitted events.
271    pub async fn next_event(
272        &self,
273        on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
274    ) -> Result<(), MacHandleError<MacError<<P::Radio as umsh_hal::Radio>::Error>>> {
275        let mut mac = self
276            .mac
277            .try_borrow_mut()
278            .map_err(|_| MacHandleError::Busy)?;
279        mac.next_event(on_event)
280            .await
281            .map_err(MacHandleError::Inner)
282    }
283
284    /// Drive the shared MAC forever, invoking `on_event` for delivered events.
285    ///
286    /// This is the preferred long-lived driver API for standalone MAC-backed tasks.
287    /// It keeps the wait policy inside the coordinator instead of requiring callers to
288    /// hand-roll `poll_cycle` loops with arbitrary sleeps.
289    pub async fn run(
290        &self,
291        mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
292    ) -> Result<(), MacHandleError<MacError<<P::Radio as umsh_hal::Radio>::Error>>> {
293        loop {
294            self.next_event(&mut on_event).await?;
295        }
296    }
297
298    /// Drive the shared MAC forever while ignoring emitted events.
299    pub async fn run_quiet(
300        &self,
301    ) -> Result<(), MacHandleError<MacError<<P::Radio as umsh_hal::Radio>::Error>>> {
302        self.run(|_, _| {}).await
303    }
304
305    /// Fills a caller-provided buffer with random bytes from the shared coordinator RNG.
306    pub fn fill_random(
307        &self,
308        dest: &mut [u8],
309    ) -> Result<(), MacHandleError<core::convert::Infallible>> {
310        self.with_mac(|mac| {
311            mac.rng_mut().fill_bytes(dest);
312            Ok(())
313        })
314    }
315
316    /// Returns the current coordinator clock time in milliseconds.
317    pub fn now_ms(&self) -> Result<u64, MacHandleError<core::convert::Infallible>> {
318        self.with_mac(|mac| Ok(mac.clock().now_ms()))
319    }
320
321    #[cfg(feature = "software-crypto")]
322    /// Registers an ephemeral software identity with the shared coordinator.
323    ///
324    /// This is primarily used by the node layer when a PFS session becomes active.
325    pub fn register_ephemeral(
326        &self,
327        parent: LocalIdentityId,
328        identity: umsh_crypto::software::SoftwareIdentity,
329    ) -> Result<LocalIdentityId, MacHandleError<CapacityError>> {
330        self.with_mac(|mac| mac.register_ephemeral(parent, identity))
331    }
332
333    #[cfg(feature = "software-crypto")]
334    /// Removes a previously registered ephemeral identity.
335    pub fn remove_ephemeral(
336        &self,
337        id: LocalIdentityId,
338    ) -> Result<bool, MacHandleError<core::convert::Infallible>> {
339        self.with_mac(|mac| Ok(mac.remove_ephemeral(id)))
340    }
341
342    /// Cancel a pending ACK-requested send, stopping retransmissions.
343    ///
344    /// Returns `true` if the pending ACK was found and removed. Returns
345    /// `false` if the send was not found or the coordinator was busy.
346    pub fn cancel_pending_ack(&self, identity_id: LocalIdentityId, receipt: SendReceipt) -> bool {
347        self.mac
348            .try_borrow_mut()
349            .map(|mut mac| mac.cancel_pending_ack(identity_id, receipt))
350            .unwrap_or(false)
351    }
352
353    fn with_mac<T, E>(
354        &self,
355        f: impl FnOnce(&mut Mac<P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP>) -> Result<T, E>,
356    ) -> Result<T, MacHandleError<E>> {
357        let mut mac = self
358            .mac
359            .try_borrow_mut()
360            .map_err(|_| MacHandleError::Busy)?;
361        f(&mut mac).map_err(MacHandleError::Inner)
362    }
363}