Module device_node

Module device_node 

Source
Expand description

The device node: a full umsh-mac/umsh-node stack running on the device itself, alongside the ULCP session.

The device identity “exists even when no phone is attached” (ULCP spec §Identities); this module is what makes that true. It is an ordinary MAC + Host pump with the device’s constraints baked into DeviceNodePlatform:

  • Radio is a LoraphyRadio over the node’s virtual mux bundle (NODE_CH, mux client B) behind the shared duty-ledger gate: the session and the node share one physical radio through radio_mux and draw from one combined PROP_PHY_DUTY_LIMIT budget. A refused transmit is shed via the MAC’s CAD-backoff path rather than killing the pump.
  • Rng is a ChaCha20 CSPRNG seeded from the board’s hardware TRNG at boot (NodeRng): project policy forbids non-crypto RNGs, and under BLE builds the RNG peripheral is not ours to read at runtime.
  • The counter store is the board’s — the CS parameter — so TX reservation boundaries for the device identity and per-peer RX replay boundaries survive power cycles, flushed from inside the MAC pump one whole-map record per persist block.

The node always exists: a device identity is generated and persisted at first boot, so bring-up is unconditional and the only question is whether the node is transmitting. That is configuration — the PHY enable state and the forwarding switch — plus the NODE_ACTIVE gate, which closes while a factory reset is in flight (the identity has been erased from storage but the running MAC still holds it until the reboot that completes the wipe).

Beacon requests arrive through BEACON_TRIGGER rather than from any specific button handler: the trigger is an input, so a button press, bring-up, and the advertisement-policy timers in advert_loop all reach the radio by one path.

§Board seam

Embassy task functions cannot be generic, so the spawnable tasks stay in each firmware as thin shims around the *_loop functions here, and the board owns the two statics whose types depend on CS: the MAC cell and its counter store. Everything else — every static whose type is fixed, and every line of logic — is here once.

Structs§

AdvertPolicy
What the device announces without being asked, mirrored from the device domain’s advertisement-policy properties.
DeviceNodeParts
Everything bring-up produced, for the board to spawn its task shims around. Embassy tasks cannot be generic, so the spawning itself stays board-side.
DeviceNodePlatform
umsh_mac::Platform bundle for the device node, generic only over the board’s counter store — the one piece of the platform that is genuinely per-board, because it is backed by that board’s flash.
NodeHooks
Board couplings the node cannot express itself.
NodeRng
ChaCha20 CSPRNG adapter implementing the rand 0.10 traits the MAC requires (Platform::Rng: rand::CryptoRng). Seeded once at boot from the board’s hardware TRNG, exactly like the session’s IdentityRng, while that source is still ours to read.

Enums§

BeaconTrigger
Why a beacon was requested. Carried through BEACON_TRIGGER so the send path never assumes a button.

Constants§

MAX_NODE_PEERS
Every node the device domain names, which is what the MAC peer table has to hold: a full peer list and a full administrator list can be provisioned at once, and each needs a pairwise session.

Statics§

BEACON_TRIGGER
Beacon requests into the node. On a boot that skipped node bring-up the queue is never drained and requests are dropped at the try_send in request_beacon, leaving the slot inert rather than blocking the caller.
DEV_SYNC
Latest-wins hand-off from the session driver to the sync loop. A Signal rather than a queue: intermediate table states are irrelevant, only convergence on the newest snapshot matters. On a boot that skipped node bring-up (a crash-report boot) a pending snapshot just sits here unconsumed.
NODE_CH
The node’s virtual radio bundle (mux client B). Static regardless of whether the node is running: the mux fans RX out to it either way, and a full queue just drops frames per the mux’s per-client policy.

Functions§

advert_loop
Emits the device’s unsolicited announcements: one beacon at bring-up under PROP_STARTUP_BEACON, then whatever PROP_ADVERT_INTERVAL and PROP_BEACON_INTERVAL ask for.
beacon_loop
Turns beacon triggers into node sends on the device identity: a signed advertisement either way — unsolicited for the button slot, echoing a nonce for an Advertisement Request.
bring_up
Construct the MAC around the device identity and wire up the node. Call at most once. The identity is never absent — boot generates and persists one when the journal is empty — so there is no “unprovisioned” path here.
dev_sync_loop
Reconciles the node’s MAC against each DevDomainSnapshot: joins newly provisioned channels, removes de-provisioned ones (dropping their replay state), and registers peers. Peer removal is not propagated — MAC registry entries carry no key material, so a stale entry is inert, and the registry is rebuilt from the live table at the next boot.
identity_blob_loop
Answers [IDENT_REQUEST] with the node’s current signed identity.
identity_profile_loop
Keeps the Identity Request responder’s profile name synced to the live device name. The responder builds replies synchronously and cannot await, so the current name is pushed in here on each change rather than read at reply time.
mac_counters
The most recently published MAC tallies.
node_key
The public key the running node answers to, or None before bring-up.
publish_snapshot
Hand a snapshot to the sync loop, publishing the mirrors a reader can see synchronously on the way past.
pump_loop
Drives the device node’s MAC pump. Never returns while healthy; an exit means the MAC hit an unrecoverable radio error, and rebooting through the panic handler beats silently losing the device identity.
quiesce_for_reboot
Settle the mesh before a deliberate reset: let queued transmissions finish, then force every frame-counter boundary to flash.
reboot_quiesce_loop
Answers [QUIESCE_REQUEST]; see quiesce_for_reboot.
repeater_enabled
Whether the device node forwards other nodes’ frames.
request_beacon
Fire-and-forget beacon request. A full queue means a beacon (or advertisement) is already pending, so dropping the extra request loses nothing — bursts of Advertisement Requests coalesce here.
set_device_name
Publish the live device name to the node. Call at boot and whenever the session’s device name changes.
set_tx_power_dbm
Publish the transmit power the board just applied to the radio.
sign_identity_blob
Build and sign this node’s identity blob into out, returning its length.
tx_power_dbm
The applied PROP_PHY_TX_POWER, or None before the first Effect::ApplyRadio.

Type Aliases§

DeviceNode
DeviceNodeHandle
DeviceNodeHost
DeviceNodeMac
Device-node MAC sized to the session’s device-domain tables, which are the only provisioning source it has: 1 identity (the device identity; no PFS ephemerals on the device node), MAX_NODE_PEERS peers, MAX_CHANNEL_KEYS channels (a smaller MAC table would refuse channels the property surface accepted), 4 pending ACKs, 4 TX slots (beacons and future acks — no application traffic), 255-byte frames, 32-entry dup cache. The per-channel replay maps are the RAM hot spot (~330 bytes per tracked sender): 4 full-key + 2 hint-only senders per channel keeps the whole table ~2 KiB/channel; extra concurrent senders on one channel fail closed (dropped, never accepted unchecked).
DeviceNodeMacArena
The uninitialized arena a board declares for its MAC. Board-side because its type depends on CS, and a static cannot be generic. A MaybeUninit rather than a StaticCell so a board may place it in a NOLOAD region the runtime never zeroes (the Heltec V2 puts it in the classic ESP32’s dram2_seg, where a StaticCell’s flag byte would come up as garbage); bring_up’s at-most-once contract is what the cell’s flag used to enforce.
DeviceNodeRadio
The node’s radio path: its virtual mux bundle behind the shared duty-ledger admission gate.
NodeMutex