fix(firmware): keep onboarding digest compatible with ESP-IDF 6

This commit is contained in:
ruv 2026-09-11 09:19:33 -04:00
parent a64e9e98fb
commit 4fbe58f736
4 changed files with 50 additions and 18 deletions

View file

@ -2,9 +2,9 @@
## Status
Accepted and physically qualified with one attached ESP32 S3 and an unsigned
Mac Catalyst development build. ESP32 C6 qualification and signed distribution
remain release gates.
Accepted and physically qualified with one attached ESP32 S3 and one attached
ESP32 C6 through an unsigned Mac Catalyst development build. Signed
distribution remains a release gate.
## Context
@ -20,7 +20,7 @@ application diagnostics or provisioning receipts.
## Decision
1. Firmware 0.8.9 adds a bounded line protocol over the physical USB serial
1. Firmware 0.8.12 provides a bounded line protocol over the physical USB serial
channel. A discovery request carries a random 128 bit nonce. The device
response binds that nonce to chip family, firmware version, current node
identity, private server target, configuration state, and a pseudonymous
@ -52,6 +52,14 @@ application diagnostics or provisioning receipts.
exact chip and port check, a private backup, a matching image, and captured
boot evidence. Release publication remains a separate maintainer action.
8. The device digest uses the PSA Crypto SHA 256 interface on ESP IDF 5.4 and
6.0. This keeps the stable domain separated identity while avoiding the
legacy Mbed TLS hash API removed by ESP IDF 6.
9. ESP32 C6 mmWave UART defaults use GPIO 4 and GPIO 5. Startup rejects any
configured mmWave pin pair that overlaps the active console RX or TX pins.
This preserves the onboarding input path after a failed mmWave probe.
## Security and privacy consequences
The app gains Mac App Sandbox USB and serial device access, but not general file
@ -67,7 +75,7 @@ rescanned over USB.
## Acceptance test
Connect an ESP32 with firmware 0.8.9 to a Mac that is already receiving RuView
Connect an ESP32 with firmware 0.8.12 to a Mac that is already receiving RuView
nodes. Use only the macOS Add Sensor workflow to preserve WiFi, assign the
lowest unused identity, and set the Mac private address. Pass when the UI
reports the expected chip and firmware, the reassigned node remains fresh for
@ -77,3 +85,9 @@ and no credential appears in app logs or the provisioning receipt.
The 2026-08-31 physical run passed on an ESP32 S3. The measured witness is in
`docs/validation/2026-08-31-macos-usb-node-onboarding.md`.
The 2026-09-11 physical run passed on an ESP32 C6 over
`cu.usbserial-3120`. The Mac app flashed the image, received the nonce bound
hello receipt, preserved node identity 3, committed the server route, and
verified fresh server evidence at minus 34 dBm. The run also exposed and fixed
the GPIO 17 console RX conflict described above.

View file

@ -46,5 +46,15 @@ firmware image built under ESP-IDF 5.4, and the application image occupied
## Remaining gates
Physical ESP32 C6, new WiFi credential entry, signed Mac distribution, and
cross platform serial bridges remain unmeasured.
New WiFi credential entry, signed Mac distribution, and cross platform serial
bridges remain unmeasured.
## ESP32 C6 follow up
On 2026-09-11, an ESP32 C6 on `cu.usbserial-3120` completed the Mac app flash,
hello, configure, and verify sequence with firmware 0.8.12. The app preserved
the existing node identity 3 and verified fresh sensing server evidence at
minus 34 dBm. The board returned `RUVIEW_HELLO_OK_V1` within the bounded serial
exchange after the C6 mmWave defaults moved from the UART0 RX conflict on GPIO
17 to GPIO 4 and GPIO 5. This closes only the physical C6 onboarding gate. It
does not qualify new WiFi credential entry or signed distribution.

View file

@ -19,9 +19,9 @@ This firmware captures WiFi Channel State Information (CSI) from an ESP32-S3 (pr
> | **Fall detection** | Phase acceleration threshold | Configurable sensitivity |
> | **Programmable sensing** | WASM modules loaded over HTTP | Hot-swap, no reflash |
## Development firmware 0.8.9: guided Mac onboarding
## Firmware 0.8.12: guided Mac onboarding
The unreleased 0.8.9 source adds the protocol used by the RuView macOS Add
Firmware 0.8.12 adds the protocol used by the RuView macOS Add
Sensor workflow. A directly attached S3 or C6 can identify itself through a
nonce bound USB serial receipt, preserve its current WiFi settings, accept an
unused node ID and private LAN server address, then reboot. The Mac app does
@ -34,6 +34,10 @@ credentials are bounded, committed only in NVS, and never included in the
receipt or diagnostic output. See
[ADR 348](../../docs/adr/ADR-348-native-macos-usb-node-onboarding.md).
On ESP32 C6, the default optional mmWave UART uses GPIO 4 and GPIO 5. Firmware
refuses any mmWave configuration that overlaps the active console pins. This
prevents a sensor probe from taking over UART0 RX and disabling Mac onboarding.
## Firmware 0.8.8 in plain language
Release 0.8.8 makes the sensing stream more internally consistent and easier

View file

@ -16,8 +16,8 @@
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/sha256.h"
#include "nvs.h"
#include "psa/crypto.h"
static const char *TAG = "serial_onboard";
static nvs_config_t s_current_config;
@ -43,20 +43,24 @@ static void device_digest(char output[17])
uint8_t base_mac[6] = {0};
uint8_t digest[32] = {0};
static const uint8_t domain[] = "ruview-device-v1";
mbedtls_sha256_context context;
mbedtls_sha256_init(&context);
if (esp_efuse_mac_get_default(base_mac) == ESP_OK &&
mbedtls_sha256_starts(&context, 0) == 0 &&
mbedtls_sha256_update(&context, domain, sizeof(domain) - 1) == 0 &&
mbedtls_sha256_update(&context, base_mac, sizeof(base_mac)) == 0 &&
mbedtls_sha256_finish(&context, digest) == 0) {
uint8_t digest_input[sizeof(domain) - 1 + sizeof(base_mac)];
size_t digest_length = 0;
memcpy(digest_input, domain, sizeof(domain) - 1);
const esp_err_t mac_result = esp_efuse_mac_get_default(base_mac);
if (mac_result == ESP_OK) {
memcpy(digest_input + sizeof(domain) - 1, base_mac, sizeof(base_mac));
}
if (mac_result == ESP_OK &&
psa_crypto_init() == PSA_SUCCESS &&
psa_hash_compute(PSA_ALG_SHA_256, digest_input, sizeof(digest_input),
digest, sizeof(digest), &digest_length) == PSA_SUCCESS &&
digest_length == sizeof(digest)) {
for (size_t index = 0; index < 8; index++) {
snprintf(output + index * 2, 3, "%02x", digest[index]);
}
} else {
memcpy(output, "0000000000000000", 17);
}
mbedtls_sha256_free(&context);
}
static void emit_hello(const char *nonce)