From 11b692abfaad7709c005f87982e5a489946ecf12 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 7 Aug 2026 16:24:53 -0700 Subject: [PATCH] Harden firmware recovery and retries Improve companion BLE delivery and idempotent remote CLI retries, make FPF7 persistence atomic with FPF6 migration, verify staged OTA data on resume, and clarify filter policy wildcard and BlackHole semantics. --- docs/_javascript/filter_tool.js | 3 +- docs/cli_commands.md | 23 +- docs/filter_tool.md | 4 +- docs/flood_filtering.md | 21 +- docs/halo_keymind_settings.md | 2 +- examples/companion_radio/MyMesh.cpp | 4 +- examples/simple_repeater/MyMesh.cpp | 315 +++++++++++++----- examples/simple_repeater/MyMesh.h | 8 +- .../simple_room_server/FloodRuleEngine.cpp | 180 ++++++++-- examples/simple_room_server/FloodRuleEngine.h | 6 +- examples/simple_room_server/MyMesh.cpp | 47 ++- examples/simple_room_server/MyMesh.h | 3 + scripts/test_filter_tool.js | 12 + src/helpers/BaseChatMesh.cpp | 21 +- src/helpers/BaseChatMesh.h | 5 +- src/helpers/BleTxStallWatchdog.h | 109 ++++++ src/helpers/CompanionFrameQueue.h | 124 ++++--- src/helpers/DeferredCliCommand.h | 20 +- src/helpers/FloodFilterPolicy.h | 9 + src/helpers/RemoteCliReplyCache.h | 106 ++++-- src/helpers/RemoteCliRequest.h | 51 +++ src/helpers/esp32/SerialBLEInterface.cpp | 125 ++++++- src/helpers/esp32/SerialBLEInterface.h | 16 +- src/helpers/nrf52/SerialBLEInterface.cpp | 119 ++++++- src/helpers/nrf52/SerialBLEInterface.h | 6 + src/helpers/ota/MerkleTree.cpp | 75 +++-- src/helpers/ota/MerkleTree.h | 18 + src/helpers/ota/OtaCli.cpp | 3 + src/helpers/ota/OtaManager.cpp | 140 ++++++-- src/helpers/ota/OtaManager.h | 19 +- test/README.md | 5 +- .../test_ble_tx_stall_watchdog.cpp | 155 +++++++++ .../test_companion_frame_queue.cpp | 45 +++ .../test_deferred_cli_command.cpp | 10 +- .../test_flood_filter_policy.cpp | 16 + test/test_ota/test_ota_core.cpp | 107 ++++++ .../test_remote_cli_reply_cache.cpp | 41 ++- 37 files changed, 1666 insertions(+), 307 deletions(-) create mode 100644 src/helpers/BleTxStallWatchdog.h create mode 100644 src/helpers/RemoteCliRequest.h create mode 100644 test/test_ble_tx_stall_watchdog/test_ble_tx_stall_watchdog.cpp diff --git a/docs/_javascript/filter_tool.js b/docs/_javascript/filter_tool.js index 1d905d8d..862a29c6 100644 --- a/docs/_javascript/filter_tool.js +++ b/docs/_javascript/filter_tool.js @@ -141,6 +141,7 @@ if (optional) return ""; throw new FilterToolError("Channel is required."); } + if (channel === "*") return ""; if (channel.toLowerCase() === "public") return "public"; if (channel[0] === "#") { if (channel.length < 2 || channel.length > 31 || /\s/.test(channel)) { @@ -149,7 +150,7 @@ return channel; } if (/^(?:[0-9a-fA-F]{32}|[0-9a-fA-F]{64})$/.test(channel)) return channel.toUpperCase(); - throw new FilterToolError("Channel must be public, #channel, or a 128/256-bit hexadecimal key."); + throw new FilterToolError("Channel must be *, public, #channel, or a 128/256-bit hexadecimal key."); } function normalizeScopeName(value) { diff --git a/docs/cli_commands.md b/docs/cli_commands.md index a9ff9206..c906367a 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1665,9 +1665,11 @@ compile this table. - `all`: Match every received hop count (`0-63`). - `0+`, `all`, and an omitted hop expression are equivalent. The CLI displays the saved range as `all`. -- `channel=*|public|#name|128-bit-key|256-bit-key`: Optional authenticated - group-channel match. It applies only to `GRP_TXT`/`GRP_DATA`; `type=any` - plus a channel condition therefore matches only those group types. +- `channel=*|public|#name|128-bit-key|256-bit-key`: Optional channel match. + `channel=*` means no channel condition at all, so the row matches everything + selected by `type=` (including all flood payload types with `type=any`). It + does not authenticate a packet. `public`, `#name`, and raw keys authenticate + one channel and therefore narrow the row to `GRP_TXT`/`GRP_DATA`. - `prefix=`: Optional ordered source-path prefix of one to three pbyte IDs. IDs must all be 2, 4, or 6 hex characters, matching a packet's 1-, 2-, or 3-byte pbyte width. `path=` is an alias. @@ -1678,7 +1680,12 @@ compile this table. - `drop`: Explicit drop action. The `flood.rule` form requires an explicit action. For compatibility, a legacy `flood.filter` row with no rewrite, rate, or stop action is treated as drop. -- `region=`: Rewrite action using an existing locally allowed region. +- `scope=`: Direct public-name scope rewrite. It derives a transport key + from the name and does not require a configured region. For example, + `scope=BlackHole86` is a regionless sink scope; `region=BlackHole86` would + instead require a real configured, flood-allowed region with that name. +- `region=`: Rewrite using an existing locally allowed region and one of + that region's transport keys. - `rate=N/min`: Per-node, per-row fixed one-minute forwarding limit. It can be the only action or accompany `scope=`/`region=`. Counters are charged only for packets that pass all forwarding gates. @@ -1687,12 +1694,12 @@ compile this table. - `stop` or `action=stop`: Apply this matching row, then stop lower-order FPF7 rows from processing. It can stand alone or accompany drop, rewrite, or rate. A stop-only row acts as an exception to lower-priority FPF7 rules. + If the same row uses `region=` and that configured region is missing, denied, + wildcard, or has no usable transport key, both the rewrite and its `stop` + are inert so lower-order safety rows still run. A direct `scope=` target does + not depend on region configuration. - `suspend=tempradio`: Optional. Skip this row only while the temporary radio is actually active. -- `scope=`: Optional scope-setting action. The name is normalized with a - leading `#` and its 128-bit transport key is derived directly from that - hashtag. It does not need to exist in the region list. Public names up to 30 - characters are accepted; private `$` scopes are not. - `require=region`: Legacy alias for `in=allowed`. Apply the row only if the original incoming packet already passes this repeater's region gate. An incoming transport scope must resolve to a locally allowed diff --git a/docs/filter_tool.md b/docs/filter_tool.md index dc4b5f3e..6f5ef46c 100644 --- a/docs/filter_tool.md +++ b/docs/filter_tool.md @@ -64,7 +64,7 @@ in the simulator below. The examples draw from hops=Received hop count: all, 3+, 2-6, or 3 - channel=Group channel + channel=* means no channel condition; a name or key authenticates one group channel rx.scope=Original incoming transport scope path=Path prefix, blacklist, bucket, or loop match tempradio=Temporary-radio state @@ -163,7 +163,7 @@ in the simulator below. The examples draw from