Commit Graph

30 Commits

Author SHA1 Message Date
you 37396823ad fix: align Go packet decoder with MeshCore firmware spec
Match the C++ firmware wire format (Packet::writeTo/readFrom):

1. Field order: transport codes are parsed BEFORE path_length byte,
   matching firmware's header → transport_codes → path_len → path → payload

2. ACK payload: just 4-byte CRC checksum, not dest+src+ackHash.
   Firmware createAck() writes only ack_crc (4 bytes).

3. TRACE payload: tag(4) + authCode(4) + flags(1) + pathData,
   matching firmware createTrace() and onRecvPacket() TRACE handler.

4. ADVERT features: parse feat1 (0x20) and feat2 (0x40) optional
   2-byte fields between location and name, matching AdvertDataBuilder
   and AdvertDataParser in the firmware.

5. Transport code naming: code1/code2 instead of nextHop/lastHop,
   matching firmware's transport_codes[0]/transport_codes[1] naming.

Fixes applied to both cmd/ingestor/decoder.go and cmd/server/decoder.go.
Tests updated to match new behavior.
2026-03-29 07:50:51 -07:00
copilot-swe-agent[bot] 7b97c532a1 test: fix env isolation and comment accuracy in channel key tests
Agent-Logs-Url: https://github.com/Kpa-clawbot/meshcore-analyzer/sessions/38b3e96f-861b-4929-8134-b1b9de39a7fc

