mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-19 08:25:14 +00:00
e6c30e1a7e
Addresses the four P0+P1 firmware reconciliation gaps from the umbrella audit (issue #1279). RED commit: `0a4c084e` (asserts on stub returns; all 13 assertions fail). GREEN commit: `13867681`. ## What's in this PR ### P0 — silently dropped data - **#1 GRP_DATA (0x06) decoder.** Outer envelope is the same shape as GRP_TXT (`channel_hash(1)+MAC(2)+ciphertext`) per `firmware/src/helpers/BaseChatMesh.cpp:476,500`. Factored `decryptChannelBlock(...)` helper used by both 5 and 6. When a channel key matches, the inner is parsed per `firmware/src/helpers/BaseChatMesh.cpp:382-385` as `data_type(uint16 LE) + data_len(1) + blob(data_len)`. Surfaces `{channelHash, MAC, dataType, dataLen, decryptedBlob}` on decrypt or `{channelHash, MAC, encryptedData}` otherwise. Server-side decoder surfaces envelope only (no key store). - **#2 MULTIPART (0x0A) decoder.** Per `firmware/src/Mesh.cpp:289`, byte0 = `(remaining<<4) | inner_type`. When `inner_type == PAYLOAD_TYPE_ACK (0x03)`, next 4 bytes are the LE ack_crc per `firmware/src/Mesh.cpp:292-307`. Surfaces `{remaining, innerType, innerTypeName, innerAckCrc | innerPayload}`. ### P1 — mis-classified / opaque - **#3 `advertRole()` raw-type fix.** Per `firmware/src/helpers/AdvertDataHelpers.h:7-12`, ADV_TYPE_NONE = 0 and 5-15 are FUTURE. The previous boolean fallback collapsed both into `"companion"`, silently relabelling unknown/reserved types. New behaviour: type 0 → `none`, 1 → `companion`, 2-4 → `repeater`/`room`/`sensor`, 5-15 → `type-N`. `ValidateAdvert` accepts the new labels. - **#4 CONTROL (0x0B) byte0 flags + length.** Per `firmware/src/Mesh.cpp:69` + `createControlData` at `Mesh.cpp:609`, byte0 high-bit marks the zero-hop direct subset. Surfaces `{ctrlFlags, ctrlZeroHop, ctrlLength}`. ### Drift fix - `cmd/server/store.go` `payloadTypeNames` now includes `6: GRP_DATA` and `10: MULTIPART` (previously omitted; canonical decoder map already had them). ## Lockstep & TDD Both `cmd/ingestor/decoder.go` and `cmd/server/decoder.go` updated in the same commits — same wire-vector tests live in both packages (`cmd/{ingestor,server}/issue1279_test.go`). Per-item RED→GREEN visible in `git log`. | Item | Tests | RED proof | |---|---|---| | #1 GRP_DATA | ingestor: NoKey + DecryptedInner; server: Envelope | 6 assertions failed pre-impl | | #2 MULTIPART | ingestor + server: Ack + NonAck | 8 assertions failed pre-impl | | #3 advertRole | ingestor + server: 7-row table | 3 assertions failed pre-impl | | #4 CONTROL | ingestor + server: ZeroHop + MultiHop | 6 assertions failed pre-impl | ## What's NOT in this PR The umbrella issue lists P2 items that ship in follow-up PRs: - Live + compare legend entries for the long tail of newly-named types (#1274 + others). - TransportCodes UI surface + filter grammar. - feat1/feat2 capability badges. - `payloadTypeNames` consolidation across server/ingestor (drift-prevention). Leave the umbrella open after this merges. Refs #1279 --------- Co-authored-by: OpenClaw Bot <bot@openclaw.local>
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
package main
|
|
|
|
// Tests for issue #1279 P0+P1 decoder additions (server-side).
|
|
//
|
|
// Wire-vector citations identical to the ingestor counterpart:
|
|
// - GRP_DATA outer: firmware/src/helpers/BaseChatMesh.cpp:500
|
|
// - MULTIPART byte0: firmware/src/Mesh.cpp:289
|
|
// - MULTIPART ACK inner: firmware/src/Mesh.cpp:292-307
|
|
// - CONTROL byte0 flags: firmware/src/Mesh.cpp:69 + Mesh.cpp:609
|
|
// - advertRole label rules: firmware/src/helpers/AdvertDataHelpers.h:7-12
|
|
|
|
import "testing"
|
|
|
|
func TestDecodeGrpDataEnvelopeServer(t *testing.T) {
|
|
// Server-side decoder has no channel keys: envelope only.
|
|
buf := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11}
|
|
p := decodeGrpData(buf)
|
|
if p.Type != "GRP_DATA" {
|
|
t.Fatalf("type=%q want GRP_DATA", p.Type)
|
|
}
|
|
if p.ChannelHash != 0xAA {
|
|
t.Errorf("channelHash=%d want 170", p.ChannelHash)
|
|
}
|
|
if p.ChannelHashHex != "AA" {
|
|
t.Errorf("channelHashHex=%q want AA", p.ChannelHashHex)
|
|
}
|
|
if p.MAC != "bbcc" {
|
|
t.Errorf("mac=%q want bbcc", p.MAC)
|
|
}
|
|
if p.EncryptedData != "ddeeff11" {
|
|
t.Errorf("encryptedData=%q want ddeeff11", p.EncryptedData)
|
|
}
|
|
}
|
|
|
|
func TestDecodeMultipartAckServer(t *testing.T) {
|
|
buf := []byte{0x33, 0xEF, 0xBE, 0xAD, 0xDE}
|
|
p := decodeMultipart(buf)
|
|
if p.Type != "MULTIPART" {
|
|
t.Fatalf("type=%q want MULTIPART", p.Type)
|
|
}
|
|
if p.Remaining == nil || *p.Remaining != 3 {
|
|
t.Errorf("remaining=%v want 3", p.Remaining)
|
|
}
|
|
if p.InnerType == nil || *p.InnerType != 0x03 {
|
|
t.Errorf("innerType=%v want 3", p.InnerType)
|
|
}
|
|
if p.InnerTypeName != "ACK" {
|
|
t.Errorf("innerTypeName=%q want ACK", p.InnerTypeName)
|
|
}
|
|
if p.InnerAckCrc != "deadbeef" {
|
|
t.Errorf("innerAckCrc=%q want deadbeef", p.InnerAckCrc)
|
|
}
|
|
}
|
|
|
|
func TestDecodeMultipartNonAckServer(t *testing.T) {
|
|
buf := []byte{0x22, 0x01, 0x02, 0x03}
|
|
p := decodeMultipart(buf)
|
|
if p.Remaining == nil || *p.Remaining != 2 {
|
|
t.Errorf("remaining=%v want 2", p.Remaining)
|
|
}
|
|
if p.InnerType == nil || *p.InnerType != 0x02 {
|
|
t.Errorf("innerType=%v want 2", p.InnerType)
|
|
}
|
|
if p.InnerTypeName != "TXT_MSG" {
|
|
t.Errorf("innerTypeName=%q want TXT_MSG", p.InnerTypeName)
|
|
}
|
|
if p.InnerPayload != "010203" {
|
|
t.Errorf("innerPayload=%q want 010203", p.InnerPayload)
|
|
}
|
|
}
|
|
|
|
func TestAdvertRoleLabelsRawTypeServer(t *testing.T) {
|
|
cases := []struct {
|
|
typ int
|
|
want string
|
|
}{
|
|
{0, "none"},
|
|
{1, "companion"},
|
|
{2, "repeater"},
|
|
{3, "room"},
|
|
{4, "sensor"},
|
|
{5, "type-5"},
|
|
{15, "type-15"},
|
|
}
|
|
for _, tc := range cases {
|
|
got := advertRole(&AdvertFlags{Type: tc.typ, Repeater: tc.typ == 2, Room: tc.typ == 3, Sensor: tc.typ == 4})
|
|
if got != tc.want {
|
|
t.Errorf("advertRole(type=%d) = %q, want %q", tc.typ, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecodeControlZeroHopServer(t *testing.T) {
|
|
buf := []byte{0x81, 0xAA, 0xBB, 0xCC}
|
|
p := decodeControl(buf)
|
|
if p.Type != "CONTROL" {
|
|
t.Fatalf("type=%q want CONTROL", p.Type)
|
|
}
|
|
if p.CtrlFlags != "81" {
|
|
t.Errorf("ctrlFlags=%q want 81", p.CtrlFlags)
|
|
}
|
|
if p.CtrlZeroHop == nil || !*p.CtrlZeroHop {
|
|
t.Errorf("ctrlZeroHop=%v want true", p.CtrlZeroHop)
|
|
}
|
|
if p.CtrlLength == nil || *p.CtrlLength != 4 {
|
|
t.Errorf("ctrlLength=%v want 4", p.CtrlLength)
|
|
}
|
|
}
|
|
|
|
func TestDecodeControlMultiHopServer(t *testing.T) {
|
|
buf := []byte{0x01, 0x42}
|
|
p := decodeControl(buf)
|
|
if p.CtrlFlags != "01" {
|
|
t.Errorf("ctrlFlags=%q want 01", p.CtrlFlags)
|
|
}
|
|
if p.CtrlZeroHop == nil || *p.CtrlZeroHop {
|
|
t.Errorf("ctrlZeroHop=%v want false", p.CtrlZeroHop)
|
|
}
|
|
if p.CtrlLength == nil || *p.CtrlLength != 2 {
|
|
t.Errorf("ctrlLength=%v want 2", p.CtrlLength)
|
|
}
|
|
}
|