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;