server roles, event loops, companion transports, storage and prefs.json

Server roles (slice 7)
- CommonCLI split into upstream's shape; region CLI moved into CommonCLI;
  ClientACL, RegionMap, TransportKeyStore and both server meshes in
  upstream shape over an Arduino compat shim (helpers/compat: Arduino.h,
  Stream/Print, Serial, SHA256, FILESYSTEM/File over Zephyr fs)
- room server processAck port; `get acl` prints as upstream
- room: default name Room-XXXX, display/input rotation, live radio UI refresh

Event loops (slice 7b)
- repeater and room server share server_main_common with a ServerRole
  descriptor; shared event bits in src/mesh_events.h
- room server deadline-driven (idle wakes 2.2/s -> 0.08/s); companion
  loop() runs on housekeeping (closes the lazy contact-flush gap)
- `get loop.wakes` in debug builds (CONFIG_ZEPHCORE_LOOP_WAKE_STATS)

Companion transports and WiFi (slice 8)
- upstream MultiSerialInterface: BLE, USB/UART and TCP are each a
  BaseSerialInterface, every connected transport gets every frame;
  CompanionSerial holds a lossless reply; shared frame_txq for BLE/TCP
- SerialCompanionTransport and LinuxTCPTransport replaced by
  TcpCompanionTransport; ZephyrBLE split (gatt layout, DFU)
- WiFi companion (upstream wifi.ssid/pwd/enabled CLI, TCP port 5000) on
  boards declaring `capabilities: wifi` in zephcore.yml: the PSRAM
  ESP32-S3 boards, plus Heltec V3 (140 contacts), Wireless Tracker (130)
  and both C6 boards (280); ESP32-S3 without PSRAM gets bounded WiFi
  buffers and a measured 72 KB heap; PSRAM boards' internal heap 72 KB
- TCP queues: own depth (ZEPHCORE_TCP_QUEUE_SIZE=8), RX waits instead
  of dropping
- RAM: companion ClientACL dropped; main/BT-TX/TCP stacks sized from
  measured peaks (-7 KB nRF companions, -9 KB ESP32 companions)
- ESP32 WiFi stopped working under ~200 frames/s unicast. Fixed by:
  patches/zephyr/0017 never block the WiFi task in the RX callback;
  patches/zephyr/0018 lock shared_multi_heap (heap corruption with the
  WiFi heap in PSRAM); patches/modules/hal_espressif/0001 backport of
  hal_espressif af97fb02df (PSRAM realloc)

Storage and prefs (slice 9)
- prefs are now prefs.json through upstream's ConfigSerializer (ported
  verbatim), with upstream's key names for shared fields and ZephCore's
  own under "zc"; on first boot the binary new_prefs / repeater prefs is
  migrated and kept, so a downgrade still boots with the old settings
- helpers/PrefsCodec: both legacy binary layouts (companion 272 B,
  server 311 B) as Zephyr-free codecs; sanitizeNodePrefs is the only
  validator
- fix: server prefs could be replaced by a truncated file when a write
  failed; identity, prefs and channel writes go through one checked
  atomic replace (ZephyrFsUtil)
- fix: a corrupt cad_auto byte turned adaptive CAD off instead of the
  default on
- fix: a deliberate 300 s GPS duty on a repeater was rewritten to 48 h
  on every boot (that migration now runs on legacy files only)
- one identity loader for all roles (IdentityFile); an unusable key
  file is always parked as .bad before regenerating
- server store no longer stamps repeater defaults on room server and
  observer; companion first-boot volume policy moved into the store
  (adoptVolume)
