Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ULCP: Full Protocol

Note

This chapter has been superseded and is retained for reference. Its content now lives in Framing and Common Semantics, Device Domain, Saved State, and Tethered Host Services. What a device is required to implement — the question this chapter’s minimal/full split used to answer — is stated in Minimum Requirements, and every numeric identifier is listed in the Command and Property Index.

This chapter defines the full ULCP protocol: a strict superset of the minimal protocol. A device implementing this chapter implements everything in the minimal protocol — the frame format, packed unsigned integers, commands, properties, status codes, reset codes, and capabilities defined there apply here unchanged and are not repeated. The protocol version remains 6.0; a host discovers which full-protocol features a device implements through PROP_CAPS (see (#full-capabilities)), not through the version number.

The minimal protocol treats the device as a raw radio pipe: the host runs the entire UMSH MAC and the device moves frames. The full protocol keeps that division of labor — the host still owns the MAC and its own private keys — and adds narrowly scoped assistance so the device can be useful while the host is asleep or disconnected:

  • Receive filtering — the device learns which frames are relevant so it does not deliver (or wake the host for) unrelated traffic.
  • Inbound queueing — frames received while no host is attached are retained and delivered when the host asks for them.
  • Key provisioning — the host installs channel keys and pairwise peer keys so the device can recognize traffic for the host’s identity, including blind unicast, and authenticate it.
  • Acknowledgement delegation — for peers whose pairwise keys are provisioned, the device can send MAC acks on the host’s behalf while the host is away.
  • Saved state — the device can snapshot its configuration to non-volatile storage and resume autonomous operation after a power cycle with no host present.

There is no outbound queueing. A transmit either happens or fails while the host is attached to observe the result.

Identity Model

The full protocol supports exactly two node identities:

  • The device identity — a node belonging to the device itself, used for in-band management, diagnostics, repeater forwarding (see (#prop-mac-repeater-enabled)), and (in future revisions) periodic advertisement behavior. Its Ed25519 private key is held by the device and is never readable through this protocol.

    A device identity always exists. A device advertising CAP_DEV_IDENTITY that finds no stored keypair at power-on MUST generate one from a cryptographic random source and persist it before processing any host command; PROP_DEV_KEY therefore never reports an empty value on a running device. Provisioning an identity is not a commissioning step: a factory-fresh radio is already a node, and PROP_DEV_PRIVATE_KEY (see (#prop-dev-private-key)) exists to install a particular identity — restoring a known repeater onto replacement hardware — not to bring one into being.

    The corollary is that a radio holds a throwaway identity from first power-on until a specific one is installed. This is safe because it never reaches the air: PROP_PHY_ENABLED is false post-reset, and a radio with nothing saved boots with the PHY disabled. It is not safe automatically on the restore path — see (#cmd-restore).

  • The tethered host identity — the single UMSH identity owned by the attached host. Of the identity keypair itself, the device holds only the 32-byte public key; the host’s private key MUST NOT be transferred to the device, and this protocol provides no mechanism for doing so (see Security Boundary). The device may additionally hold host-domain state derived or delegated by the host — channel keys, per-peer symmetric keys, filters, and queued traffic — as defined in this chapter.

Because the device never holds the host’s private key, it cannot perform ECDH on the host’s behalf. All pairwise key material the device uses for the host identity is derived by the host and explicitly provisioned per peer (see (#prop-host-peer-keys)). The device identity is different: the device holds that private key, so it performs its own key agreement and needs only peer public keys (see (#prop-dev-peers)).

State Classes

Every piece of device state belongs to exactly one of three classes. The classes determine what survives a host attach, a change of host, and a power cycle.

Session State

State that exists only while a host is attached: transaction (TID) correlation, transport reassembly buffers, and session-scoped properties — currently only PROP_MAC_PROMISCUOUS. Session state is reset to defaults on every attach. Resetting it never affects radio operation.

Device Domain

State that belongs to the device itself, independent of which host is attached:

  • the device identity keypair (independently persisted; never part of the saved snapshot — see (#saved-state))
  • the device identity’s channel keys ((#prop-dev-channel-keys)) and peer list ((#prop-dev-peers))
  • the RF configuration (PROP_PHY_*), including PROP_PHY_ENABLED, and the duty-cycle limit
  • the human-readable device name (PROP_DEV_NAME)
  • live battery telemetry (PROP_BATTERY), when CAP_BATTERY is present
  • device behavior settings (property identifiers 70–95): the repeater forwarding switch (PROP_MAC_REPEATER_ENABLED), with the rest of the range reserved for future definition (further repeater policy, positioning, periodic advertisement of the device identity, and similar)
  • transport configuration such as PROP_BLE_PAIRING_PIN

The RF configuration is deliberately device-domain: a site repeater keeps its frequency and regulatory limits no matter which phone pairs with it. An attached host may still reconfigure it at any time.

Host Domain

State that belongs to the currently configured tethered host identity:

  • PROP_HOST_KEY itself
  • the host’s channel keys and peer keys
  • the receive filter table and acknowledgement-delegation policy
  • the inbound queue: its configuration and its contents

The host domain is volatile across a power cycle, and only across a power cycle. It MUST NOT be persisted: it is not part of the saved snapshot, and at power-on every host-domain property takes its documented default.

It emphatically does survive a disconnect. A detached radio keeps filtering, queueing and acknowledging on behalf of its host for as long as it stays powered — that is the entire value of the host domain, and nothing about the host going out of range changes what the host wants done.

The two together give the host a simple rule with no detection in it: a host MUST establish its complete host domain on every tethered attach, writing every part of it rather than reasoning about what the device already holds. Key material cannot be compared anyway — the key tables never read it back (see (#provisioning-security)), so a peer’s pairwise keys can be replaced without changing anything the host can observe. Where the device is already provisioned as asked, the rewrite is redundant; that is preferable to depending on a signal that would also have to cover partial provisioning, another administrator’s intervention, and future device behavior. A boot generation or reset indication MAY be used to skip the rewrite as an optimization, never to decide whether it is needed.

Host Replacement

The host domain is keyed by PROP_HOST_KEY. Setting PROP_HOST_KEY to a value different from its current value — including setting it to empty — MUST atomically reset the entire host domain to defaults: the key tables and filter table are cleared, PROP_HOST_AUTO_ACK reverts to false, and the inbound queue is discarded. Because the host domain is never persisted, this is a live-state operation with no durable component: a power cycle cannot resurrect a previous host’s provisioning regardless.

Setting PROP_HOST_KEY to its current value is idempotent and has no side effects.

This rule is what makes re-pairing safe: when a companion radio is paired with a different phone, the new host configures its own identity and the previous host’s keys, filters, and queued traffic cease to exist — while the device domain (the radio’s own identity, channels, and settings) is untouched.

Attach, Detach, and Synchronization

How attach and detach are detected is defined by the transport binding:

  • BLE — enabling/disabling notifications on Frame Out, as specified in ULCP over BLE.
  • USB-CDC — assertion and deassertion of DTR on the ULCP interface.
  • Bare UART — implementation-defined. A device with no way to detect host presence MAY treat the host as permanently attached, in which case it never enters detached operation and offline assistance ((#inbound-queueing), (#ack-delegation)) is unavailable on that transport.

On attach, the device MUST reset session state (see (#state-classes)) and MUST NOT modify the device or host domains in any way. In particular, the PHY is not disabled and no property outside session state changes value. The device MUST NOT emit any frame before attach, and emits no unsolicited notification as a result of the attach itself.

Because attach no longer implies any known default state, the host synchronizes by fetching, not by assuming. The following post-attach procedure is RECOMMENDED:

  1. CMD_PROP_GET for PROP_LAST_STATUS. If it returns a reset code (see Reset Codes), the device has reset since the last host command, so any state that is not restored from saved state (notably queue contents) has been lost.
  2. CMD_PROP_GET for PROP_HOST_KEY. An empty value is the ordinary case after a power cycle — the host domain does not survive one — and the host simply provisions. A value matching the host’s own identity means its provisioning is still live from before the disconnect. Any other value means another host has taken the radio over since this host last attached; the queue and provisioning belong to that identity, and this host must decide whether to take the radio over (see (#host-replacement)) before doing anything else.
  3. CMD_PROP_GET for the device-domain properties the host depends on (PROP_SAVED, the PROP_PHY_* configuration), and for PROP_HOST_RX_QUEUE_COUNT.
  4. Establish the complete host domain (see (#host-domain)): write PROP_HOST_KEY, the key tables, the filter table and the delegation policy in full. The key tables are read back only to find entries the host no longer wants, which it removes; entries it does want are written whether or not the device reports them, since key material is not readable and so cannot be compared.
  5. Issue CMD_QUEUE_DRAIN when actually ready to process backlogged traffic.

More generally, a host MUST tolerate unsolicited CMD_PROP_IS, CMD_PROP_INSERTED, and CMD_PROP_REMOVED notifications at any time while attached, updating its view of the affected property accordingly: device state can change for reasons the host did not initiate, and publication of the new authoritative value is how the protocol reports that.

On detach, the device discards session state, keeps operating with the current device- and host-domain state, and begins detached operation: accepted frames are queued rather than delivered, and acknowledgement delegation (if enabled) becomes active.

Saved State

A device advertising CAP_SAVE can snapshot its provisioning to non-volatile storage so that it can operate autonomously across power cycles — the radio can be powered on in the morning with no phone present, restore its configuration, enable the PHY, and resume queueing and acknowledging on the host’s behalf.

  • CMD_SAVE (see (#cmd-save)) atomically writes the current device domain configuration — including the RF configuration and the current value of PROP_PHY_ENABLED — to non-volatile storage, replacing any previous snapshot.

    The host domain is never part of a snapshot (see (#host-domain)): a radio’s autonomy is its own configuration, and whichever host it is serving re-establishes its keys, filters and delegation policy on every attach. Dynamic read-only state, including queue contents and PROP_BATTERY, is likewise never saved. The device identity keypair is excluded for a different reason: it is independently persisted the moment it is installed or generated (see (#prop-dev-private-key)) and is changed only by explicit provisioning or CMD_CLEAR — neither CMD_RESTORE nor a reboot can revert the device identity to an earlier key.

  • At boot, if a snapshot exists, the device MUST restore it and resume operation accordingly before processing any host command: the RF configuration is applied and the PHY is re-enabled if it was enabled when saved, so a repeater is forwarding before anything else happens. Host-domain behavior — filtering, queueing, acknowledgement delegation — does not resume, because there is no host domain until a host provides one. If no snapshot exists, all properties take their documented post-reset values.

  • CMD_RESTORE (see (#cmd-restore)) reverts the device domain to the snapshot on demand, letting the host abort uncommitted configuration changes — without rebooting the hardware or dropping the ULCP link. It is observable either as a protocol reset (STATUS_RESET_RESTORED) or as a series of property-update publications; hosts handle both.

  • CMD_CLEAR (see (#cmd-clear)) erases the snapshot and all other persisted provisioning, including the device identity private key. It does not modify live (in-RAM) state; a subsequent CMD_RST completes a factory reset. Transport-level state such as BLE bonds is not affected.

  • PROP_SAVED (see (#prop-saved)) reports the state of the stored snapshot, which is not simply whether one exists — see (#snapshot-integrity).

Saving is explicit rather than automatic: nothing is written to non-volatile storage when properties change (the exceptions are the device identity and PROP_BLE_PAIRING_PIN). This gives the host control over flash wear and a well-defined “known good” configuration, and it means a radio never persists provisioning its host did not deliberately ask to keep.

Two consequences deserve emphasis:

  • Post-reset values come from the snapshot. CMD_RST reverts properties to their post-reset values, as always — but on a device with a snapshot, the post-reset value of every saved property is its saved value, not its documented default. This applies to the device domain only; the host domain has no saved value and always returns to its documented defaults. Factory defaults are restored by CMD_CLEAR followed by CMD_RST. A host that implements only the minimal protocol and expects documented defaults after CMD_RST will find the PHY already configured and enabled on a radio that was provisioned for autonomous operation; such a host still works if it explicitly sets the properties it cares about.
  • Queue contents and replay baselines are not saved. Frames queued before a power loss are gone afterward, even if they were acknowledged on the host’s behalf — the sender believes them delivered. Likewise the per-peer frame-counter baselines used by acknowledgement delegation restart (see Counter Resynchronization). These share the host domain’s lifetime, which is why re-provisioning after a power cycle is a resynchronization point rather than an inconvenience. Implementations MAY persist the queue to narrow this window, but hosts MUST NOT rely on it.

Snapshot Integrity

The snapshot is the one piece of state whose loss is silent and remote. A device configured to operate unattended comes back from a rejected snapshot deaf and non-forwarding, with nobody attached to be told, and recovery requires physically visiting it. The requirements below exist for that case.

  • A snapshot MUST be self-describing enough that a device can distinguish a payload it cannot read from an absent one. A device MUST NOT apply a payload it does not fully understand.
  • Devices MUST NOT silently boot bare after rejecting a snapshot. Where the storage retains earlier generations, the device MUST fall back to the newest generation that does decode, in preference to booting with documented defaults. A device MAY bound how far back it walks.
  • PROP_SAVED MUST report a fallback and an unreadable snapshot distinguishably from both “saved” and “nothing saved” (see (#prop-saved)). Devices with a local indicator SHOULD signal it there as well, since the host-visible report reaches nobody on an unattended device.
  • A device that restored an older generation is otherwise in normal operation: nothing is refused, and CMD_SAVE replaces the stored snapshot and clears the condition.

Only forward compatibility is required. Newer firmware MUST read snapshots written by older firmware, taking the documented default for anything the older writer did not record, and MUST ignore content it does not recognize. Firmware downgrade is out of scope: an older image reading a newer snapshot has no defined behavior, and saving from a downgraded image is destructive by design.

Additional Commands

The full protocol assigns the four command identifiers reserved by the minimal protocol for table operations, and adds four more:

IdMnemonicDirDescription
4CMD_PROP_INSERTHost->DeviceInsert an item into a multi-value property
5CMD_PROP_REMOVEHost->DeviceRemove an item from a multi-value property
7CMD_PROP_INSERTEDDevice->HostItem-inserted notification
8CMD_PROP_REMOVEDDevice->HostItem-removed notification
11CMD_QUEUE_DRAINHost->DeviceDeliver queued inbound frames
12CMD_SAVEHost->DeviceSave state to non-volatile storage
13CMD_CLEARHost->DeviceErase all saved state
14CMD_RESTOREHost->DeviceRestore state from the saved snapshot
15CMD_FACTORY_RESETHost->DeviceErase all mutable state (incl. bonds) and reboot

CMD 4: (Host -> Device) CMD_PROP_INSERT

  0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |      CMD      | PROP_KEY (PUI, 1-3 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ITEM VALUE ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_PROP_INSERT

Insert item into property. Commands the device to add the given item to the given multi-value property, and to emit a CMD_PROP_INSERTED command for that property if successful.

The payload for this command is the property identifier encoded in the packed unsigned integer format, followed by exactly one item encoded in the property’s item form (see (#multi-value-properties)). The item is not preceded by a length prefix, regardless of whether the property uses item length prefixes in its multi-item value form; the framing layer bounds the item.

If the item is already present the command fails with STATUS_ALREADY, except where a property defines replacement semantics for matching items (see, e.g., (#prop-host-peer-keys)). If the property exists but is not a multi-value property, the command fails with STATUS_INVALID_ARGUMENT.

If an error occurs, the value of the emitted PROP_LAST_STATUS will be set accordingly to the status code for the error.

CMD 5: (Host -> Device) CMD_PROP_REMOVE

  0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |      CMD      | PROP_KEY (PUI, 1-3 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ITEM SELECTOR ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_PROP_REMOVE

Remove item from property. Commands the device to remove the item matching the given selector from the given multi-value property, and to emit a CMD_PROP_REMOVED command for that property if successful.

The payload for this command is the property identifier encoded in the packed unsigned integer format, followed by an item selector. Each multi-value property documents its selector form; unless stated otherwise it is the full item value.

If no matching item is present, the command fails with STATUS_ITEM_NOT_FOUND. If the property exists but is not a multi-value property, the command fails with STATUS_INVALID_ARGUMENT.

If an error occurs, the value of the emitted PROP_LAST_STATUS will be set accordingly to the status code for the error.

CMD 7: (Device -> Host) CMD_PROP_INSERTED

  0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |      CMD      | PROP_KEY (PUI, 1-3 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  REPORTED ITEM ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_PROP_INSERTED

Item-inserted notification. Sent by the device in response to a successful CMD_PROP_INSERT (with the TID of that command), or unsolicited with a TID of zero when the device adds an item to a multi-value property for its own reasons.

The payload is the property identifier followed by the inserted item as the device reports it (see (#multi-value-properties)) — never in a form containing key material.

CMD 8: (Device -> Host) CMD_PROP_REMOVED

  0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |      CMD      | PROP_KEY (PUI, 1-3 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  REPORTED ITEM ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_PROP_REMOVED

Item-removed notification. Sent by the device in response to a successful CMD_PROP_REMOVE (with the TID of that command), or unsolicited with a TID of zero when the device removes an item from a multi-value property for its own reasons.

The payload is the property identifier followed by the removed item as the device reports it.

CMD 11: (Host -> Device) CMD_QUEUE_DRAIN

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |CMD_QUEUE_DRAIN|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_QUEUE_DRAIN

Deliver queued inbound frames. Commands the device to deliver every frame currently held in the inbound queue (see (#inbound-queueing)), oldest first, as ordinary CMD_STR_RECV commands on STR_PHY_RAW carrying the buffered-frame metadata described in (#buffered-metadata). The command payload SHOULD be empty and MUST be ignored.

Queued frames are only delivered in response to this command; attaching to the device does not by itself cause queued frames to be delivered (see (#inbound-queueing)). This lets the host finish synchronizing its session and signal that it is actually ready to process backlogged traffic.

The drain covers exactly the frames held in the queue when the command is received. Because accepted frames are always delivered live while a host is attached, the queue cannot grow while a drain is in progress: the drain always covers a fixed set of frames and always terminates. If the command was sent with a non-zero TID, the device reports completion by emitting CMD_PROP_IS for PROP_LAST_STATUS with STATUS_OK and the matching TID immediately after delivering the last covered frame. Draining an empty queue succeeds immediately.

Frames that arrive while a drain is in progress are not part of it: they are delivered live, and MAY therefore interleave with the buffered deliveries. RX_FLAG_BUFFERED distinguishes the two, and UMSH does not guarantee in-order delivery in any case (see (#inbound-queueing)).

If the device does not implement queueing (CAP_HOST_RX_QUEUE not advertised), the command fails with STATUS_UNIMPLEMENTED.

If an error occurs, the value of the emitted PROP_LAST_STATUS will be set accordingly to the status code for the error.

CMD 12: (Host -> Device) CMD_SAVE

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |    CMD_SAVE   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_SAVE

Save state. Commands the device to atomically write the current device domain and host domain to non-volatile storage as described in (#saved-state), replacing any existing snapshot. The command payload SHOULD be empty and MUST be ignored.

The response is a CMD_PROP_IS for PROP_LAST_STATUS with the command’s TID: STATUS_OK once the snapshot is durably stored, or an appropriate error status (for example STATUS_NOMEM) if it is not; on failure the previous snapshot, if any, MUST remain intact.

This command is only available on devices advertising CAP_SAVE; otherwise it fails with STATUS_UNIMPLEMENTED.

CMD 13: (Host -> Device) CMD_CLEAR

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |   CMD_CLEAR   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_CLEAR

Clear saved state. Commands the device to erase from non-volatile storage the saved snapshot and all other persisted provisioning, including the device identity private key. Live (in-RAM) state is unaffected; transport-level state such as BLE bonds and PROP_BLE_PAIRING_PIN is also unaffected. A CMD_CLEAR followed by CMD_RST restores factory protocol behavior.

Because a device identity always exists (see (#identity-model)), the CMD_RST that completes the sequence MUST generate and persist a new one rather than leave the device with none — the same thing a factory-fresh power-on does, and for the same reason. PROP_DEV_KEY therefore reports a different key after the sequence, never an empty one.

The previous identity is gone from the moment CMD_RST completes, but anything the device built around it — a running device node, in particular — MUST NOT continue to originate traffic under it, even where that state survives until the next boot.

The command payload SHOULD be empty and MUST be ignored. The response is a CMD_PROP_IS for PROP_LAST_STATUS with the command’s TID.

Unlike CMD_SAVE, this command is available regardless of capabilities; a device with nothing persisted succeeds trivially.

CMD 14: (Host -> Device) CMD_RESTORE

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |  CMD_RESTORE  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_RESTORE

Restore saved state. Commands the device to revert its device-domain configuration to the contents of the saved snapshot (see (#saved-state)). Regardless of how completion is reported (below), the resulting state is the same:

  • saved device-domain properties take their saved values, and the saved RF configuration and PHY enable state are applied;
  • the hardware is not reset, and the transport link and attach state are preserved;
  • the host domain is not touched: it is not part of a snapshot, so a restore has nothing to revert it to. The inbound queue contents, per-peer replay baselines, filters and delegation policy all survive unconditionally;
  • independently persisted state outside the snapshot — the device identity keypair and PROP_BLE_PAIRING_PIN — is not affected; and
  • the saved snapshot itself is not modified.

A restore never enables the PHY under an identity the snapshot was not taken for. A snapshot records which device identity was live when it was written. If that does not match the live PROP_DEV_KEY, the device MUST apply the restore with PROP_PHY_ENABLED false, whatever the snapshot says.

This is the replacement-hardware case, and it is the one path where a freshly generated identity can reach the air. Restoring a repeater’s saved domain onto a new board before installing that repeater’s key (see (#prop-dev-private-key)) would otherwise bring the radio up advertising as the node the snapshot describes, signing as a key nobody has ever seen. Installing the key first, then restoring, is the intended order and enables the PHY normally; the rule is what makes the wrong order safe rather than merely discouraged. A snapshot that does not record an identity is treated as matching.

Together with CMD_SAVE, this provides a commit/abort pattern: the host can make live configuration changes and either persist them with CMD_SAVE or discard them with CMD_RESTORE.

The command payload SHOULD be empty and SHOULD NOT be processed. A device reports a successful restore in one of two forms, both valid; the two forms differ only in reporting and in session-state handling, never in the resulting configuration or retained data:

  • Reset form — the device additionally resets its protocol session state (transaction bookkeeping and session-scoped properties), as on attach. As with CMD_RST, the TID is ignored; completion is signaled by an unsolicited CMD_PROP_IS for PROP_LAST_STATUS carrying the reset code STATUS_RESET_RESTORED (see (#full-reset-codes)). On receiving it, the host discards its cached view of all properties and assumes saved properties hold their saved values; dynamic read-only properties (such as PROP_HOST_RX_QUEUE_COUNT) reflect live state and are re-fetched.

  • Update form — the device applies the revert in place, emitting an unsolicited CMD_PROP_IS (with key material omitted, where applicable) for every property whose value changed, and then reports completion with CMD_PROP_IS for PROP_LAST_STATUS carrying STATUS_OK and the command’s TID. Session state is not reset in this form.

A host MUST handle both forms: it treats STATUS_RESET_RESTORED as full reversion to saved values, applies any unsolicited property updates, and recognizes completion by either the reset notification or the matching-TID STATUS_OK. This is not an extra burden in practice — hosts must already tolerate unsolicited CMD_PROP_IS value changes at any time (see (#attach-sync)). A host that does not know the snapshot’s contents (for example, because a previous session saved it) re-fetches the properties it depends on, exactly as in the post-attach procedure.

If an error occurs — in particular STATUS_INVALID_STATE when no snapshot exists (see PROP_SAVED) — the value of the emitted PROP_LAST_STATUS will be set accordingly, no state is modified, and no reset code is emitted.

CMD 15: (Host -> Device) CMD_FACTORY_RESET

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 0| RES | TID |CMD_FACTORY_RST|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure: Structure of CMD_FACTORY_RESET

Return the radio to a blank factory state. Commands the device to erase every piece of mutable state it holds — both the persisted state CMD_CLEAR erases (the saved snapshot, all persisted provisioning, and the device identity private key) and the transport-level state CMD_CLEAR deliberately preserves: all BLE bonds and the configured PROP_BLE_PAIRING_PIN — and then reboot. After the reboot the radio is indistinguishable from one that has never been provisioned or paired.

This differs from CMD_CLEAR + CMD_RST in two ways: it also clears transport-level pairing state (bonds and PIN), and it performs a hardware reboot rather than only a protocol-session reset.

The command payload SHOULD be empty and MUST be ignored. Unlike every other command, CMD_FACTORY_RESET has no response: the device wipes its storage and reboots, which drops the transport link. A host treats the ensuing disconnect (and the radio’s subsequent reappearance in a factory state) as completion; it MUST NOT wait for a PROP_LAST_STATUS. The TID is therefore irrelevant.

Because clearing the bonds invalidates the encrypted link the command arrived on, a host that issues CMD_FACTORY_RESET over a bonded transport should also discard its own pairing to the radio.

This command is available regardless of capabilities.

This command is only available on devices advertising CAP_SAVE; otherwise it fails with STATUS_UNIMPLEMENTED.

Multi-Value Properties

A multi-value property holds an unordered set of items rather than a single value. The minimal protocol already contains one (PROP_CAPS, which is constant); the full protocol adds mutable ones.

The host writes items (CMD_PROP_SET, CMD_PROP_INSERT) in the property’s item form. When the device reports items (CMD_PROP_IS, CMD_PROP_INSERTED, CMD_PROP_REMOVED), it reports them exactly as written — except where the item form contains symmetric key material. Such a property documents what is reported instead: the entry with its key material omitted, or a short derived digest form (a channel key is reported as its derived channel identifier), so that secrets can never be read back (see (#provisioning-security)).

The commands valid on a mutable multi-value property are:

  • CMD_PROP_GET — the device replies with CMD_PROP_IS whose value is the concatenation of all items as reported. If the property is documented as having an item length prefix, each item is preceded by its length in octets encoded as a packed unsigned integer; properties whose reported items are fixed-size omit the prefix.
  • CMD_PROP_SET — replaces the entire contents with the items encoded in the value, each in item form (with the same length-prefix rule). Setting an empty value clears the property. Success is reported with a CMD_PROP_IS carrying the new complete value as reported.
  • CMD_PROP_INSERT / CMD_PROP_REMOVE — add or remove one item, as defined above.

Hosts manipulating large tables SHOULD prefer Insert/Remove over whole-table Set, since a full table may not fit comfortably in one frame on all transports.

Mutation Atomicity

State-changing operations in this protocol are transactional and fail closed:

  • The device MUST validate a complete request before changing any state. A whole-table CMD_PROP_SET whose value contains any invalid item fails without applying any of it.
  • Whole-table replacement is atomic: no observer of device behavior (frame filtering, acknowledgement decisions) sees a mixture of the old and new contents.
  • Operations that include durable writes — CMD_SAVE, CMD_CLEAR, installing or generating the device identity, and setting PROP_BLE_PAIRING_PINMUST NOT report success before the durable write has completed.
  • On any failure, the prior live and durable state remains unchanged, and the device MUST NOT emit CMD_PROP_IS, CMD_PROP_INSERTED, or CMD_PROP_REMOVED notifications describing a partially applied change.
  • Host replacement is atomic in the same sense: at no point may the device operate with a mixture of the old and new hosts’ keys, filters, or policy. It involves no durable write, so it cannot fail partway.

Atomicity is per operation, not per sequence. Establishing a host domain is several property writes, and an interruption between them leaves a mixture of old and new — bounded by the fact that a host-key change resets the domain first and a reboot empties it. A host repairs this the same way it provisions in the first place: by writing everything again.

Property Allocation

The full protocol allocates property identifiers by state class:

RangeClass
48–63Session-scoped and global protocol state
64–95Device domain
96–127Host domain (PROP_HOST_*)

Unassigned identifiers in these ranges are reserved.

IdMnemonicCommandsDescription
48PROP_MAC_PROMISCUOUSGet, SetDeliver all frames (session-scoped)
49PROP_SAVEDGetSaved-snapshot state
64PROP_DEV_KEYGetDevice identity public key
65PROP_DEV_PRIVATE_KEYSetDevice identity private key (write-only)
66PROP_DEV_CHANNEL_KEYSGet, Set, Insert, RemoveDevice identity channel keys
67PROP_DEV_PEERSGet, Set, Insert, RemoveDevice identity peer list
68PROP_DEV_NAMEGet, SetHuman-readable device name
69PROP_BATTERYGet, IsBattery status snapshot
70PROP_MAC_REPEATER_ENABLEDGet, SetAutonomous repeater forwarding enable
71PROP_IDENTGetSigned node identity of the device identity
72PROP_IDENT_ROLEGet, SetAdvertised node role, or empty to derive it
73PROP_IDENT_MOBILEGet, SetAdvertise the mobile capability bit
96PROP_HOST_KEYGet, SetTethered host identity public key
97PROP_HOST_CHANNEL_KEYSGet, Set, Insert, RemoveHost channel keys
98PROP_HOST_PEER_KEYSGet, Set, Insert, RemoveHost pairwise peer keys
99PROP_HOST_RX_FILTERSGet, Set, Insert, RemoveHost receive filter table
100PROP_HOST_AUTO_ACKGet, SetAcknowledgement delegation enable
101PROP_HOST_RX_QUEUE_COUNTGetFrames currently queued
102PROP_HOST_RX_QUEUE_CAPACITYGet, SetQueue capacity in frames
103PROP_HOST_RX_QUEUE_DROPPEDGetFrames dropped from the queue

PROP 48: PROP_MAC_PROMISCUOUS

  • Type: Single-Value, Read-Write, Session-Scoped
  • Asynchronous Updates: No
  • Required: CAP_HOST_FILTER
  • Value Type: BOOL
  • Post-Attach Value: 0 (false)

When true, every frame the PHY successfully receives is delivered to the host over STR_PHY_RAW, bypassing receive filtering. This is a live-session diagnostic mode: frames that are delivered only because of promiscuous mode are never queued while the host is detached, and never acknowledged on the host’s behalf.

This is the only session-scoped property: it reverts to false on every attach.

PROP 49: PROP_SAVED

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: No
  • Required: CAP_SAVE
  • Value Type: UINT8

Whether a saved snapshot is in effect (see (#saved-state)) — that is, whether the device is armed for autonomous operation across a power cycle — and, when the answer is qualified, how:

ValueMeaning
0Nothing is saved. Every property holds its documented default.
1The most recently saved snapshot is in effect.
2A saved snapshot is in effect, but a newer stored generation was rejected at boot and this is an earlier one. The device is operating on configuration older than what was last saved.
3A snapshot exists but no stored generation could be read. The device booted with documented defaults despite having been saved.

Values 2 and 3 are conditions to report to the operator, not errors to recover from automatically: the configuration the device is running is not the configuration that was last written, and only whoever wrote it can say what should replace it. A successful CMD_SAVE returns the value to

  1. Values 2 and 3 persist for the remainder of the power cycle and MUST NOT be cleared by CMD_RST or CMD_RESTORE, neither of which re-reads storage.

A host that treats any non-zero value as “saved” behaves correctly, and loses only the warning.

PROP 64: PROP_DEV_KEY

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: No
  • Required: CAP_DEV_IDENTITY
  • Value Type: 32 octets, or empty
  • Post-Reset Value: Persisted

The Ed25519 public key of the device identity (see (#identity-model)). The public key is also emitted as the success response when the private key is installed or generated (see (#prop-dev-private-key)).

An empty value means the device has no device identity. A conforming device does not report one in normal operation — an identity is generated at first boot if none is stored — so hosts SHOULD treat an empty value as a fault to surface rather than as an invitation to provision one.

Frames addressed to the device identity are processed by the device itself. They are additionally delivered or queued to the host only if they independently match the host’s receive filtering (see (#receive-filtering)).

PROP 65: PROP_DEV_PRIVATE_KEY

  • Type: Single-Value, Write-Only
  • Asynchronous Updates: No
  • Required: CAP_DEV_IDENTITY
  • Value Type: 32 octets, or empty
  • Post-Reset Value: Persisted

Installs or generates the device identity private key. An identity always exists already (see (#identity-model)), so both forms replace one:

  • Setting a 32-octet value installs it as the device identity’s Ed25519 private key. This is the recovery path — moving a known repeater’s identity onto replacement hardware — not a commissioning step.
  • Setting an empty value commands the device to generate a fresh private key entirely on-device from its cryptographically secure random number generator. On-device generation is RECOMMENDED over installation, since a generated key never exists anywhere but the radio.

In both cases, success is reported by emitting CMD_PROP_IS for PROP_DEV_KEY — carrying the resulting public key — with the command’s TID. The private key itself is never emitted. Success MUST NOT be reported before the new identity is in effect and durably stored. Replacing an existing device identity is permitted; implementations SHOULD treat the device identity’s peer list and channel keys as still valid, since they are not derived from the identity key.

This property is write-only: CMD_PROP_GET MUST fail with STATUS_UNIMPLEMENTED and MUST NOT disclose the value or whether an identity is configured (use PROP_DEV_KEY for that).

The device identity is not part of the saved snapshot (see (#saved-state)): it is durably persisted as soon as it is installed or generated, and it is changed only by another set of this property or by CMD_CLEAR. CMD_RESTORE never reverts it — though it does read the identity a snapshot was taken under, and refuses to enable the PHY when it does not match (see (#cmd-restore)).

Replacing the identity takes effect for the property surface immediately and for anything the device built around the old key at the next boot. The old key stops being one the device claims at once, so a device running a device node MUST stop originating traffic under it rather than continue until the reboot.

Installing a private key is subject to the same transport security requirements as all key provisioning (see (#provisioning-security)).

PROP 66: PROP_DEV_CHANNEL_KEYS

  • Type: Multiple-Value, Read-Write
  • Has Item Length Prefix: No
  • Asynchronous Updates: No
  • Required: CAP_DEV_IDENTITY
  • Item Form: 32 octets (the channel key)
  • Digest Form: 2 octets (the derived channel identifier)
  • Remove Selector: the 32-octet channel key
  • Post-Reset Value: Empty, or restored from saved state

The set of channel keys belonging to the device identity — channels the radio’s own node participates in (for example, a site-infrastructure management channel). These are independent of the host domain: they survive host replacement and are distinct from PROP_HOST_CHANNEL_KEYS.

For each key the device derives the 2-byte channel identifier and the channel’s K_enc/K_mic (see Multicast Packet Keys). The digest form reported for each entry is that derived channel identifier; the key itself is never read back.

Device channel keys do not create implicit host receive filters: frames on these channels are consumed by the device node and reach the host only through the host’s own filtering.

PROP 67: PROP_DEV_PEERS

  • Type: Multiple-Value, Read-Write
  • Has Item Length Prefix: No
  • Asynchronous Updates: No
  • Required: CAP_DEV_IDENTITY
  • Item Form: 32 octets (the peer’s Ed25519 public key)
  • Remove Selector: the 32-octet public key
  • Post-Reset Value: Empty, or restored from saved state

The device identity’s peer list: the set of peer public keys the device node recognizes and may communicate with securely. Because the device holds the device identity’s private key, it performs its own key agreement (Unicast Key Agreement) for these peers — no symmetric keys are provisioned, and the entries contain no secret material.

How the device node uses this list (management access control, secure diagnostics, and so on) is application behavior outside the scope of this protocol.

PROP 68: PROP_DEV_NAME

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_DEV_NAME
  • Value Type: 1–64 octets of UTF-8, without U+0000
  • Post-Reset Value: Implementation-defined default, or restored from saved state

The operator-assigned, human-readable name of the physical device. It is independent of the device and host cryptographic identities and MUST NOT be derived from a bonded host or other host-domain state.

Setting the property changes the live name immediately. Like other ordinary device-domain configuration, it is included in a CMD_SAVE snapshot but is not independently persisted merely by being set. Applications and transports that present the device to a person SHOULD use this value when practical. They MAY shorten it to fit a constrained presentation, but MUST NOT split a UTF-8 code point when doing so.

The name is intentionally public metadata. Operators should assume that any value used in discovery advertisements can be observed by nearby devices.

PROP 69: PROP_BATTERY

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: Yes
  • Required: CAP_BATTERY
  • Value Type: Battery status snapshot (see below), or empty
  • Post-Reset Value: Current measurement, or empty if reporting is unsupported

A device advertising CAP_BATTERY has a battery capable of powering its operation and recognizes this property. The capability does not require the hardware to support reporting any measurement: an implementation that cannot report battery status at all answers CMD_PROP_GET successfully with an empty value.

A non-empty value is a snapshot of the battery measurements the platform supports, taken as one measurement event:

OctetsField
1Field flags
0 or 2Battery voltage, UINT16_LE, millivolts
0 or 1Battery level, UINT8, percent (0–100)
0+Charge state, PUI

Bits 0 (voltage), 1 (level), and 2 (charge state) of the field flags octet indicate which fields are present; present fields follow in the order above. Bits 3–7 are reserved and MUST be zero; a host MUST treat a value with a reserved bit set, or whose length does not match its field flags, as malformed.

Which fields a platform can report is fixed for a given hardware and firmware configuration; an individual snapshot carries those it can currently substantiate. A field is absent either because the implementation never reports that measurement, or because the value is not derivable in the device’s present state — a level estimated from resting terminal voltage is not obtainable while the pack is charging, and a charger that reports no completion signal offers no moment at which to recalibrate one. An implementation MUST NOT report a value it knows to be unreliable in place of omitting the field.

Absence MUST NOT be used to indicate a depleted or disconnected battery, and it is not how a failed measurement is reported: an implementation whose attempt to take a reading fails answers CMD_PROP_GET with STATUS_FAILURE.

A host MUST treat an absent field as unknown at that instant, and MUST NOT carry a value forward from an earlier snapshot in its place.

The value returned by CMD_PROP_GET reflects a measurement performed when the request is serviced, not a previously cached reading; concurrent requests MAY share one measurement. How each field is produced is platform-defined — in particular, the level estimate is not necessarily derived from the voltage measurement, and a platform with a fuel gauge may report a level without reporting a voltage at all.

The fields:

Battery voltage
The measured voltage at the battery terminals, in millivolts. This is the battery voltage, not an external-power input or regulated system voltage; it may therefore reflect the normal voltage elevation that occurs while the battery is charging.
Battery level
The implementation’s estimate of the battery’s state of charge, as an integer percentage from 0 through 100 inclusive. A host MUST NOT derive this value from the voltage field or assume that successive estimates change monotonically.
Charge state
The current battery charge state:
ValueName
0BATTERY_CHARGE_STATE_DISCHARGING
1BATTERY_CHARGE_STATE_CHARGING
2BATTERY_CHARGE_STATE_CHARGED
BATTERY_CHARGE_STATE_DISCHARGING
The charging system reports neither active charging nor charge completion. This is the charge state used for a disconnected battery when the implementation can detect that condition; an absent field never carries that meaning.
BATTERY_CHARGE_STATE_CHARGING
The charging system reports that the battery is actively receiving charge.
BATTERY_CHARGE_STATE_CHARGED
External power is present and the charging system reports that charging has completed. A battery at 100 percent while operating without external power remains in BATTERY_CHARGE_STATE_DISCHARGING.

The property contains live, read-only state. It is never included in a saved snapshot and is not changed by CMD_RESTORE. A device MAY emit unsolicited CMD_PROP_IS updates when the reported snapshot changes. Such updates SHOULD be coalesced or rate-limited so that measurement noise does not produce excessive ULCP traffic.

PROP 70: PROP_MAC_REPEATER_ENABLED

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_REPEATER
  • Value Type: BOOL
  • Post-Reset Value: Persisted

The first of the device-behavior settings (property identifiers 70–95). When true, the device identity acts as an autonomous mesh repeater: its on-board node forwards overheard routable frames according to Repeater Operation, and it sets the repeater capability bit in its node identity. When false, the device identity does not forward and the bit is clear.

The capability bit is a statement of fact and MUST track the live forwarding state. The advertised role is a separate matter: it is configuration, set through PROP_IDENT_ROLE (see (#prop-ident-role)), and defaults to being derived from this flag rather than being fixed by it. A mobile repeater and a fixed tracker are both expressible.

This property governs only the forwarding behavior of the device identity. It is independent of PROP_MAC_PROMISCUOUS (a session-scoped host-delivery mode) and of the host identity, which never forwards.

The flag is device-domain state: it is part of the saved snapshot, so a CMD_SAVE arms an unattended repeater across power cycles, and it survives a change of host.

Forwarding parameters other than the on/off switch — region codes, minimum RSSI/SNR, and flood-contention tuning — are not exposed by this property in the current protocol revision; a repeater applies its local defaults. Later revisions MAY define additional device-behavior properties (identifiers 70–95) to configure them.

PROP 71: PROP_IDENT

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: No
  • Required: CAP_IDENT
  • Value Type: Signed node-identity payload

The device identity’s complete signed node identity: the canonical payload encoding — role, capabilities, and the descriptive options the device advertises — followed by its 64-octet detached EdDSA signature over that encoding.

This is the same statement the device makes over the air, in its standalone framing. A device MUST build it from the same values it would advertise in an Identity Request response, so a host reading it locally and a peer hearing it on the mesh cannot disagree about what the device is. It differs from that response in exactly two ways, both structural: it carries no request nonce, and it is authenticated by the signature rather than by an enclosing authenticated unicast.

The contents are nonce-free and timestamp-free, so the value is a function of the device’s configuration alone. A device MAY cache it, but is not required to: reading this property is an operator-scale event.

PROP 72: PROP_IDENT_ROLE

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_IDENT
  • Value Type: UINT8, or empty
  • Post-Reset Value: Empty, or restored from saved state

The ROLE byte the device identity advertises (see Node Primary Role).

An empty value — the factory default — means the device derives the role from what it is actually doing: Repeater while PROP_MAC_REPEATER_ENABLED is set, Tracker otherwise. Any other value is advertised verbatim.

Role and forwarding are deliberately separate. Forwarding is a fact, reported through the repeater capability bit; the role is how the device presents itself, which is the operator’s choice. Deriving it by default keeps the common cases right without a configuration step, and setting it explicitly expresses the ones derivation cannot reach — a repeater that is also mobile, a fixed node that is not a repeater.

Tethering does not appear here, or anywhere in a node identity. Whether some host is currently attached over the local control link is a transient local relationship, not a durable characteristic of the node, and the mesh has no business knowing it.

PROP 73: PROP_IDENT_MOBILE

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_IDENT
  • Value Type: BOOL
  • Post-Reset Value: 0 (false), or restored from saved state

Whether the device identity advertises the mobile capability bit: true for a device that moves, false for one installed in a fixed location.

Orthogonal to PROP_IDENT_ROLE and to PROP_MAC_REPEATER_ENABLED, and orthogonal to whether a host is tethered. A hand-carried repeater and a pole-mounted sensor are both ordinary configurations.

PROP 96: PROP_HOST_KEY

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_HOST_FILTER
  • Value Type: 32 octets, or empty
  • Post-Reset Value: Empty

The Ed25519 public key of the tethered host identity. Setting this property tells the device which node identity it is assisting; an empty value means no host identity is configured.

Setting this property to a value different from its current value resets the entire host domain, as specified in (#host-replacement). Setting it to its current value is idempotent.

Like the rest of the host domain, this property is never saved: it is empty at every power-on, whatever the radio was doing before.

A configured host key acts as an implicit destination-hint receive filter (see (#receive-filtering)).

PROP 97: PROP_HOST_CHANNEL_KEYS

  • Type: Multiple-Value, Read-Write
  • Has Item Length Prefix: No
  • Asynchronous Updates: No
  • Required: CAP_HOST_KEYS
  • Item Form: 32 octets (the channel key)
  • Digest Form: 2 octets (the derived channel identifier)
  • Remove Selector: the 32-octet channel key
  • Post-Reset Value: Empty

The set of channel keys provisioned for the host identity. For each key the device derives the channel identifier and the channel K_enc/K_mic; the digest form is the derived channel identifier, and the key itself is never read back.

Each derived channel identifier acts as an implicit channel receive filter (see (#receive-filtering)). Host channel keys serve two assistance purposes:

  • recognizing multicast traffic on the host’s channels while the host is detached, so it can be queued; and
  • recognizing blind unicast traffic addressed to the host identity, which requires the channel key to decrypt the concealed destination/source addresses (see Blind Unicast Processing) and to form the combined blind unicast payload keys used for authentication and acknowledgement.

Channel keys are group-membership credentials, not host private keys, so provisioning them is consistent with the security boundary. They still grant whoever holds the device the ability to read and send traffic on those channels; see (#provisioning-security).

PROP 98: PROP_HOST_PEER_KEYS

  • Type: Multiple-Value, Read-Write
  • Has Item Length Prefix: No
  • Asynchronous Updates: No
  • Required: CAP_HOST_KEYS
  • Item Form: Structure, 64 octets
  • Digest Form: 32 octets (the peer’s public key)
  • Remove Selector: the 32-octet peer public key
  • Post-Reset Value: Empty

Pairwise symmetric key material provisioned for specific already-known peers of the host identity. The item form is:

+---------------------+-----------+-----------+
| PEER_PUBLIC_KEY     |   K_ENC   |   K_MIC   |
+---------------------+-----------+-----------+
        32 B              16 B        16 B

Figure: Peer key entry item form

Where PEER_PUBLIC_KEY is the peer’s Ed25519 public key and K_ENC and K_MIC are the stable pairwise keys for the (host, peer) pair, derived by the host as described in HKDF Inputs for Unicast. The device never derives these itself — it cannot, because it does not hold the host’s private key.

As an exception to the usual CMD_PROP_INSERT duplicate rule, inserting an entry whose PEER_PUBLIC_KEY matches an existing entry replaces that entry. Replacement updates only the stored key material: the peer’s replay baseline (see (#ack-delegation)) and any frames already queued from that peer are unaffected, since both are keyed by the peer’s identity rather than by the key values. The digest form is the peer public key alone: K_ENC and K_MIC are never read back.

Provisioned peer keys let the device authenticate inbound unicast and blind unicast from those specific peers and acknowledge it on the host’s behalf (see (#ack-delegation)). They grant no capability regarding any other peer, and do not allow the device to establish new pairwise relationships.

PROP 99: PROP_HOST_RX_FILTERS

  • Type: Multiple-Value, Read-Write
  • Has Item Length Prefix: Yes
  • Asynchronous Updates: No
  • Required: CAP_HOST_FILTER
  • Item Form: Structure
  • Remove Selector: the full item
  • Post-Reset Value: Empty

The explicit receive filter table. Each item is a filter entry:

+-------------+----------------------+
| FILTER_TYPE | FILTER_VALUE ...
+-------------+----------------------+
     1 B          type-specific

Figure: Filter entry format

TypeNameValueMatches
0FILTER_DEST_HINT3 octetsFrames whose destination hint field equals the value
1FILTER_CHANNEL_ID2 octetsChannel-addressed frames (MCST, BUNI, BUAR) whose channel identifier equals the value
2FILTER_PKT_TYPE1 octetFrames whose FCF packet-type field equals the value (0–7)

Entries with an unrecognized FILTER_TYPE, or whose value length does not match the type, fail with STATUS_INVALID_ARGUMENT.

See (#receive-filtering) for how this table combines with the implicit filters derived from PROP_HOST_KEY and PROP_HOST_CHANNEL_KEYS.

PROP 100: PROP_HOST_AUTO_ACK

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_HOST_AUTO_ACK
  • Value Type: BOOL
  • Post-Reset Value: 0 (false)

When true, the device sends MAC acknowledgements on behalf of the host identity for qualifying frames received while the host is detached, as specified in (#ack-delegation). When false, the device never transmits on the host identity’s behalf.

PROP 101: PROP_HOST_RX_QUEUE_COUNT

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: No
  • Required: CAP_HOST_RX_QUEUE
  • Value Type: UINT16_LE
  • Units: frames
  • Post-Reset Value: 0

The number of frames currently held in the inbound queue. The host typically reads this right after attaching to decide whether (and when) to issue CMD_QUEUE_DRAIN.

PROP 102: PROP_HOST_RX_QUEUE_CAPACITY

  • Type: Single-Value, Read-Write
  • Asynchronous Updates: No
  • Required: CAP_HOST_RX_QUEUE (CMD_PROP_SET support is OPTIONAL)
  • Value Type: UINT16_LE
  • Units: frames
  • Post-Reset Value: Implementation-Specific

The maximum number of frames the inbound queue can hold. Devices with a fixed queue size fail CMD_PROP_SET with STATUS_UNIMPLEMENTED; devices that allow adjustment fail values they cannot honor with STATUS_INVALID_ARGUMENT.

PROP 103: PROP_HOST_RX_QUEUE_DROPPED

  • Type: Single-Value, Read-Only
  • Asynchronous Updates: No
  • Required: CAP_HOST_RX_QUEUE
  • Value Type: UINT32_LE
  • Units: frames
  • Post-Reset Value: 0

The cumulative number of frames discarded from the inbound queue — evicted by the circular queue-full policy or otherwise not retained (see (#inbound-queueing)) — since the device last reset. A non-zero increase across a detached interval tells the host that its view of that interval is incomplete. The counter wraps modulo 2^32.

Receive Filtering

Receive filtering determines which successfully received frames are accepted for the host — delivered live when the host is attached, or queued when it is not.

The device evaluates each received frame against the union of:

  • the explicit filters in PROP_HOST_RX_FILTERS;
  • an implicit destination-hint filter for the first 3 bytes of PROP_HOST_KEY, when a host key is configured; and
  • an implicit channel filter for the derived channel identifier of each key in PROP_HOST_CHANNEL_KEYS.

A frame matching any filter is accepted. Hints and channel identifiers are prefilters, not proof (see Addressing); filtering by them can only over-accept, never mis-reject, and the host performs full cryptographic verification as usual.

The implicit destination-hint filter matches unicast traffic addressed to the host identity. Encrypted blind unicast addressed to the host is matched through its channel filter (its destination hint is concealed on the wire); the device MAY additionally use a provisioned channel key to decrypt the address block and narrow the match.

Two kinds of returning traffic identify themselves only by the MIC of a frame the host previously sent, so no destination or channel filter can address them: a MAC Ack carries no destination hint and its public ack_mic is the first 4 bytes of the acknowledged frame’s MIC, and a repeater’s onward copy of a host frame keeps the host’s MIC while its destination hint names the remote peer. The device therefore records the leading 4 MIC bytes of each frame it transmits on the host’s behalf and implicitly accepts any received frame whose trailer opens with a recorded value — for a MAC Ack this matches the returning acknowledgement, and for other packet types it matches the host’s own send being carried onward, which the host’s forwarding-confirmation machinery must overhear to stop retransmitting. These records evict lazily, so multiple echoes of one send — acks arriving over different return routes, repeats from different repeaters — are all delivered. A MAC Ack whose ack_mic matches no recorded frame is still accepted if an explicit FILTER_PKT_TYPE entry selects it.

Broadcast packets — payload-carrying broadcasts and beacons alike — are implicitly accepted for live delivery: a broadcast is addressed to every node, the host included. The rule is live-only. While the host is detached, a broadcast is queued only when an explicit filter selects it (e.g., a FILTER_PKT_TYPE entry with value 0), so ambient broadcast traffic cannot displace queued unicast frames.

Device-domain state never creates implicit host filters: frames for the device identity or its channels reach the host only if the host’s own filtering matches them.

Compatibility rule: when no host key is configured, no host channel keys are provisioned, and the explicit filter table is empty, filtering is considered unconfigured and every successfully received frame is accepted. This is exactly the minimal protocol’s behavior, so a host that implements only the minimal protocol observes no difference on a full-protocol device in its factory state. As soon as any filter (implicit or explicit) exists, only matching frames are accepted.

Promiscuous mode (see (#prop-mac-promiscuous)) bypasses filtering for live delivery only.

Inbound Queueing

When CAP_HOST_RX_QUEUE is supported and the host is detached, accepted frames are placed in a FIFO inbound queue instead of being discarded. Each queue entry records the frame, its receive metadata (RSSI, LQI, SNR), the time of reception, and whether the device acknowledged it (see (#ack-delegation)).

When the host is attached, accepted frames are delivered live over STR_PHY_RAW exactly as in the minimal protocol. Attaching does not flush the queue: frames queued while the host was away remain queued until the host issues CMD_QUEUE_DRAIN (see (#cmd-queue-drain)). Frames received after attach are therefore delivered live even while older frames remain queued, and live deliveries MAY interleave with buffered deliveries during a drain (RX_FLAG_BUFFERED distinguishes them). A host that wants to process the backlog first drains promptly after attaching and MAY defer its processing of interleaved live deliveries; RX_AGE in the buffered-frame metadata gives coarse (one-second) relative timing but is not sufficient to reconstruct a strict total order — and UMSH itself does not guarantee in-order delivery in any case.

The queue is circular: when a new frame is accepted and the queue is full, the oldest queued frame is discarded and the new frame is appended. The queue therefore always holds the most recent accepted traffic. Every frame discarded by this eviction increments PROP_HOST_RX_QUEUE_DROPPED.

Eviction can discard a frame that was already acknowledged on the host’s behalf — the sender believes it delivered, but the host will never receive it. This is the same best-effort custody semantic that applies to power loss (see (#ack-delegation) and (#saved-state)): a delegated ack asserts volatile custody, not guaranteed delivery.

Duplicate detection for queueing uses the standard final-destination mechanisms of replay detection: per-peer frame-counter state and the recent accepted-MIC cache used for the backward window, where the device holds the keys to apply them. A frame identified as a previously accepted frame MUST NOT consume an additional queue slot; it is coalesced with the existing entry. A Route Retry form of a queued frame is the same logical packet (same MIC and frame counter) and coalesces with it. Coalescing a duplicate is separate from acknowledging it — a coalesced duplicate may still have its ack retransmitted under the duplicate-acknowledgement window (see (#ack-delegation)). For frames the device cannot authenticate (no provisioned keys), no protocol-defined duplicate detection applies and each received frame occupies its own entry.

Buffered-Frame Metadata

The Recv metadata of STR_PHY_RAW (see Metadata for Recv) is extended with two trailing fields:

  • RX_FLAGS (u8): Buffered-frame flags
    • RX_FLAG_BUFFERED Bit 0: The frame was held in the inbound queue and is being delivered by CMD_QUEUE_DRAIN.
    • RX_FLAG_ACKED Bit 1: The device already transmitted a MAC ack for this frame on the host’s behalf. The host MUST NOT send another ack for it.
    • All other bits: RESERVED, transmitted as zero
  • RX_AGE (u32, little-endian): Seconds elapsed between reception of the frame and its delivery to the host. Zero for live delivery.

As with the existing metadata fields, the metadata may be truncated at any field boundary; absent fields are treated as zero. Live deliveries MAY therefore continue to omit these fields entirely, which keeps the encoding byte-compatible with the minimal protocol.

Acknowledgement Delegation

With PROP_HOST_AUTO_ACK enabled, the device acknowledges qualifying inbound frames so that senders’ retransmission logic is satisfied while the host is away. The device MUST transmit a MAC ack for a received frame if and only if all of the following hold:

  1. PROP_HOST_AUTO_ACK is true and no host is attached.
  2. The frame’s packet type requests acknowledgement: UNAR, or BUAR where the device also holds the frame’s channel key.
  3. The frame is addressed to the host identity: its (possibly decrypted) destination hint matches PROP_HOST_KEY, and its source resolves to an entry in PROP_HOST_PEER_KEYS — by full public key when the S flag is set, or by unique 3-byte prefix match otherwise.
  4. The frame authenticates: its MIC verifies under the pairwise K_MIC for UNAR, or under the combined blind unicast payload keys for BUAR.
  5. The frame is accepted as new by the replay-detection rules, applied per provisioned peer. The device advances a peer’s replay baseline only when it accepts a frame from that peer into the queue; a frame it fails to store leaves the baseline unchanged, so its retransmissions remain acceptable later.
  6. The frame was placed in the inbound queue (see (#inbound-queueing)). Because the queue is circular, placement normally succeeds by evicting the oldest entry when full; a frame that nevertheless cannot be stored (for example, one exceeding the device’s buffer) is not acknowledged, so the sender keeps retrying until the host returns.

Duplicates. An authenticated frame that replay detection identifies as a previously accepted frame — typically a retransmission whose original ack was lost — is not queued again, but the device MAY retransmit its acknowledgement under the core duplicate-acknowledgement window: only when the frame authenticates and its counter is no more than 8 behind the peer’s baseline, and without advancing or otherwise modifying the replay baseline. Re-acknowledging a duplicate is independent of queue coalescing (see (#inbound-queueing)) and does not mark anything newly accepted. Frames farther behind the baseline MUST NOT be acknowledged.

Reboot. Per-peer replay baselines are not saved (see (#saved-state)). After a device reset, the first authenticated frame accepted from a provisioned peer re-establishes that peer’s baseline at face value, exactly as on first contact (see Counter Resynchronization). The consequence is that after a reboot, previously captured authenticated frames may be accepted, queued, and acknowledged if replayed in a counter sequence acceptable from the newly established baseline. The host MAC remains authoritative for duplicate suppression when the frames are eventually delivered, so this creates a limited availability and resource-consumption window (queue slots and delegated acks), but it does not permit forgery or duplicate application delivery. Implementations concerned about this threat MAY persist a compact per-peer counter watermark (batched or range-reserved to limit flash wear), but hosts MUST NOT assume they do.

Custody. A delegated ack acknowledges volatile custody by default: the frame is held in RAM until drained, and the loss window on power failure is documented in (#saved-state). Implementations that persist the queue provide durable custody, but hosts and application protocols MUST NOT rely on it.

The acknowledgement is an ordinary MAC Ack packet: the ack MIC is the first 4 bytes of the acknowledged frame’s MIC and the 4-byte ack tag is computed as specified in Ack Tag Construction, using the provisioned pairwise keys (combined with the channel keys for BUAR). The ack carries no destination hint. If the original frame carried a flood hop count, the ack’s FHOPS_REM is initialized from the original frame’s FHOPS_ACC.

Delegated ack transmissions use the device’s normal transmit path and are subject to the configured duty-cycle limit; the device MUST NOT exceed the limit to send an ack. An ack that cannot be sent leaves the queued frame marked unacknowledged.

Frames that are accepted but fail any of conditions 2–5 — no peer key, no channel key, authentication impossible to evaluate — are still queued (subject to filtering); they are simply not acknowledged. The host performs its own verification after draining and may ack late if the application finds that useful.

While a host is attached, the device never acks on its behalf: live-delivered frames are the host’s responsibility. Acks generated by the device identity for its own traffic are ordinary device-node behavior and are not governed by this section.

Provisioning Security

Provisioning moves real key material onto the device, within the limits of the security boundary: channel keys and per-peer symmetric keys — and the device identity’s own private key — but never the host’s private key. The rules:

  • All symmetric key material, and the device identity private key, is write-only. CMD_PROP_GET and all device-emitted notifications report key-bearing properties without their secrets (see (#multi-value-properties)): peer public keys without K_ENC/K_MIC, derived channel identifiers (the digest form) instead of channel keys, and never the device private key. This holds for both identities’ key tables. These read-backs let the host verify what is provisioned after a reconnect without any secret ever crossing the link a second time — which matters because more than one host may be able to attach over the radio’s lifetime (transport bonds are possession credentials, not identity credentials), and a later host must not be able to extract an earlier host’s keys.
  • Commands that carry key material — CMD_PROP_SET and CMD_PROP_INSERT for the key tables, and any set of PROP_DEV_PRIVATE_KEYMUST NOT be carried over a transport that does not meet the requirements of the transport’s security binding: physical possession for serial transports, or an encrypted bonded LESC link as specified in ULCP over BLE.
  • A compromised or stolen device exposes the provisioned channels, the provisioned pairwise conversations, and its own device identity, but cannot impersonate the host to any new peer, cannot sign as the host, and cannot decrypt traffic for peers or channels that were never provisioned.
  • Hosts SHOULD provision the minimum useful set of peers and channels, SHOULD remove entries that are no longer needed, and SHOULD prefer on-device generation of the device identity over installing one.
  • A device advertising CAP_SAVE MUST store persisted key material in the most protected storage available to it.

Additional Status Codes

The full protocol assigns two additional status codes (see Status Codes):

IdName
19STATUS_ALREADY
20STATUS_ITEM_NOT_FOUND
STATUS_ALREADY
The requested state is already in effect; in particular, the item passed to CMD_PROP_INSERT is already present in the property.
STATUS_ITEM_NOT_FOUND
The item or selector passed to CMD_PROP_REMOVE does not match any item in the property.

Additional Reset Codes

The full protocol assigns one additional reset code (see Reset Codes):

IdName
115STATUS_RESET_RESTORED
STATUS_RESET_RESTORED
Protocol reset into the saved snapshot, emitted when a device completes CMD_RESTORE in its reset form (see (#cmd-restore)). Unlike the other reset codes, this one does not indicate a hardware or firmware restart: the transport link and attach state survive it. Like STATUS_RESET_SOFTWARE, it is emitted during normal operation and does not indicate a problem.

Additional Capabilities

The full protocol assigns the following capability codes (see Capabilities):

CodeNameRequiresGrants
32CAP_HOST_FILTERPROP_HOST_KEY, PROP_MAC_PROMISCUOUS, PROP_HOST_RX_FILTERS, and the receive-filtering behavior
33CAP_HOST_RX_QUEUECAP_HOST_FILTERThe inbound queue, its properties, CMD_QUEUE_DRAIN, and the buffered-frame metadata
34CAP_HOST_KEYSCAP_HOST_FILTERPROP_HOST_CHANNEL_KEYS and PROP_HOST_PEER_KEYS
35CAP_HOST_AUTO_ACKCAP_HOST_KEYS, CAP_HOST_RX_QUEUEPROP_HOST_AUTO_ACK and acknowledgement delegation
36CAP_SAVECMD_SAVE, CMD_RESTORE, PROP_SAVED, and boot-time restoration of saved state
37CAP_DEV_IDENTITYThe device identity: PROP_DEV_KEY, PROP_DEV_PRIVATE_KEY, PROP_DEV_CHANNEL_KEYS, PROP_DEV_PEERS
38CAP_DEV_NAMEPROP_DEV_NAME
39CAP_BATTERYBattery-powered operation and PROP_BATTERY
40CAP_REPEATERCAP_DEV_IDENTITYPROP_MAC_REPEATER_ENABLED and autonomous repeater forwarding by the device identity
41CAP_IDENTCAP_DEV_IDENTITYPROP_IDENT, PROP_IDENT_ROLE, PROP_IDENT_MOBILE — serving and configuring the device identity’s advertised node identity

A device MUST NOT advertise a capability without also advertising the capabilities it requires. CMD_PROP_INSERT/CMD_PROP_REMOVE, CMD_CLEAR, and the two additional status codes are part of the base protocol and need no capability; a device that defines no mutable multi-value properties simply has nothing to apply them to.