fix: MeshCore spec compliance audit and packet quality warnings

Audited against meshcore-spec and fixed all compliance gaps:

- Fix trace hash size encoding: use 1<<(flags&3) not (flags&3)+1
- Reject reserved path hash_size_code 3 per spec Section 3
- Minimum packet size 3 bytes (was 2)
- Add MAX_PACKET_PAYLOAD (184) and min payload (1) validation
- Tighten payload minimums: ANON_REQ≥51, encrypted≥20, group≥19,
  multipart≥2
- Add node_type 0 ("none") to NODE_TYPES
- Fix node_type fallback display (was "node=type=14", now "node=0x0e")

Add packet quality warnings for likely-corrupt frames:
- decode_meshcore_packet returns (str, list[str]) with quality warnings
- ADVERT: flag bad node_type, bad UTF-8 in name, future/bad timestamps
- Combine content warnings with low-SNR signal at display layer
- Warnings stack: one flag = maybe, multiple = high confidence corrupt

Update test corpus and generators for tightened validation rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-04-01 11:57:32 -06:00
1 parent d551095f3f
commit d01de9d14d
10 files changed
+198 -82

No files matched your search

+12 -6
View File
@@ -94,7 +94,7 @@ def advert_appdata(draw):
name = data[8 if has_loc else 0:].decode("ascii", errors="replace")
app_data = bytes([flags]) + data
node_label = NODE_TYPES.get(node_type, f"type={node_type}")
node_label = NODE_TYPES.get(node_type, f"0x{node_type:02x}")
return app_data, {"flags": flags, "node_type": node_label, "location": loc, "name": name}
@@ -158,7 +158,7 @@ def valid_encrypted_peer_packet(draw):
dst = draw(st.integers(min_value=0, max_value=255))
src = draw(st.integers(min_value=0, max_value=255))
mac = draw(st.binary(min_size=2, max_size=2))
ct_len = draw(st.integers(min_value=0, max_value=64))
ct_len = draw(st.integers(min_value=16, max_value=64).filter(lambda n: n % 16 == 0))
ct = draw(st.binary(min_size=ct_len, max_size=ct_len))
hdr = bytes([mc_header(ptype, route_type)])
@@ -188,7 +188,7 @@ def valid_group_packet(draw):
hs, hops = draw(path_strategy())
ch = draw(st.integers(min_value=0, max_value=255))
mac = draw(st.binary(min_size=2, max_size=2))
ct_len = draw(st.integers(min_value=0, max_value=64))
ct_len = draw(st.integers(min_value=16, max_value=64).filter(lambda n: n % 16 == 0))
ct = draw(st.binary(min_size=ct_len, max_size=ct_len))
hdr = bytes([mc_header(ptype, route_type)])
@@ -220,7 +220,7 @@ def invalid_packet(draw):
pkt, _ = draw(valid_ack_packet())
cut = draw(st.integers(min_value=0, max_value=max(0, len(pkt) - 2)))
packet = pkt[:cut]
if len(packet) < 2:
if len(packet) < 3:
error = "too_short"
else:
error = "bad_framing"
@@ -230,12 +230,18 @@ def invalid_packet(draw):
ptype = draw(st.sampled_from(list(PAYLOAD_TYPES.keys())))
hdr = bytes([mc_header(ptype, route_type)])
# path_len claiming huge path
bad_pl = draw(st.integers(min_value=0xC0, max_value=0xFF))
# Use hash_size_code 3 (reserved, 0xC0-0xFF) or valid codes with huge counts
bad_pl = draw(st.sampled_from([
*range(0xC0, 0x100), # reserved hash_size_code 3
0x3F, # 63 hops × 1 byte = 63 (valid encoding, will overflow data)
0x60, # 32 hops × 2 bytes = 64 (valid encoding, will overflow data)
0x95, # 21 hops × 3 bytes = 63 (valid encoding, will overflow data)
]))
tail = draw(st.binary(min_size=1, max_size=20))
packet = hdr + bytes([bad_pl]) + tail
error = "bad_framing"
else: # random
packet = draw(st.binary(min_size=2, max_size=50))
packet = draw(st.binary(min_size=3, max_size=50))
error = "not_meshcore"
header = packet[0] if packet else 0