- ZephyrDataStore 1435 -> ~850 lines
This commit is contained in:
liquidraver
2026-09-25 08:34:21 +02:00
parent b361c33901
commit 38f815a0a1
114 changed files with 11280 additions and 10308 deletions
+64 -96
View File
@@ -1,124 +1,92 @@
/*
* SPDX-License-Identifier: MIT
* TransportKeyStore - Transport key cache for region filtering
*/
#include "TransportKeyStore.h"
#include <string.h>
#include <psa/crypto.h>
#include <SHA256.h>
uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const {
/* HMAC-SHA256 using PSA Crypto */
uint8_t hmac[32];
uint8_t type = packet->getPayloadType();
/* Build message: type + payload */
uint8_t msg[MAX_PACKET_PAYLOAD + 1];
msg[0] = type;
memcpy(&msg[1], packet->payload, packet->payload_len);
size_t msg_len = 1 + packet->payload_len;
/* Import HMAC key */
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC);
psa_set_key_bits(&attr, sizeof(key) * 8);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE);
psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_key_id_t key_id;
psa_status_t status = psa_import_key(&attr, key, sizeof(key), &key_id);
if (status != PSA_SUCCESS) {
return 0;
}
size_t out_len;
status = psa_mac_compute(key_id, PSA_ALG_HMAC(PSA_ALG_SHA_256),
msg, msg_len, hmac, sizeof(hmac), &out_len);
psa_destroy_key(key_id);
if (status != PSA_SUCCESS) {
return 0;
}
/* Extract first 2 bytes as transport code (little-endian, matching Arduino SHA256 finalizeHMAC behavior) */
uint16_t code = (uint16_t)hmac[0] | ((uint16_t)hmac[1] << 8);
if (code == 0) { // reserve codes 0000 and FFFF
code++;
} else if (code == 0xFFFF) {
code--;
}
return code;
uint16_t code;
SHA256 sha;
sha.resetHMAC(key, sizeof(key));
uint8_t type = packet->getPayloadType();
sha.update(&type, 1);
sha.update(packet->payload, packet->payload_len);
sha.finalizeHMAC(key, sizeof(key), &code, 2);
if (code == 0) { // reserve codes 0000 and FFFF
code++;
} else if (code == 0xFFFF) {
code--;
}
return code;
}
bool TransportKey::isNull() const {
for (size_t i = 0; i < sizeof(key); i++) {
if (key[i]) return false;
}
return true; // key is all zeroes
for (size_t i = 0; i < sizeof(key); i++) { // ZEPHCORE: -Wsign-compare
if (key[i]) return false;
}
return true; // key is all zeroes
}
void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) {
if (num_cache < MAX_TKS_ENTRIES) {
cache_ids[num_cache] = id;
cache_keys[num_cache] = key;
num_cache++;
} else {
// TODO: evict oldest cache entry
}
if (num_cache < MAX_TKS_ENTRIES) {
cache_ids[num_cache] = id;
cache_keys[num_cache] = key;
num_cache++;
} else {
// TODO: evict oldest cache entry
}
}
void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) {
// first, check cache
for (int i = 0; i < num_cache; i++) {
if (cache_ids[i] == id) { // cache hit!
dest = cache_keys[i];
return;
}
}
for (int i = 0; i < num_cache; i++) { // first, check cache
if (cache_ids[i] == id) { // cache hit!
dest = cache_keys[i];
return;
}
}
// calc key for publicly-known hashtag region name
SHA256 sha;
sha.update(name, strlen(name));
sha.finalize(&dest.key, sizeof(dest.key));
// calc key for publicly-known hashtag region name (SHA256 hash, first 16 bytes)
uint8_t hash[32];
size_t out_len;
psa_hash_compute(PSA_ALG_SHA_256, (const uint8_t*)name, strlen(name),
hash, sizeof(hash), &out_len);
memcpy(dest.key, hash, sizeof(dest.key));
putCache(id, dest);
putCache(id, dest);
}
int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) {
int n = 0;
// first, check cache
for (int i = 0; i < num_cache && n < max_num; i++) {
if (cache_ids[i] == id) {
keys[n++] = cache_keys[i];
}
}
if (n > 0) return n; // cache hit!
int n = 0;
for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache
if (cache_ids[i] == id) {
keys[n++] = cache_keys[i];
}
}
if (n > 0) return n; // cache hit!
// TODO: retrieve from hardware keystore
// TODO: retrieve from difficult-to-copy keystore
// store in cache (if room)
for (int i = 0; i < n; i++) {
putCache(id, keys[i]);
}
return n;
// store in cache (if room)
for (int i = 0; i < n; i++) {
putCache(id, keys[i]);
}
return n;
}
bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) {
invalidateCache();
// TODO: update hardware keystore
return false; // failed
invalidateCache();
// TODO: update hardware keystore
return false; // failed
}
bool TransportKeyStore::removeKeys(uint16_t id) {
invalidateCache();
// TODO: remove from hardware keystore
return false; // failed
invalidateCache();
// TODO: remove from hardware keystore
return false; // failed
}
bool TransportKeyStore::clear() {
invalidateCache();
// TODO: clear hardware keystore
return false; // failed
invalidateCache();
// TODO: clear hardware keystore
return false; // failed
}