feat(meshcore): show the scope/region a received message was sent with (#3742 Phase 2)#3762
Conversation
#3742 Phase 2) Phase 2 of #3742 (reporter m0urs): surface the scope/region a received MeshCore message was sent under, so users can spot unscoped senders and reply in-scope. A scope isn't carried as plaintext — a TRANSPORT-route packet carries a 16-bit transport_code_1 that is an HMAC keyed by the scope's transport key over the packet's own payload. So a code can't be reversed to a name; instead we recompute the code for each scope name we know about and match (exactly what the firmware authors' meshcore-ha integration does): key = SHA-256("#" + name)[:16] code = LE16(HMAC-SHA256(key, [payloadType, ...payload])[:2]) (0->1 / 0xFFFF->0xFFFE) - New pure resolver `meshcoreScopeResolve.ts` on top of the existing `decodeMeshCorePacket` — validated against firmware ground-truth vectors. - Native backend carries the OTA `raw_hex` through the existing inbound correlation buffer (pendingTxtMsgPath) onto contact_message/channel_message. - Manager resolves scope against a cached known-scope set (per-channel scopes + default scope, refreshed on connect and on any scope change) and stamps scopeCode/scopeName. Migration 106 adds the two columns (all 3 backends). - UI renders under received messages: "🌐 no scope" (unscoped), "🔒 muenchen" (resolved), or "🔒 #a3f2" (scoped but not a known scope). scopeCode semantics: 0 = sent unscoped, >0 = the transport code, NULL = no scope info (room posts, serial backend, uncorrelated). Tests: resolver crypto (10), manager wiring (4), repo round-trip incl. the 0-sentinel. Full suite 7574 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBLhGGNh35oMwTL53va1Y5
|
Claude finished @Yeraze's task —— View job PR Review: feat(meshcore): show the scope/region a received message was sent with
Overall AssessmentThis is a well-engineered feature. The crypto derivation mirrors the authoritative firmware algorithm precisely, the plumbing is clean, and the test coverage is thorough. A few items worth noting: Crypto /
|
Comment-only follow-up to the PR #3762 review (no behavior change): - cite the firmware/meshcore.js route-type enum for the TRANSPORT_FLOOD/DIRECT scoped-route check, and note both transport variants are treated as scoped. - note the candidate scan is one HMAC per known scope (realistically 1–5). - note the raw_hex correlation shares Phase 1's adjacency-buffer ordering limitation (scope resolves null if LogRxData arrives after its recv event). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBLhGGNh35oMwTL53va1Y5
|
Thanks — verdict noted (ready to merge). Triage: Manager #2 (refresh on default-scope change) — already handled. The POST Manager #1 ( #3 (resolve race) — agreed, no issue (snapshot Set). Doc-comment suggestions — applied in 5af29db: cited the firmware route-type enum for the TRANSPORT_FLOOD/DIRECT check, noted the candidate scan scale (1–5), and noted the raw_hex correlation shares Phase 1's adjacency-buffer ordering limitation. No behavior change. |
|
Claude finished @Yeraze's task —— View job PR Review: feat(meshcore): show the scope/region a received message was sent with
Overall AssessmentThis is a well-structured implementation. The crypto derivation correctly mirrors the authoritative firmware algorithm, the plumbing follows the Phase 1 pattern cleanly, and the test coverage is thorough. The previous review's open items were all resolved in the follow-up commit. No blockers — the PR is ready to merge. Crypto /
|
Phase 2 of #3742 (builds on the merged Phase 1 hop/route display, #3750). Per the reporter (m0urs): surface the scope/region a received MeshCore message was sent under — so users can spot senders using no scope (a common newcomer issue in DE region-gated meshes) and reply in the same scope to improve delivery.
How it works
A scope is not carried as plaintext. A TRANSPORT-route packet carries a 16-bit
transport_code_1that is a content-keyed HMAC, not a static hash of the name:So a code can't be reversed to a name. Instead we recompute the code for each scope name the source knows about (per-channel scopes + the default scope) over this packet's payload and match — the exact approach in the firmware authors' own Home-Assistant integration (
meshcore-ha'smatch_flood_scope). Validated against firmware ground-truth vectors.Changes
meshcoreScopeResolve.ts(new, pure) —scopeTransportKey/computeTransportCode/resolveMessageScope, on top of the existingdecodeMeshCorePacket. 10 unit tests against firmware vectors incl. the reserved-value clamp.raw_hexthrough the existing inbound correlation buffer (pendingTxtMsgPath, the same path{ROUTE}/{SNR}already ride) ontocontact_message/channel_message. Pure field plumbing, no logic.knownScopesset (refreshed on connect + any channel/default-scope change), stampsscopeCode/scopeName, persists + maps back on boot.scopeCode(int) +scopeName(text), nullable.🌐 no scope— sent unscoped (scopeCode = 0)🔒 muenchen— resolved to a known scope🔒 #a3f2— scoped, but not one of the user's known scopes (raw code hex)scopeCodesemantics: 0 = sent unscoped, >0 = the transport code, NULL = no scope info (room posts, serial backend, uncorrelated packet).Tests
Resolver crypto (10), manager wiring raw_hex→knownScopes→message (4), repo round-trip incl. the
0unscoped sentinel and NULL-omitted rows, migration registry → 106. Existing MeshCore suite (116) still green. Full suite 7574 passed, 0 failed; tsc + build clean.🤖 Generated with Claude Code