Co-authored-by: KpaBap <746025+KpaBap@users.noreply.github.com>
2026-03-28 15:27:26 -07:00
Kpa-clawbot e0c2d37041 fix: port hashChannels key derivation to Go ingestor (fixes #218)
Add HashChannels config field and deriveHashtagChannelKey() to the Go
ingestor, matching the Node.js server-helpers.js algorithm:
SHA-256(channelName) -> first 32 hex chars (16 bytes AES-128 key).

Merge priority preserved: rainbow (lowest) -> derived -> explicit (highest).

Tests include cross-language vectors validated against Node.js output
and merge priority / normalization / skip-explicit coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 15:27:26 -07:00
Kpa-clawbot cdcaa476f2 rename: MeshCore Analyzer → CoreScope (Phase 1 — backend + infra)
Rename product branding, binary names, Docker images, container names,
Go modules, proto go_package, CI, manage.sh, and documentation.

Preserved (backward compat):
- meshcore.db database filename
- meshcore-data / meshcore-staging-data directory paths
- MQTT topics (meshcore/#, meshcore/+/+/packets, etc.)
- proto package namespace (meshcore.v1)
- localStorage keys

Changes by category:
- Go modules: github.com/corescope/{server,ingestor}
- Binaries: corescope-server, corescope-ingestor
- Docker images: corescope:latest, corescope-go:latest
- Containers: corescope-prod, corescope-staging, corescope-staging-go
- Supervisord programs: corescope, corescope-server, corescope-ingestor
- Branding: siteName, heroTitle, startup logs, fallback HTML
- Proto go_package: github.com/corescope/proto/v1
- CI: container refs, deploy path
- Docs: 8 markdown files updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 14:08:15 -07:00
KpaBap 8e18351c73 Merge pull request #221 from Kpa-clawbot/feat/telemetry-decode
feat: decode telemetry packets — battery voltage + temperature on nodes
2026-03-28 13:45:00 -07:00
copilot-swe-agent[bot] a827fd3b43 fix: gate telemetry on sensor flag, fix 0°C emission, safe migration with PRAGMA check
Agent-Logs-Url: https://github.com/Kpa-clawbot/meshcore-analyzer/sessions/1c2af64b-0e8a-4dd0-ae80-e296f70437e9

Co-authored-by: KpaBap <746025+KpaBap@users.noreply.github.com>
2026-03-28 20:35:50 +00:00
Kpa-clawbot b326e3f1a6 fix: pprof port conflict crashed Go server — non-fatal bind + separate ports
Server defaults to 6060, ingestor to 6061. Removed shared PPROF_PORT
env var. Bind failure logs warning instead of log.Fatal killing the process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 13:01:41 -07:00
Kpa-clawbot 54cbc648e0 feat: decode telemetry from adverts — battery voltage + temperature on nodes
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>
2026-03-28 12:07:42 -07:00
Kpa-clawbot aba4270ceb fix: undefined err in packets_v view creation (use vErr)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 12:00:04 -07:00
Kpa-clawbot 57b0188158 fix: create packets_v VIEW in Go ingestor schema (#217)
Fresh Go installs failed with 'no such table: packets_v' because the
ingestor created tables but never the VIEW that the Go server queries.

Add DROP VIEW IF EXISTS + CREATE VIEW packets_v to applySchema(), using
the v3 definition (observer_idx → observers.rowid JOIN). The view is
rebuilt on every startup to stay current with any definition changes.

Add tests: verify view exists after OpenStore, and verify it returns
correct observer_id/observer_name via the LEFT JOIN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:28:38 -07:00
Kpa-clawbot f374a4a775 fix: enforce consistent types between Go ingestor writes and server reads
Schema:
- observers.noise_floor: INTEGER → REAL (dBm has decimals)
- battery_mv, uptime_secs remain INTEGER (always whole numbers)

Ingestor write side (cmd/ingestor/db.go):
- UpsertObserver now accepts ObserverMeta with battery_mv (int),
  uptime_secs (int64), noise_floor (float64)
- COALESCE preserves existing values when meta is nil
- Added migration: cast integer noise_floor values to REAL

Ingestor MQTT handler (cmd/ingestor/main.go — already updated):
- extractObserverMeta extracts hardware fields from status messages
- battery_mv/uptime_secs cast via math.Round to int on write

Server read side (cmd/server/db.go):
- Observer.BatteryMv: *float64 → *int (matches INTEGER storage)
- Observer.UptimeSecs: *float64 → *int64 (matches INTEGER storage)
- Observer.NoiseFloor: *float64 (unchanged, matches REAL storage)
- GetObservers/GetObserverByID: use sql.NullInt64 intermediaries
  for battery_mv/uptime_secs, sql.NullFloat64 for noise_floor

Proto (proto/observer.proto — already correct):
- battery_mv: int32, uptime_secs: int64, noise_floor: double

Tests:
- TestUpsertObserverWithMeta: verifies correct SQLite types via typeof()
- TestUpsertObserverMetaPreservesExisting: nil-meta preserves values
- TestExtractObserverMeta: float-to-int rounding, empty message
- TestSchemaNoiseFloorIsReal: PRAGMA table_info validation
- TestObserverTypeConsistency: server reads typed values correctly
- TestObserverTypesInGetObservers: list endpoint type consistency

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:22:14 -07:00
Kpa-clawbot 6d31cb2ad6 feat: add pprof profiling controlled by ENABLE_PPROF env var
Add net/http/pprof support to both Go server (default port 6060) and
ingestor (default port 6061). Profiling is off by default — only
starts the pprof HTTP listener when ENABLE_PPROF=true.

PPROF_PORT env var overrides the default port for each binary.

Enable on staging-go in docker-compose with exposed ports 6060/6061.
Not enabled on prod.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:18:33 -07:00
you 331dc0090e test: add load test with throughput and latency metrics
TestLoadTestThroughput: 1000 messages × 4 writes each = 4000 writes,
20 concurrent goroutines. Reports msgs/sec, p50/p95/p99 latency,
SQLITE_BUSY count, and total errors. Hard-asserts zero BUSY errors.
2026-03-28 16:54:06 +00:00
you cef8156a86 fix: set MaxIdleConns(1) to match MaxOpenConns(1)
Prevents unnecessary connection close/reopen churn from the default
MaxIdleConns(2) when only 1 connection is ever open.
2026-03-28 16:37:56 +00:00
you 9751141ffc feat: add observability metrics and concurrency tests
Observability:
- Add DBStats struct with atomic counters for tx_inserted, tx_dupes,
  obs_inserted, node_upserts, observer_upserts, write_errors
- Log SQLite config on startup (busy_timeout, max_open_conns, journal)
- Periodic stats logging every 5 minutes + final stats on shutdown
- Instrument all write paths with counter increments

Tests:
- TestConcurrentWrites: 20 goroutines × 50 writes (1000 total) with
  interleaved InsertTransmission + UpsertNode + UpsertObserver calls.
  Verifies zero errors and data integrity under concurrent load.
- TestDBStats: verifies counter accuracy for inserts, duplicates,
  upserts, and that LogStats does not panic
2026-03-28 16:36:50 +00:00
you 9c5ffbfb0c fix: resolve SQLite SQLITE_BUSY write contention in ingestor
Three changes to eliminate concurrent write collisions:

1. Add _busy_timeout=5000 to ingestor SQLite DSN (matches server)
   - SQLite will wait up to 5s for the write lock instead of
     immediately returning SQLITE_BUSY

2. Set SetMaxOpenConns(1) on ingestor DB connection pool
   - Serializes all DB access at the Go sql.DB level
   - Prevents multiple goroutines from opening overlapping writes

3. Change SetOrderMatters(false) to SetOrderMatters(true)
   - MQTT handlers now run sequentially per client
   - Eliminates concurrent handler execution that caused
     overlapping multi-statement write flows

Root cause: concurrent MQTT handlers (SetOrderMatters=false) each
performed multiple separate writes (transmission lookup/insert,
observation insert, node upsert, observer upsert) without transactions
or connection limits. SQLite only permits one writer at a time, so
under bursty MQTT traffic the ingestor was competing with itself.
2026-03-28 16:16:07 +00:00
Kpa-clawbot 51fdc432d7 fix: TestUpsertNode expects advert_count=0 (UpsertNode doesn't increment it)
UpsertNode only updates name/role/lat/lon/last_seen. The advert_count
field is modified exclusively by IncrementAdvertCount, which is called
separately in the MQTT handler. The test incorrectly expected count=2
after two UpsertNode calls; the correct value is 0 (the schema default).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 00:48:10 -07:00
Kpa-clawbot 28bb6c5daf fix: InsertTransmission 2-return in db_test.go (13 call sites)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 23:01:54 -07:00
Kpa-clawbot f636fc3f7e fix: InsertTransmission returns 2 values — handle isNew at all call sites
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:58:11 -07:00
Kpa-clawbot 848ddf7fb7 fix: node pruning runs hourly not daily
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:45:48 -07:00
Kpa-clawbot 520adcc6ab feat: move stale nodes to inactive_nodes table, fixes #202
- Create inactive_nodes table with identical schema to nodes
- Add retention.nodeDays config (default 7) in Node.js and Go
- On startup: move nodes not seen in N days to inactive_nodes
- Daily timer (24h setInterval / goroutine ticker) repeats the move
- Log 'Moved X nodes to inactive_nodes (not seen in N days)'
- All existing queries unchanged — they only read nodes table
- Add 14 new tests for moveStaleNodes in test-db.js
- Both Node (db.js/server.js) and Go (ingestor/server) implemented

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:43:53 -07:00
Kpa-clawbot 8e96b29859 fix: advert_count counts unique transmissions, not observations
advert_count was incremented on every upsertNode call, meaning each
observation of the same ADVERT packet inflated the count. Node N6NU
showed 4191 'adverts' but only had 77 unique ADVERT transmissions.

Changes:
- db.js: Remove advert_count increment from upsertNode SQL. Add
  separate incrementAdvertCount() called only for new transmissions.
  insertTransmission() now returns isNew flag.
- server.js: All three ADVERT processing paths (MQTT format 1,
  companion bridge, API) now check isNew before incrementing.
- cmd/ingestor/db.go: Same fix in Go — UpsertNode no longer
  increments, new IncrementAdvertCount method added.
  InsertTransmission returns (bool, error) with isNew flag.
- cmd/ingestor/main.go: Check isNew before calling IncrementAdvertCount.
- One-time startup migration recalculates advert_count from
  transmissions table (payload_type=4 matching node public_key).

Fixes #200

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:31:34 -07:00
Kpa-clawbot a8c74ec411 fix: correct Go test call signatures after decryption refactor
The #197 decryption fix added channelKeys parameter to decodePayload and
DecodePacket, but the test call sites were malformed:

- DecodePacket(hex, nil + stringExpr) → nil concatenated with string (type error)
- decodePayload(type, make([]byte, N, nil)) → nil used as make capacity (type error)

Fixed to:
- DecodePacket(hex + stringExpr, nil) → string concat then nil channelKeys
- decodePayload(type, make([]byte, N), nil) → proper 3-arg call

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:29:14 -07:00
Kpa-clawbot bcf7159538 fix: detect garbage text after channel decryption, fixes #197
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>
2026-03-27 21:48:37 -07:00
Kpa-clawbot 77988ded3e fix: #184-#189 — sanitize names, packetsLast24h, ReadMemStats cache, dup name indicator, heatmap warning
#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>
2026-03-27 20:50:08 -07:00
Kpa-clawbot 91a8c0405f feat(go): implement channel decryption for GRP_TXT packets, fixes #176
Go ingestor never had channel decryption — GRP_TXT packets were stored
with raw encrypted data while Node.js decoded them successfully.

Changes:
- decoder.go: Add decryptChannelMessage() implementing MeshCore channel
  crypto (HMAC-SHA256 MAC verification + AES-128-ECB decryption), matching
  the algorithm in @michaelhart/meshcore-decoder. Update decodeGrpTxt(),
  decodePayload(), and DecodePacket() to accept and pass channel keys.
  Add Payload fields: ChannelHashHex, DecryptionStatus, Channel, Text,
  Sender, SenderTimestamp.
- config.go: Add ChannelKeysPath and ChannelKeys fields to Config struct.
- main.go: Add loadChannelKeys() that loads channel-rainbow.json (same
  file used by Node.js server) from beside the config file, with env var
  and config overrides. Pass loaded keys through the decoder pipeline.
- decoder_test.go: Add 14 channel decryption tests covering valid
  decryption, MAC failure, wrong key, no-sender messages, bracket
  sender exclusion, key iteration, channelHashHex formatting, and
  decryption status states. Cross-validated against Node.js output.
- Update all DecodePacket/decodePayload/decodeGrpTxt/handleMessage call
  sites in test files to pass the new channelKeys parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 17:55:52 -07:00
Kpa-clawbot 59c225593f fix(go): resolve 6 backend issues — #165 #168 #169 #170 #171 #172
#165 — build_time in API: already implemented (BuildTime ldflags in
Dockerfile.go, main.go, StatsResponse, HealthResponse)

#168 — subpaths API slow: cache (subpathCache with TTL) and invalidation
already in place; verified working

#169 — distance API slow: cache (distCache with TTL) and invalidation
already in place; verified working

#170 — audio-lab/buckets: in-memory store path already implemented,
matching Node.js pktStore.packets iteration with type grouping and
size-distributed sampling

#171 — channels stale latest message: add companion bridge handling to
Go ingestor for meshcore/message/channel/<n> and meshcore/message/direct/<id>
MQTT topics. Stores decoded channel messages with type CHAN in decoded_json,
enabling the channels endpoint to find them. Also handles direct messages.

#172 — packets page not live-updating: add missing direction field to WS
broadcast packet map for full parity with txToMap/Node.js fullPacket shape.
WS broadcast shape verified correct (type, data.packet structure, timestamp,
payload_type, observer_id all present).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 16:06:24 -07:00
Kpa-clawbot 616af26981 fix: Go ingestor normalize mqtt:// to tcp:// and mqtts:// to ssl:// for paho
Paho MQTT client uses tcp:// and ssl:// schemes, not mqtt:// and mqtts://.
Also properly configure TLS for mqtts connections with InsecureSkipVerify
when rejectUnauthorized is false.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 07:56:57 -07:00
Kpa-clawbot e89c2bfe1f test: add comprehensive Go test coverage for ingestor (80%) and server (90%)
- ingestor: add config_test.go (LoadConfig, env overrides, legacy MQTT)
- ingestor: add main_test.go (toFloat64, firstNonEmpty, handleMessage, advertRole)
- ingestor: extend decoder_test.go (short buffer errors, edge cases, all payload types)
- ingestor: extend db_test.go (empty hash, timestamp updates, BuildPacketData, schema)
- server: add config_test.go (LoadConfig, LoadTheme, health thresholds, ResolveDBPath)
- server: add helpers_test.go (writeJSON/Error, queryInt, mergeMap, round, percentile, spaHandler)
- server: extend db_test.go (all query functions, filters, channel messages, node health)
- server: extend routes_test.go (all endpoints, error paths, analytics, observer analytics)
- server: extend websocket_test.go (multi-client, buffer full, poller cycle)

Coverage: ingestor 48% -> 80%, server 52% -> 90%

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 00:07:44 -07:00
Kpa-clawbot 488ead617d feat: add standalone Go MQTT ingestor (cmd/ingestor/)
First step of Go rewrite — separates MQTT ingestion from the Node.js
web server. Single static binary (no CGO) that connects to MQTT
brokers, decodes MeshCore packets, and writes to the shared SQLite DB.

Ported from JS:
- decoder.js → decoder.go (header, path, all payload types, adverts)
- computeContentHash → Go (SHA-256, path-independent)
- db.js v3 schema → db.go (transmissions, observations, nodes, observers)
- server.js MQTT logic → main.go (multi-broker, reconnect, IATA filter)

25 Go tests passing (golden fixtures from production + schema compat).
No existing JS files modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 23:22:26 -07:00