umsh_mac/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! UMSH MAC-layer coordinator and supporting state types.
4//!
5//! > Note: This reference implementation is a work in progress and was developed
6//! > with the assistance of an LLM. It should be considered experimental.
7//!
8//! This crate is the central runtime for the UMSH mesh protocol. It owns every piece of
9//! radio-facing state and drives the full MAC lifecycle: receiving and authenticating
10//! inbound frames, forwarding eligible frames as a repeater, issuing and verifying transport
11//! ACKs, retransmitting unacknowledged sends, suppressing duplicates, enforcing replay
12//! windows, managing frame-counter persistence, and servicing the outbound transmit queue.
13//!
14//! The crate is `no_std` compatible. All data structures are backed by
15//! [`heapless`](https://docs.rs/heapless) fixed-capacity collections; capacity is controlled
16//! by const-generic parameters on [`Mac`] so the compiler enforces sizing at build time with
17//! zero heap allocation.
18//!
19//! # Architecture overview
20//!
21//! ```text
22//! ┌────────────────────────────────────────────────────────────────┐
23//! │  Application / upper layers                                    │
24//! │  queue_broadcast / queue_unicast / queue_multicast / …         │
25//! └──────────────────────────┬─────────────────────────────────────┘
26//!                            │  SendOptions  →  SendReceipt
27//!                            ▼
28//! ┌────────────────────────────────────────────────────────────────┐
29//! │  Mac<P>  (coordinator.rs)                                      │
30//! │                                                                │
31//! │  ┌─────────────────┐  ┌────────────────┐  ┌────────────────┐  │
32//! │  │ IdentitySlot[N] │  │ PeerRegistry   │  │ ChannelTable   │  │
33//! │  │  frame counters │  │  public keys   │  │  channel keys  │  │
34//! │  │  pending ACKs   │  │  cached routes │  │  derived keys  │  │
35//! │  │  pairwise keys  │  └────────────────┘  └────────────────┘  │
36//! │  └─────────────────┘                                           │
37//! │  ┌──────────────────────────────────────────────────────────┐  │
38//! │  │ TxQueue  (priority-ordered outbound frame buffer)        │  │
39//! │  └──────────────────────────────────────────────────────────┘  │
40//! │  ┌──────────────────────────────────────────────────────────┐  │
41//! │  │ DuplicateCache  │  ReplayWindow (per peer, per identity) │  │
42//! │  └──────────────────────────────────────────────────────────┘  │
43//! └──────────────────────────┬─────────────────────────────────────┘
44//!                            │  async next_event()
45//!                            ▼
46//! ┌────────────────────────────────────────────────────────────────┐
47//! │  Platform  (umsh-hal + umsh-crypto)                            │
48//! │  Radio · Clock · Rng · Aes/Sha · CounterStore · KeyValueStore  │
49//! └────────────────────────────────────────────────────────────────┘
50//! ```
51//!
52//! # Modules and key types
53//!
54//! ## [`coordinator`] — the top-level state machine
55//!
56//! [`Mac<P>`] is the single top-level type. Create one with [`Mac::new`], register
57//! identities and peers, then drive it with [`Mac::run`], [`Mac::run_quiet`], or
58//! [`Mac::next_event`] depending on whether you want a long-lived driver loop or manual
59//! multiplexing with other async work. Everything else in this crate exists to support
60//! `Mac`.
61//!
62//! Supporting types in this module:
63//!
64//! - [`LocalIdentityId`] — opaque slot index returned when registering a local keypair.
65//! - [`LocalIdentity`] — either a long-term platform identity or an ephemeral software
66//!   identity for PFS sessions.
67//! - [`IdentitySlot`] — per-identity runtime state: keys, frame counter, pending ACKs.
68//! - [`OperatingPolicy`] — transmission-time rules for the local node (amateur-radio mode,
69//!   operator callsign, per-channel overrides).
70//! - [`RepeaterConfig`] — controls whether and how inbound frames are forwarded.
71//! - [`AmateurRadioMode`] — shared enum governing encryption and identification requirements
72//!   under ham-radio law.
73//! - [`ChannelPolicy`] — per-channel overrides within an [`OperatingPolicy`].
74//! - [`SendError`], [`MacError`], [`CounterPersistenceError`] — error types for queuing,
75//!   runtime event processing, and frame-counter store operations respectively.
76//!
77//! ## [`send`] — outbound transmission types
78//!
79//! - [`SendOptions`] — high-level parameters for a single send: MIC size, encryption,
80//!   flood hops, ACK request, source route, salt, etc.
81//! - [`SendReceipt`] — opaque token returned for ACK-requested sends; matched against
82//!   inbound MAC ACKs to confirm delivery.
83//! - [`TxQueue`] — priority-ordered, fixed-capacity queue of sealed frames waiting for
84//!   radio transmission.
85//! - [`QueuedTx`] — one entry in the transmit queue; includes frame bytes, priority,
86//!   not-before timestamp, and CAD retry count.
87//! - [`TxPriority`] — priority classes from highest (`ImmediateAck`) to lowest
88//!   (`Application`).
89//! - [`AckState`] — ACK lifecycle state machine covering queued sends, forwarding
90//!   confirmation, retry scheduling, and final destination ACK waiting.
91//! - [`PendingAck`] — full tracking record for one in-flight ACK-requested send, stored
92//!   in the identity slot until delivery is confirmed or the deadline expires.
93//! - [`ResendRecord`] — verbatim sealed frame bytes retained for retransmission without
94//!   re-sealing.
95//!
96//! ## [`cache`] — duplicate suppression and replay protection
97//!
98//! - [`DuplicateCache`] — a fixed-size FIFO ring that records recently-seen
99//!   [`DupCacheKey`] values. Before forwarding or delivering any received frame, the
100//!   coordinator checks this cache; matching entries are silently dropped. Prevents
101//!   re-delivery of frames that echoed back via multiple repeater paths.
102//! - [`DupCacheKey`] — keyed on the truncated MIC for authenticated packets (unforgeable
103//!   and compact) or a 32-bit hash of the frame body for unauthenticated ones (broadcast).
104//! - [`ReplayWindow`] — per-peer, per-identity sliding window over frame counters. Rejects
105//!   exact counter replays and frames older than the backtrack window, while tolerating
106//!   a small amount of out-of-order delivery. Backed by a [`RecentMic`] ring for
107//!   backward-window disambiguation.
108//! - [`ReplayVerdict`] — outcome of a replay check: `Accept`, `Duplicate`, or `Replay`.
109//!
110//! ## [`peers`] — remote peer and channel registries
111//!
112//! - [`PeerRegistry`] — a flat list of [`PeerInfo`] records (public key + last-seen time +
113//!   cached route). Looked up by hint or full key when matching inbound packets and routing
114//!   outbound sends.
115//! - [`PeerId`] — opaque index into the peer registry.
116//! - [`CachedRoute`] — either an explicit source route or a flood-distance estimate,
117//!   learned from successfully received packets and used to route future sends without
118//!   flooding.
119//! - [`PeerCryptoMap`] — per-identity map from [`PeerId`] to [`PeerCryptoState`]
120//!   (established pairwise keys + replay window). One map per [`IdentitySlot`].
121//! - [`ChannelTable`] — flat list of registered multicast channels. Each entry stores the
122//!   raw channel key, the derived `k_enc`/`k_mic` keys (precomputed at registration time),
123//!   and the 2-byte channel ID (also precomputed). Looked up by channel ID when
124//!   authenticating inbound multicast and blind-unicast frames.
125//!
126//! ## [`handle`] — shared-ownership coordinator access
127//!
128//! - [`MacHandle`] — a `Copy`-able, lifetime-bounded reference to a `RefCell<Mac<P>>`.
129//!   Designed for multi-task environments (e.g., `tokio` or RTOS task pairs) where one
130//!   task runs the `next_event` loop while another enqueues sends or updates configuration
131//!   without holding a long-lived mutable borrow.
132//!
133//! # Platform trait
134//!
135//! [`Platform`] is the single integration point. Implement it once per deployment target to
136//! supply concrete driver types for all hardware abstractions:
137//!
138//! ```rust,ignore
139//! struct MyPlatform;
140//!
141//! impl umsh_mac::Platform for MyPlatform {
142//!     type Identity = MyHsmIdentity;
143//!     type Aes      = MyAesDriver;
144//!     type Sha      = MyShaDriver;
145//!     type Radio    = MySx1262Driver;
146//!     type Delay    = MyDelay;
147//!     type Clock    = MyMonotonicClock;
148//!     type Rng      = MyTrng;
149//!     type CounterStore = MyFlashStore;
150//!     type KeyValueStore = MyNvmStore;
151//! }
152//! ```
153//!
154//! The `umsh` workspace crate provides a `std`/`tokio`-backed implementation
155//! (`tokio_support::StdPlatform`) suitable for desktop development and testing.
156//!
157//! # Frame-counter persistence
158//!
159//! UMSH uses a monotonic frame counter (not a timestamp) for replay protection. Because the
160//! counter must never reuse a value, it must be committed to non-volatile storage before the
161//! corresponding value is used on-air, or after a power cycle the counter could reset to a
162//! previously-seen value, allowing old ciphertexts to replay. The coordinator manages this
163//! automatically:
164//!
165//! 1. On startup, call [`Mac::load_persisted_counter`] for each long-term identity to read
166//!    the last-committed boundary from the [`umsh_hal::CounterStore`] and set the live
167//!    counter to the next safe starting point.
168//! 2. At runtime, the coordinator schedules a persist whenever the live counter crosses a
169//!    block boundary (every [`COUNTER_PERSIST_BLOCK_SIZE`] frames, default 128). While a
170//!    persist is pending, sends will eventually block with [`SendError::CounterPersistenceLag`]
171//!    if the store is not flushed in time.
172//! 3. The application calls [`Mac::service_counter_persistence`] (typically from the
173//!    `next_event` callback or a background task) to drain the pending write queue.
174//!
175//! # `no_std` usage
176//!
177//! Enable `default-features = false` in `Cargo.toml`. The crate compiles without the
178//! standard library. All capacity limits are compile-time const generics. The `std` feature
179//! enables [`test_support`], which provides software-backed driver stubs for unit testing.
180
181use embedded_hal_async::delay::DelayNs;
182
183pub(crate) const RECENT_MIC_CAPACITY: usize = 8;
184pub(crate) const REPLAY_BACKTRACK_SLOTS: u32 = 8;
185pub(crate) const REPLAY_STALE_MS: u64 = 5 * 60 * 1000;
186pub(crate) const MAX_SOURCE_ROUTE_HOPS: usize = 15;
187pub(crate) const MAX_RESEND_FRAME_LEN: usize = 256;
188pub(crate) const DEFAULT_DUP_CACHE_SIZE: usize = 64;
189pub(crate) const MAX_FORWARD_RETRIES: u8 = 3;
190pub(crate) const MAX_CAD_ATTEMPTS: u8 = 5;
191
192/// Default identity-slot capacity for the common `Mac<P>` configuration.
193pub const DEFAULT_IDENTITIES: usize = 4;
194/// Default remote-peer capacity for the common `Mac<P>` configuration.
195pub const DEFAULT_PEERS: usize = 16;
196/// Default shared-channel capacity for the common `Mac<P>` configuration.
197pub const DEFAULT_CHANNELS: usize = 8;
198/// Default pending-ACK capacity for the common `Mac<P>` configuration.
199pub const DEFAULT_ACKS: usize = 16;
200/// Default transmit-queue depth for the common `Mac<P>` configuration.
201pub const DEFAULT_TX: usize = 16;
202/// Default frame-buffer capacity for the common `Mac<P>` configuration.
203pub const DEFAULT_FRAME: usize = MAX_RESEND_FRAME_LEN;
204/// Default duplicate-cache capacity for the common `Mac<P>` configuration.
205pub const DEFAULT_DUP: usize = DEFAULT_DUP_CACHE_SIZE;
206
207/// Error returned when a fixed-capacity MAC data structure is full.
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct CapacityError;
210
211/// Bundle of platform-specific associated types used by the higher layers.
212pub trait Platform {
213    /// Local identity implementation.
214    type Identity: umsh_crypto::NodeIdentity;
215    /// AES provider implementation.
216    type Aes: umsh_crypto::AesProvider;
217    /// SHA/HMAC provider implementation.
218    type Sha: umsh_crypto::Sha256Provider;
219    /// Radio implementation.
220    type Radio: umsh_hal::Radio;
221    /// Async delay implementation.
222    type Delay: DelayNs;
223    /// Monotonic clock implementation.
224    type Clock: umsh_hal::Clock;
225    /// Random-number generator implementation.
226    type Rng: rand::CryptoRng;
227    /// Persistent frame-counter store implementation.
228    type CounterStore: umsh_hal::CounterStore;
229    /// General-purpose persistent key-value store implementation.
230    type KeyValueStore: umsh_hal::KeyValueStore;
231}
232
233mod cache;
234mod coordinator;
235mod handle;
236mod peers;
237mod send;
238
239pub use cache::{DupCacheKey, DuplicateCache, RecentMic, ReplayVerdict, ReplayWindow};
240pub use coordinator::{
241    AmateurRadioMode, ChannelPolicy, CounterPersistenceError, IdentitySlot, LocalIdentity,
242    LocalIdentityId, Mac, MacError, OperatingPolicy, RepeaterConfig, SendError,
243};
244pub use handle::{MacHandle, MacHandleError};
245pub use peers::{
246    CachedRoute, ChannelState, ChannelTable, HintReplayState, PeerCryptoMap, PeerCryptoState,
247    PeerId, PeerInfo, PeerRegistry,
248};
249pub use send::{
250    AckState, ChannelInfoRef, MacEventRef, PacketFamily, PendingAck, PendingAckError, QueuedTx,
251    ReceivedPacketRef, ResendRecord, RouteHops, RxMetadata, SendOptions, SendReceipt, TxPriority,
252    TxQueue,
253};
254pub use umsh_hal::Snr;
255
256#[cfg(feature = "std")]
257pub mod test_support;
258
259#[cfg(test)]
260mod tests;