Compared decoder.js against the MeshCore firmware source (Dispatcher.cpp,
Packet.h, Mesh.cpp, AdvertDataHelpers.h) and fixed all mismatches:
1. Field order: transport codes now parsed BEFORE path_length byte,
matching the spec: [header][transport_codes?][path_length][path][payload]
2. ACK payload: was incorrectly decoded as dest(1)+src(1)+ackHash(4).
Firmware shows ACK is just checksum(4) — no dest/src hashes.
3. TRACE payload: was incorrectly decoded as flags(1)+tag(4)+dest(6)+src(1).
Firmware shows tag(4)+authCode(4)+flags(1)+pathData.
4. ADVERT appdata: added missing feature1 (0x20 flag) and feature2
(0x40 flag) parsing — 2-byte fields between location and name.
5. Transport code field naming: renamed nextHop/lastHop to code1/code2
to match spec terminology (transport_code_1/transport_code_2).
6. Fixed incorrect field size labels in packets.js hex breakdown:
dest/src are 1 byte, MAC is 2 bytes (not 6B/6B/4B).
7. Fixed ANON_REQ/PATH comment typos (dest was listed as 6 bytes,
MAC as 4 bytes — both wrong, code was already correct).
All 329 tests pass (66 decoder + 263 spec/golden).
Sensor nodes embed telemetry (battery_mv, temperature_c) in their advert
appdata after the null-terminated name. This commit adds decoding and
storage for both the Go ingestor and Node.js backend.
Changes:
- decoder.go/decoder.js: Parse telemetry bytes from advert appdata
(battery_mv as uint16 LE millivolts, temperature_c as int16 LE /100)
- db.go/db.js: Add battery_mv INTEGER and temperature_c REAL columns
to nodes and inactive_nodes tables, with migration for existing DBs
- main.go/server.js: Update node telemetry on advert processing
- server db.go: Include battery_mv/temperature_c in node API responses
- Tests: Decoder telemetry tests (positive, negative temp, no telemetry),
DB migration test, node telemetry update test, server API shape tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Channels with garbage-decrypted names (pre-#197 data still in DB) are now
filtered at the API level using the same non-printable character heuristic
from #197. Applied in both Node.js server.js and Go server (store.go, db.go).
No data is deleted — only filtered from API responses.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After decryption produces text, validate it's printable UTF-8.
If it contains more than 2 non-printable characters (excluding
newline/tab), mark as decryption_failed with text: null.
Applied to both Node (decoder.js) and Go (cmd/ingestor/decoder.go)
decoders. Added tests for garbage and valid text in both.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
#184: Strip non-printable chars (<0x20 except tab/newline) from ADVERT
names in Go server decoder, Go ingestor decoder, and Node decoder.js.
#185: Add visual (N) badge next to node names when multiple nodes share
the same display name (case-insensitive). Shows in list, side pane, and
full detail page with 'also known as' links to other keys.
#186: Add packetsLast24h field to /api/stats response.
#187#188: Cache runtime.ReadMemStats() with 5s TTL in Go server.
#189: Temporarily patch HTMLCanvasElement.prototype.getContext during
L.heatLayer().addTo(map) to pass { willReadFrequently: true }, preventing
Chrome console warning about canvas readback performance.
Tests: 10 new tests for buildDupNameMap + dupNameBadge (143 total frontend).
Cache busters bumped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
decodePath() trusted the pathByte hop count without checking available
buffer space. Corrupt packet cbecda1c7d37d4c0 (route_type=3, pathByte
0xAD) claimed 45 hops × 3 bytes = 135 bytes, but only 65 bytes existed
past the header. Node's Buffer.subarray silently returns empty buffers
for out-of-range slices, producing 23 empty-string hops in the output.
Fix: clamp hashCount to floor(available / hashSize). Add a 'truncated'
flag so consumers know the path was incomplete. No empty hops are ever
returned now.
fixes#183
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a CHANNEL_MSG (GRP_TXT) can't be decrypted, the decoder now includes:
- channelHashHex: zero-padded uppercase hex string of the channel hash byte
- decryptionStatus: 'decrypted', 'no_key', or 'decryption_failed'
Frontend changes:
- Packet list preview shows '🔒 Ch 0xXX (no key)' or '(decryption failed)'
- Detail pane hex breakdown shows channel hash with status label
- Detail pane message area shows channel hash info for undecrypted packets
6 new decoder tests (58 total): channelHashHex formatting, decryptionStatus
for no keys, empty keys, bad keys, and short encrypted data.
Fixes#123
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
decodeEncryptedPayload: dest(1)+src(1)+MAC(2) per PAYLOAD_VER_1
decodeAck: dest(1)+src(1)+ack_hash(4)
decodeAnonReq: dest(1)+pubkey(32)+MAC(2)
decodePath: dest(1)+src(1)+MAC(2)+data
Source: firmware/src/Mesh.cpp lines 129-130, MeshCore.h CIPHER_MAC_SIZE=2
Golden fixture tests need updating to match correct output.
ADV_TYPE_ROOM=3 (0b0011) was misread as chat+repeater because decoder
treated lower nibble as individual bits. Now correctly: type & 0x0F as
enum (0=none, 1=chat, 2=repeater, 3=room, 4=sensor).
Includes startup backfill: scans all adverts and fixes any node roles
in the DB that were incorrectly set to 'repeater' when they should be
'room'. Logs count of fixed nodes on startup.
Timestamp is decoded from the ADVERT but never persisted to the nodes
table. The validation was rejecting valid nodes with slightly-off clocks
(28h future) and nodes broadcasting timestamp=4. No reason to gate on it.