From 4e97a7ed99d1900be3ce5b3dbd645856668238ec Mon Sep 17 00:00:00 2001 From: liu weikai Date: Sun, 14 Jun 2026 03:16:14 +0800 Subject: [PATCH] refactor(protocol): split mesh capability surface --- docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md | 88 +++++++++---------- .../PROTOCOL_ADAPTER_PARITY_SPEC.md | 6 +- .../include/chat/ports/i_mesh_adapter.h | 9 ++ .../chat/infra/meshcore/meshcore_adapter.cpp | 7 ++ .../src/chat/infra/meshtastic/mt_adapter.cpp | 9 ++ .../esp/radio/meshtastic_radio_adapter.cpp | 3 + .../common/src/app/linux_app_services.cpp | 9 ++ .../linux/common/src/app/linux_demo_world.cpp | 9 ++ .../src/chat/linux_raw_lora_mesh_adapter.cpp | 9 ++ .../infra/meshcore/meshcore_radio_adapter.cpp | 7 ++ .../meshtastic/meshtastic_radio_adapter.cpp | 9 ++ 11 files changed, 118 insertions(+), 47 deletions(-) diff --git a/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md b/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md index 98b60a4b..add47605 100644 --- a/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md +++ b/docs/audits/PROTOCOL_ADAPTER_DRIFT_AUDIT.md @@ -19,12 +19,12 @@ Primary root cause: | Meshtastic broadcast `want_response` | Shared policy in working tree | High | ESP32 and nRF now share the app-data destination/ACK/response decision; broadcast air ACK is suppressed while request response intent is preserved. | | Meshtastic request/reply core | Partially shared | Medium | NodeInfo/Position reply gating, TraceRoute reply gating, TraceRoute payload mutation, and TraceRoute/Position action lifecycle tracking are shared; actual reply send still lives in adapters/UI. | | Meshtastic duplicated policy ownership | Mostly shared | Medium | App-data send intent, NodeInfo reannounce gate, NodeInfo/Position reply gates, TraceRoute reply gate, TraceRoute payload mutation, TraceRoute/Position result lifecycle, and PKI/NO_CHANNEL resync decisions now live in shared runtime/policy. Packet construction and radio IO still live in adapters. | -| MeshCore NodeInfo query/reply | Confirmed drift | High | ESP32 implements MeshCore NodeInfo control frames; nRF `requestNodeInfo()` ignores dest/want_response and sends advert only. | -| MeshCore trace | Confirmed drift | Medium | ESP32 parses/forwards MeshCore `PAYLOAD_TYPE_TRACE`; nRF has no equivalent implementation. UI must not expose MC trace. | -| MeshCore app-data ACK/capability | Confirmed drift | Medium | ESP32 claims and tracks app-data ACK; nRF can set a direct app flag but does not declare or track ACK capability. | +| MeshCore NodeInfo query/reply | Shared runtime/effects | Medium | ESP32 and nRF now route `requestNodeInfo()` through `MeshCoreRuntime` and shared control payload codecs; platform adapters still own packet IO/projection. | +| MeshCore trace | Shared lifecycle, platform-limited routing | Medium | ESP32 and nRF use native `PAYLOAD_TYPE_TRACE` and shared completion/timeout policy; nRF still uses a minimal one-hop hash route. | +| MeshCore app-data ACK/capability | Shared lifecycle | Medium | ESP32 and nRF now declare ACK tracking only when runtime pending/completion handling is wired. ACK frame scheduling remains adapter IO. | | MeshCore direct routing/identity | Confirmed platform gap | High | ESP32 has richer peer routes, identity, secrets, and direct path behavior; nRF path is simplified. | | MeshCore duplicated/incomplete ownership | Confirmed architecture debt | Critical | MeshCore protocol truth is split between shared helpers, ESP32 adapter, and nRF simplified adapter. | -| Capability granularity | Confirmed design gap | High | `MeshCapabilities` is too coarse to protect UI/actions from protocol-specific drift. | +| Capability granularity | Fine-grained flags added | Medium | Coarse legacy fields remain for compatibility; new flags describe NodeInfo, Position, TraceRoute, app response, and ACK tracking separately. | ## Architecture Finding @@ -211,10 +211,10 @@ Residual risk: Evidence: -- ESP32 `MeshCoreAdapter::sendNodeInfoFrame(...)` builds NodeInfo query/info control frames. -- ESP32 `requestNodeInfo(dest, want_response)` distinguishes broadcast query, unicast query, and info send. -- nRF `MeshCoreRadioAdapter::requestNodeInfo(...)` ignores `dest` and `want_response`, then sends advert only. -- nRF `MeshCapabilities` does not claim `supports_node_info`. +- ESP32 and nRF both expose `requestNodeInfo(dest, want_response)` through + `RequestNodeInfoIntent -> MeshCoreRuntime -> SendNodeInfoEffect`. +- Shared MeshCore codecs build and parse NodeInfo query/info control frames. +- ESP32 and nRF both declare the fine-grained NodeInfo capability fields they now execute. Expected behavior: @@ -223,14 +223,14 @@ Expected behavior: Current state: -- Confirmed drift, partly masked by coarse `IMeshAdapter::requestNodeInfo()`. -- ESP32 contains the richer protocol behavior; nRF does not call a shared MeshCore NodeInfo core. +- NodeInfo query/info decision mapping is shared by `MeshCoreRuntime`. +- NodeInfo control payload layout is shared by `meshcore_payload_helpers`. +- ESP32 and nRF differ only in how the resulting `SendNodeInfoEffect` is transmitted and projected. -Next action: +Residual risk: -- Either implement MeshCore NodeInfo control frames on nRF, or make unsupported behavior explicit and - remove any UI path that assumes it works. -- Prefer extracting MeshCore NodeInfo control-frame build/parse/reply policy into shared core first. +- nRF currently has limited projection for received `PublishNodeInfoEffect`; the shared runtime can emit the + effect, but platform-specific contact persistence remains thinner than ESP32. ### MC-002 Trace @@ -238,7 +238,8 @@ Evidence: - ESP32 MeshCore adapter parses and forwards direct `PAYLOAD_TYPE_TRACE`. - Official MeshCore source uses `Mesh::createTrace()` and `onTraceRecv()` for this behavior. -- nRF MeshCore adapter has no trace implementation. +- nRF MeshCore adapter uses the shared trace runtime and sends a minimal one-hop native trace when no full route + table exists. Expected behavior: @@ -247,17 +248,18 @@ Expected behavior: Current state: -- Mono node action spec now hides MC trace. -- nRF remains unsupported. +- Trace base payload build/decode and lifecycle policy are shared. +- ESP32 still owns richer route scheduling and BLE `TraceData` projection. +- nRF declares `supports_trace_route_request` for its minimal native MeshCore trace path, but not richer route + projection. ### MC-003 App-data ACK Evidence: -- ESP32 MeshCore capabilities include `supports_appdata_ack=true`. -- ESP32 direct app-data tracks ACK state. -- nRF MeshCore direct app payload includes a want-ack flag but capabilities do not claim ACK and there is - no equivalent tracking/result path in the simplified adapter. +- ESP32 and nRF direct app-data send paths register pending ACK signatures in `MeshCoreRuntime`. +- ESP32 and nRF incoming ACK frames call shared `handleAppAck(...)`. +- Both adapters declare `supports_protocol_ack_tracking` when this runtime path is wired. Expected behavior: @@ -265,11 +267,12 @@ Expected behavior: Current state: -- Confirmed drift. +- ACK pending/completed/timeout lifecycle is shared. -Next action: +Residual risk: -- Decide if nRF MeshCore supports ACK tracking. If not, ensure callers do not treat `want_ack` success as delivery. +- ACK frame response scheduling for received want-ack app-data is still platform IO; ESP32 has a fuller peer ACK + path than nRF. ### MC-004 Direct Routing, Identity, And Secrets @@ -285,38 +288,35 @@ Expected behavior: Current state: -- Confirmed platform gap, not fully documented in capabilities. +- Confirmed platform gap, now partially documented in fine-grained capabilities. Next action: -- Split MeshCore capabilities into discovery, native NodeInfo, ACK tracking, direct route, identity/key, - and trace support. - Move route/identity policy toward shared MeshCore runtime core before expanding nRF behavior. ## Capability Drift -The current `MeshCapabilities` fields are too coarse: +Current state: -- `supports_unicast_appdata` does not say whether app-data ACK, app-level response, empty payload, - direct route, or protocol-native request/reply are supported. -- `supports_node_info` does not distinguish passive parsing, active query, direct reply, broadcast announce, - or peer reannounce. -- There is no capability for TraceRoute, Position request/reply, or MeshCore native trace. +- `MeshCapabilities` now keeps the legacy coarse flags and adds fine-grained protocol flags for NodeInfo, + Position, TraceRoute, protocol app responses, and ACK tracking. +- ESP32/nRF Meshtastic and MeshCore adapters declare the fine-grained flags they execute. +- Linux loopback/raw-lora adapters and the legacy ESP radio shim were updated to avoid silently omitting the + new fields. -This capability gap is the structural reason UI actions drift into unsupported adapters. +Residual risk: + +- There are still no separate capability fields for MeshCore direct route tables, identity-key exchange, peer + secret derivation, or rich trace route projection. Those remain documented platform differences. ## Immediate Recommended Fix Order -1. Land the shared Meshtastic app-data send policy and NodeInfo reannounce gate after build review. -2. Add a compact adapter parity test for Meshtastic app-data encoding intent: - NodeInfo request, Position request, TraceRoute request, broadcast request where valid. -3. Extract shared Meshtastic reply payload construction/availability, TraceRoute, and PKI resync policy, - replacing platform copies. -4. Split MeshCore NodeInfo capability and stop using generic `requestNodeInfo()` as if all adapters support it. -5. Add MeshCore capability fields for ACK tracking and trace support. -6. Start MeshCore shared runtime extraction from NodeInfo control frames, because the ESP32 behavior already exists - and nRF lacks it. -7. Audit PKI unknown/resync paths after the above, because they depend on NodeInfo request semantics. +1. Add parity tests that assert ESP32 and nRF adapters advertise the fine-grained capabilities they actually + execute. +2. Extract shared Meshtastic NodeInfo/Position packet construction and availability policy. +3. Move MeshCore direct route / identity-key policy toward shared runtime before expanding nRF behavior. +4. Add capability fields for MeshCore direct-route tables, identity/key exchange, and rich trace projection if UI + needs to expose those actions directly. ## Guardrail diff --git a/docs/specification/PROTOCOL_ADAPTER_PARITY_SPEC.md b/docs/specification/PROTOCOL_ADAPTER_PARITY_SPEC.md index d4e5c37a..68ba30ad 100644 --- a/docs/specification/PROTOCOL_ADAPTER_PARITY_SPEC.md +++ b/docs/specification/PROTOCOL_ADAPTER_PARITY_SPEC.md @@ -170,8 +170,8 @@ Implementation ownership: ## Capability Surface Requirements -The current `MeshCapabilities` surface is too coarse for several protocol actions. Future parity work should -split at least these fields out of generic app-data support: +`MeshCapabilities` keeps the coarse legacy flags for existing UI code, but protocol-sensitive callers should +prefer these fine-grained flags over generic app-data support: - `supports_node_info_query`; - `supports_node_info_reply`; @@ -183,7 +183,7 @@ split at least these fields out of generic app-data support: - `supports_protocol_app_response`; - `supports_protocol_ack_tracking`. -Until those fields exist, UI must be conservative and protocol-specific specs, such as +When a fine-grained flag is false, UI must be conservative and protocol-specific specs, such as `NODE_ACTION_PROTOCOL_SPEC.md`, remain authoritative. ## Extraction Requirement diff --git a/modules/core_chat/include/chat/ports/i_mesh_adapter.h b/modules/core_chat/include/chat/ports/i_mesh_adapter.h index 372bfe69..763d465c 100644 --- a/modules/core_chat/include/chat/ports/i_mesh_adapter.h +++ b/modules/core_chat/include/chat/ports/i_mesh_adapter.h @@ -25,6 +25,15 @@ struct MeshCapabilities bool supports_node_info = false; bool supports_pki = false; bool supports_discovery_actions = false; + bool supports_node_info_query = false; + bool supports_node_info_reply = false; + bool supports_node_info_reannounce = false; + bool supports_position_request = false; + bool supports_position_reply = false; + bool supports_trace_route_request = false; + bool supports_trace_route_reply = false; + bool supports_protocol_app_response = false; + bool supports_protocol_ack_tracking = false; }; /** diff --git a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp index 03652c6b..cdbac53c 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp @@ -315,6 +315,13 @@ MeshCapabilities MeshCoreAdapter::getCapabilities() const caps.supports_node_info = true; caps.supports_pki = true; caps.supports_discovery_actions = true; + caps.supports_node_info_query = true; + caps.supports_node_info_reply = true; + caps.supports_node_info_reannounce = true; + caps.supports_trace_route_request = true; + caps.supports_trace_route_reply = true; + caps.supports_protocol_app_response = true; + caps.supports_protocol_ack_tracking = true; return caps; } diff --git a/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp index e3c45653..e1f6482b 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp @@ -303,6 +303,15 @@ MeshCapabilities MtAdapter::getCapabilities() const caps.provides_appdata_sender = true; caps.supports_node_info = true; caps.supports_pki = true; + caps.supports_node_info_query = true; + caps.supports_node_info_reply = true; + caps.supports_node_info_reannounce = true; + caps.supports_position_request = true; + caps.supports_position_reply = true; + caps.supports_trace_route_request = true; + caps.supports_trace_route_reply = true; + caps.supports_protocol_app_response = true; + caps.supports_protocol_ack_tracking = true; return caps; } diff --git a/platform/esp/radio/meshtastic_radio_adapter.cpp b/platform/esp/radio/meshtastic_radio_adapter.cpp index e5a3661f..2f0f76cf 100644 --- a/platform/esp/radio/meshtastic_radio_adapter.cpp +++ b/platform/esp/radio/meshtastic_radio_adapter.cpp @@ -106,6 +106,9 @@ chat::MeshCapabilities MeshtasticRadioAdapter::getCapabilities() const caps.supports_unicast_text = true; caps.supports_unicast_appdata = true; caps.supports_node_info = true; + caps.supports_node_info_query = true; + caps.supports_node_info_reply = true; + caps.supports_node_info_reannounce = true; return caps; } diff --git a/platform/linux/common/src/app/linux_app_services.cpp b/platform/linux/common/src/app/linux_app_services.cpp index 9859d0eb..79a3b675 100644 --- a/platform/linux/common/src/app/linux_app_services.cpp +++ b/platform/linux/common/src/app/linux_app_services.cpp @@ -389,6 +389,15 @@ class LinuxLoopbackMeshAdapter final : public ::chat::IMeshAdapter .supports_node_info = true, .supports_pki = true, .supports_discovery_actions = true, + .supports_node_info_query = true, + .supports_node_info_reply = true, + .supports_node_info_reannounce = true, + .supports_position_request = true, + .supports_position_reply = true, + .supports_trace_route_request = true, + .supports_trace_route_reply = true, + .supports_protocol_app_response = true, + .supports_protocol_ack_tracking = true, }; } diff --git a/platform/linux/common/src/app/linux_demo_world.cpp b/platform/linux/common/src/app/linux_demo_world.cpp index 4e13771b..0d259525 100644 --- a/platform/linux/common/src/app/linux_demo_world.cpp +++ b/platform/linux/common/src/app/linux_demo_world.cpp @@ -67,6 +67,15 @@ class LoopbackMeshAdapter final : public ::chat::IMeshAdapter .supports_node_info = true, .supports_pki = true, .supports_discovery_actions = true, + .supports_node_info_query = true, + .supports_node_info_reply = true, + .supports_node_info_reannounce = true, + .supports_position_request = true, + .supports_position_reply = true, + .supports_trace_route_request = true, + .supports_trace_route_reply = true, + .supports_protocol_app_response = true, + .supports_protocol_ack_tracking = true, }; } diff --git a/platform/linux/common/src/chat/linux_raw_lora_mesh_adapter.cpp b/platform/linux/common/src/chat/linux_raw_lora_mesh_adapter.cpp index 48337833..8a64bc7a 100644 --- a/platform/linux/common/src/chat/linux_raw_lora_mesh_adapter.cpp +++ b/platform/linux/common/src/chat/linux_raw_lora_mesh_adapter.cpp @@ -1095,6 +1095,15 @@ bool LinuxRawLoraMeshAdapter::takePendingSendResult(::chat::MessageId& out_msg_i .supports_pki = pki_ready_, .supports_discovery_actions = active_protocol_ == ::chat::MeshProtocol::Meshtastic, + .supports_node_info_query = true, + .supports_node_info_reply = true, + .supports_node_info_reannounce = true, + .supports_position_request = true, + .supports_position_reply = true, + .supports_trace_route_request = true, + .supports_trace_route_reply = true, + .supports_protocol_app_response = true, + .supports_protocol_ack_tracking = false, }; } diff --git a/platform/nrf52/arduino_common/src/chat/infra/meshcore/meshcore_radio_adapter.cpp b/platform/nrf52/arduino_common/src/chat/infra/meshcore/meshcore_radio_adapter.cpp index 754a1a68..2211b02d 100644 --- a/platform/nrf52/arduino_common/src/chat/infra/meshcore/meshcore_radio_adapter.cpp +++ b/platform/nrf52/arduino_common/src/chat/infra/meshcore/meshcore_radio_adapter.cpp @@ -87,7 +87,14 @@ MeshCoreRadioAdapter::MeshCoreRadioAdapter(const ::chat::runtime::SelfIdentityPr ::chat::MeshCapabilities caps{}; caps.supports_unicast_appdata = true; caps.supports_broadcast_appdata = true; + caps.supports_appdata_ack = true; + caps.supports_node_info = true; caps.supports_discovery_actions = true; + caps.supports_node_info_query = true; + caps.supports_node_info_reply = true; + caps.supports_node_info_reannounce = true; + caps.supports_trace_route_request = true; + caps.supports_protocol_ack_tracking = true; return caps; } diff --git a/platform/nrf52/arduino_common/src/chat/infra/meshtastic/meshtastic_radio_adapter.cpp b/platform/nrf52/arduino_common/src/chat/infra/meshtastic/meshtastic_radio_adapter.cpp index a3f282d4..cb8573f0 100644 --- a/platform/nrf52/arduino_common/src/chat/infra/meshtastic/meshtastic_radio_adapter.cpp +++ b/platform/nrf52/arduino_common/src/chat/infra/meshtastic/meshtastic_radio_adapter.cpp @@ -486,6 +486,15 @@ MeshtasticRadioAdapter::MeshtasticRadioAdapter(const ::chat::runtime::SelfIdenti caps.provides_appdata_sender = true; caps.supports_node_info = true; caps.supports_pki = true; + caps.supports_node_info_query = true; + caps.supports_node_info_reply = true; + caps.supports_node_info_reannounce = true; + caps.supports_position_request = true; + caps.supports_position_reply = true; + caps.supports_trace_route_request = true; + caps.supports_trace_route_reply = true; + caps.supports_protocol_app_response = true; + caps.supports_protocol_ack_tracking = true; return caps; }