Finalize USB watchdog management format as MGR2
This commit is contained in:
1 parent
eeb25a8b5f
commit
5ee6059564
13 files changed
+269
-129
No files matched your search
@@ -7,12 +7,10 @@
|
||||
// which Web Crypto exposes directly. AES-CBC with a zero IV supplies the
|
||||
// required AES block primitive; only its first ciphertext block is used.
|
||||
const HEADER = 83;
|
||||
const MGR2_HEADER = 98;
|
||||
const CURRENT_HEADER = 111;
|
||||
const TAG = 16;
|
||||
const ENTRY = 13;
|
||||
const PER_PAGE = 6;
|
||||
const MGR2_PER_PAGE = 5;
|
||||
const CURRENT_PER_PAGE = 4;
|
||||
const MAX_KEYS = 36;
|
||||
const ZERO_BLOCK = new Uint8Array(16);
|
||||
@@ -221,17 +219,16 @@
|
||||
function layout(payload) {
|
||||
const magic = bytesToAscii(payload, 0, 4);
|
||||
if (magic === "MGR1") return { header: HEADER, perPage: PER_PAGE };
|
||||
if (magic === "MGR2") return { header: MGR2_HEADER, perPage: MGR2_PER_PAGE };
|
||||
if (magic === "MGR3") return { header: CURRENT_HEADER, perPage: CURRENT_PER_PAGE };
|
||||
if (magic === "MGR2") return { header: CURRENT_HEADER, perPage: CURRENT_PER_PAGE };
|
||||
return null;
|
||||
}
|
||||
|
||||
function canonicalLength(payload) {
|
||||
const format = layout(payload);
|
||||
if (!format || payload.length < format.header + TAG) {
|
||||
managementError("This data does not begin with a complete MGR1/MGR2/MGR3 management page.");
|
||||
managementError("This data does not begin with a complete MGR1/MGR2 management page.");
|
||||
}
|
||||
if (format.header >= MGR2_HEADER && ((uint16LE(payload, 83) & 0xf800) || (payload[85] & 0xc0))) {
|
||||
if (format.header === CURRENT_HEADER && ((uint16LE(payload, 83) & 0xf800) || (payload[85] & 0xc0))) {
|
||||
managementError("Reserved USB status bits must be zero.");
|
||||
}
|
||||
if (format.header === CURRENT_HEADER) usbWatchdogEvent(payload);
|
||||
@@ -306,7 +303,7 @@
|
||||
|
||||
function inputByteStreams(input) {
|
||||
if (typeof input !== "string" || input.trim() === "") {
|
||||
managementError("Paste one or more MGR1/MGR2/MGR3 payloads or GroupData packet hex values first.");
|
||||
managementError("Paste one or more MGR1/MGR2 payloads or GroupData packet hex values first.");
|
||||
}
|
||||
if (input.length > 32768) managementError("The pasted value is too large to be management-report data.");
|
||||
// A wrapped single page is one candidate, not an invalid first-line
|
||||
@@ -359,7 +356,7 @@
|
||||
}
|
||||
}
|
||||
if (!found.length) {
|
||||
managementError("No complete management page was found. Paste canonical MGR1/MGR2/MGR3 payload hex or an entire GroupData packet.");
|
||||
managementError("No complete management page was found. Paste canonical MGR1/MGR2 payload hex or an entire GroupData packet.");
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -393,7 +390,7 @@
|
||||
}
|
||||
|
||||
function usbStatus(payload) {
|
||||
if (!["MGR2", "MGR3"].includes(bytesToAscii(payload, 0, 4))) return null;
|
||||
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
|
||||
const flags = uint16LE(payload, 83);
|
||||
const names = ["supported", "loggingEnabled", "watchdogEnabled", "hostConnected",
|
||||
"readerConnected", "stalled", "recovering", "recoveryDeferred", "persistenceReady", "watchdogAuto", "loggerActive"];
|
||||
@@ -408,7 +405,7 @@
|
||||
}
|
||||
|
||||
function usbWatchdogEvent(payload) {
|
||||
if (bytesToAscii(payload, 0, 4) !== "MGR3") return null;
|
||||
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
|
||||
const code = payload[98];
|
||||
const sequence = uint32LE(payload, 107);
|
||||
if (!sequence) {
|
||||
@@ -626,8 +623,8 @@
|
||||
: event.actionCode === 4 ? "Reboot request was cancelled; no physical reset is claimed."
|
||||
: "Recovery attempt was recorded; this does not prove successful recovery."]);
|
||||
} else {
|
||||
values.push(["Last USB watchdog event", model.public.protocol === "MGR3"
|
||||
? "none recorded" : "unavailable in MGR1/MGR2"]);
|
||||
values.push(["Last USB watchdog event", model.public.protocol === "MGR2"
|
||||
? "none recorded" : "unavailable in MGR1"]);
|
||||
}
|
||||
summary.replaceChildren();
|
||||
values.forEach(([term, description]) => {
|
||||
|
||||
+13
-10
@@ -1,6 +1,6 @@
|
||||
# Management report decoder
|
||||
|
||||
Paste MGR1, MGR2, or MGR3 management-report payloads or complete GroupData packets from a
|
||||
Paste MGR1 or MGR2 management-report payloads or complete GroupData packets from a
|
||||
packet analyzer to read a radio's public management information. Everything,
|
||||
including password authentication and ACL decryption, happens locally in this
|
||||
browser. The report, password, and candidate public key are never uploaded.
|
||||
@@ -13,13 +13,13 @@ by someone who does not know that password.
|
||||
## Decode a management report
|
||||
|
||||
<div class="management-tool" data-management-decoder>
|
||||
<label for="management-packet-input">MGR1/MGR2/MGR3 payload or complete GroupData packet hex</label>
|
||||
<label for="management-packet-input">MGR1/MGR2 payload or complete GroupData packet hex</label>
|
||||
<textarea
|
||||
id="management-packet-input"
|
||||
data-role="input"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
placeholder="Paste analyzer Raw Data, canonical MGR1/MGR2/MGR3 payload hex, or one MQTT raw value per line"
|
||||
placeholder="Paste analyzer Raw Data, canonical MGR1/MGR2 payload hex, or one MQTT raw value per line"
|
||||
aria-describedby="management-packet-help"
|
||||
></textarea>
|
||||
<p class="management-help" id="management-packet-help">
|
||||
@@ -69,8 +69,7 @@ by someone who does not know that password.
|
||||
- Complete `PAYLOAD_TYPE_GRP_DATA` (`0x06`) analyzer/MQTT packet hex. It checks
|
||||
the MeshCore route header, encoded path length, version-specific page bounds, and required
|
||||
zero padding.
|
||||
- A canonical payload beginning with `4D475231` (`MGR1`), `4D475232` (`MGR2`),
|
||||
or `4D475233` (`MGR3`).
|
||||
- A canonical payload beginning with `4D475231` (`MGR1`) or `4D475232` (`MGR2`).
|
||||
- One raw packet or canonical payload per line; duplicate observations of an
|
||||
identical page are deduplicated.
|
||||
|
||||
@@ -79,22 +78,26 @@ the report-specific fingerprints and the administrator and/or OTA-signer flags.
|
||||
It cannot turn a fingerprint back into a full key. Use the optional candidate
|
||||
field to test a specific full public key.
|
||||
|
||||
MGR2 and MGR3 also show USB logging, host/reader connection, logger-client activity, stalled/recovering/deferred
|
||||
MGR2 also shows USB logging, host/reader connection, logger-client activity, stalled/recovering/deferred
|
||||
state, recovery stage, saved backoff, retry interval/inactive seconds, Auto qualification
|
||||
seconds, and persistence
|
||||
readiness. Unsupported observation is shown explicitly; legacy MGR1 has no
|
||||
USB block and shows it as unavailable. These values are the report-start
|
||||
snapshot, not proof that a Pi or host application is healthy. Older decoder
|
||||
copies require an update for MGR3.
|
||||
copies require an update for MGR2.
|
||||
|
||||
MGR3 additionally shows the latest USB watchdog action, reason mask and names,
|
||||
MGR2 also shows the latest USB watchdog action, reason mask and names,
|
||||
advisory node-clock timestamp, recorded-boot uptime seconds, event sequence,
|
||||
and whether the record was durably saved. Software recovery and re-enumeration
|
||||
mean attempted actions, not successful repairs. A reboot request is not proof
|
||||
that a reboot completed; a later cancellation can replace it. The node clock
|
||||
can be wrong, even when it resembles a valid date. Event uptime and sequence
|
||||
remain useful for ordering. MGR1/MGR2 do not carry this event field and show it
|
||||
as unavailable. All 36 ACL entries still fit, using up to nine MGR3 pages.
|
||||
remain useful for ordering. MGR1 does not carry this event field and shows it
|
||||
as unavailable. All 36 ACL entries still fit, using up to nine MGR2 pages.
|
||||
|
||||
The earlier USB-only MGR2 development layout was never released. This decoder
|
||||
accepts the finalized MGR2 format with its 111-byte public header and latest
|
||||
watchdog event.
|
||||
|
||||
Reports contain a frozen latest-event snapshot, not immediate watchdog alerts
|
||||
or a complete event history. See [USB logging watchdog](usb_logging_watchdog.md)
|
||||
|
||||
+12
-16
@@ -1,4 +1,4 @@
|
||||
# Management reports (MGR1/MGR2/MGR3)
|
||||
# Management reports (MGR1/MGR2)
|
||||
|
||||
Available on repeater (including observer), room-server and sensor firmware.
|
||||
Companions/terminal-chat nodes and KISS modems do not originate these reports.
|
||||
@@ -6,10 +6,11 @@ Off by default. A management password and explicit enable are both required.
|
||||
No radio settings, existing preferences layout, or OTA authorization policy is
|
||||
changed by enabling reporting. This protocol does **not** authorize updates.
|
||||
|
||||
Current firmware emits `MGR3`, including a public USB logging/recovery snapshot
|
||||
and its latest watchdog event. Updated decoders still accept legacy `MGR1` and
|
||||
`MGR2`; older decoder copies need an update to read MGR3. Reporting USB status
|
||||
does not enable logging or change retry policy.
|
||||
Current firmware emits `MGR2`, including a public USB logging/recovery snapshot
|
||||
and its latest watchdog event. Updated decoders still accept released `MGR1`;
|
||||
older decoder copies need an update to read MGR2. The earlier USB-only MGR2
|
||||
development layout was never released and was replaced before release.
|
||||
Reporting USB status does not enable logging or change retry policy.
|
||||
|
||||
## CLI
|
||||
|
||||
@@ -97,7 +98,7 @@ is bounded to 1.5 KiB, plus a small configuration object and temporary stack use
|
||||
## Routing and MQTT
|
||||
|
||||
Both direct/path and flood reports use **`PAYLOAD_TYPE_GRP_DATA` (`0x06`)**.
|
||||
The route bits independently select direct or flood. `MGR1`/`MGR2`/`MGR3` are application
|
||||
The route bits independently select direct or flood. `MGR1`/`MGR2` are application
|
||||
extensions with **literally plaintext public fields**, not a call to the ordinary
|
||||
encrypted `createGroupDatagram()` builder. A fixed public marker is not an owner
|
||||
or password-derived channel ID. There is no outer channel encryption.
|
||||
@@ -133,11 +134,11 @@ For a browser-local decoder that also decrypts and authenticates ACL entries,
|
||||
use the [Management report decoder](management_decoder.md). It does not upload
|
||||
the captured packet or password.
|
||||
|
||||
## Canonical MGR3 payload (little endian)
|
||||
## Canonical MGR2 payload (little endian)
|
||||
|
||||
| Offset | Bytes | Field |
|
||||
|---:|---:|---|
|
||||
| 0 | 4 | `MGR3` |
|
||||
| 0 | 4 | `MGR2` |
|
||||
| 4 | 16 | First 16 bytes of reporter public key |
|
||||
| 20 | 4 | Persisted report sequence (never wraps; exhaustion stops TX) |
|
||||
| 24 | 4 | RTC Unix timestamp; advisory, may be wrong |
|
||||
@@ -176,7 +177,7 @@ the captured packet or password.
|
||||
| after ACL | 16 | Full AES-SIV authentication tag |
|
||||
| after tag | 0-15 | Zero padding to group-compatible length; not part of canonical payload |
|
||||
|
||||
MGR3 retains all 36 ACL entries, using four per page and up to nine pages. The
|
||||
MGR2 retains all 36 ACL entries, using four per page and up to nine pages. The
|
||||
largest canonical page is 179 bytes and already group-compatible;
|
||||
the radio payload budget is unchanged. The USB snapshot is frozen with the
|
||||
other public fields at report start and repeated on each page. It is not a
|
||||
@@ -230,11 +231,6 @@ normal reboot and mode changes, but is not a log of hardware hangs or sudden
|
||||
power failures. See [USB logging watchdog](usb_logging_watchdog.md) for storage
|
||||
failure, cancellation, and review limitations.
|
||||
|
||||
Legacy MGR2 retains the same offsets through byte 97, followed by encrypted ACL
|
||||
entries at byte 98 and the authentication tag. It carries five entries per page
|
||||
and up to eight pages. Its USB status is available, but watchdog event history
|
||||
is unavailable rather than an empty modern record.
|
||||
|
||||
Legacy MGR1 retains the same offsets through byte 82, followed immediately
|
||||
by encrypted ACL entries at byte 83 and the authentication tag. It carries
|
||||
six entries per page and up to six pages, with no USB block. Its absent USB
|
||||
@@ -273,8 +269,8 @@ needs a candidate administrator's full key to identify a fingerprint.
|
||||
|
||||
Encryption is [RFC 5297 AES-SIV-CMAC-256](https://www.rfc-editor.org/rfc/rfc5297),
|
||||
using the existing rweather AES primitive. The single associated-data string is
|
||||
the complete clear header: 111 bytes for MGR3, including USB status and its event,
|
||||
98 bytes for MGR2, or 83 bytes for MGR1. All retain the existing derivation domains; the authenticated
|
||||
the complete clear header: 111 bytes for MGR2, including USB status and its event,
|
||||
or 83 bytes for MGR1. Both retain the existing derivation domains; the authenticated
|
||||
magic and layout distinguish their wire versions. Ciphertext is exactly the ACL byte length.
|
||||
Full 16-byte tag, no truncation. This deterministic misuse-resistant mode avoids
|
||||
reliance on the firmware's noncryptographic general-purpose RNG or RTC nonces.
|
||||
|
||||
@@ -155,10 +155,10 @@ an exact trigger time; this record covers the staged USB logging watchdog.
|
||||
| `defer`, `fs` | Ownership/backend deferral and verified durable persistence readiness. |
|
||||
| `rec`, `boot` | Attempted USB recoveries and durable reset authorizations. Attempts save the event and recovery count together; a failed save can leave the live recovery count unsaved. The reset count can include a later safety/health veto; it is not a count of completed physical reboots. |
|
||||
|
||||
The [MGR3 management report](management_reports.md) exports the USB status and
|
||||
The [MGR2 management report](management_reports.md) exports the USB status and
|
||||
the same latest-event record as a frozen, authenticated public snapshot. Updated
|
||||
Python and browser decoders retain MGR1/MGR2 support; older decoder copies must
|
||||
be updated to read MGR3. Events appear on the normal management schedule, not
|
||||
Python and browser decoders retain released MGR1 support; older decoder copies
|
||||
must be updated to read MGR2. Events appear on the normal management schedule, not
|
||||
as immediate alerts. Reading status does not renew the logging-client lease.
|
||||
|
||||
## Verification
|
||||
@@ -182,12 +182,16 @@ and ordinary RAK 3x72 STM32 USB Companion. The STM32 image retains all CLI
|
||||
features and its 32 KiB filesystem, but has only 368 bytes of app flash margin;
|
||||
its unsupported USB watchdog machinery is compiled out.
|
||||
|
||||
Latest-event/MGR3 verification on the same date passed 116 USB regression tests,
|
||||
Latest-event verification on the same date passed 116 USB regression tests,
|
||||
15 management tests with fatal AddressSanitizer/UndefinedBehaviorSanitizer
|
||||
checks, and the browser decoder tests. Coverage includes timestamp callbacks,
|
||||
clock corrections and rollover, legacy-state migration, commit failures,
|
||||
reboot-intent cancellation, deferred cancellation saves, and authenticated
|
||||
cross-language MGR1/MGR2/MGR3 decoding. The full native suite passed 1,713 cases
|
||||
cross-language decoding of the development report formats. The extension was
|
||||
called MGR3 during those pre-release checks, then assigned to MGR2 because the
|
||||
earlier USB-only MGR2 layout had never been released. Current decoders support
|
||||
released MGR1 and the finalized MGR2 layout; this reassignment was not a migration
|
||||
of deployed MGR2 reports. The full native suite passed 1,713 cases
|
||||
during implementation; the final packet validator also passed a fresh 116-case
|
||||
routing suite and 5,855,168 previous/current validator comparisons under fatal
|
||||
sanitizers.
|
||||
@@ -217,3 +221,9 @@ latter uses 229,020 of 229,376 app flash bytes (356 bytes of margin), retaining
|
||||
all features and the 32 KiB filesystem. The bare recipe is tighter still.
|
||||
These checks do not qualify every board or physical USB recovery. No device
|
||||
was flashed and existing `out` artifacts were not changed.
|
||||
|
||||
Finalizing the event-capable layout as MGR2 passed 1,719 native cases, 16
|
||||
management tests with real C++/Python/browser cryptography, the standalone
|
||||
browser decoder checks, and the bare STM32 Companion compile. Coverage retains
|
||||
MGR1, rejects retired formats and old prototype ciphertext, and verifies that
|
||||
invalid management sends release their packet without queueing it.
|
||||
Reference in new issue
Block a user