Skip to content

feat(ingestor): store uplink pings (/uplink) - #20

Merged
efiten merged 1 commit into
masterfrom
feat/client-uplink
Oct 16, 2026
Merged

efiten merged 1 commit into
masterfrom
feat/client-uplink

Conversation

@efiten

@efiten efiten commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Summary

Ingestor side of uplink pings, so the data starts accumulating while the map layer is being built (separate PR). CoreDrive RX v1.21.0 sends cdrx <tag> on #coredrive, listens for repeaters re-broadcasting it, and reports UPLINK_PING on meshcore/client/{pk}/uplink.

  • Handler: new uplink subtopic, gated on clientUplink.enabled (default off), stored in table client_uplink_pings (UNIQUE(rx_pubkey, tag)).
    • tag must be 8 hex characters.
    • pkt_hash must be 16 hex characters, otherwise it is dropped.
    • Echoes are validated and capped at 32.
    • GPS is optional.
  • Tag to hash without an echo: the ingestor decrypts GRP_TXT on the ping channel itself and stores the tag with its packet hash in client_uplink_tx.
    • The channel's hash byte (0x76) is shared with busy unrelated channels, so the MAC check and the cdrx <tag> text decide.
    • The channel key is derived from clientUplink.channel and never added to the channel list.
  • Retention: retention.clientUplinkDays covers both tables.
  • Docs: contract in docs/client-rx-coverage.md, keys in config.example.json.

Performance

noteUplinkTransmission runs on the observer packet path only for GRP_TXT on hash byte 0x76 that the ingestor could not decrypt. That is one HMAC per such packet; on live there were dozens per hour on that byte. Everything else returns after a field compare.

Test plan

  • go test ./... in cmd/ingestor: pass. New tests in client_uplink_test.go.
  • Firmware match: #coredrive gives hash byte 0x76, as seen on air in the beta field test (transmission b76c6a2777f9b0e9, 15 observers).
  • Live: clientUplink.enabled on, then a drive with CoreDrive uplinkPing on.

🤖 Generated with Claude Code

CoreDrive RX v1.21.0 sends "cdrx <tag>" on a hashtag channel (#coredrive),
listens for repeaters re-broadcasting it, and reports the ping as
UPLINK_PING on /uplink. This stores it, so the server can later tell per
place whether a repeater heard the companion (echo) and whether an observer
received the ping (arrival).

- case "uplink" in the client topic switch, gated on clientUplink.enabled
  (default off).
- handleClientUplink (cmd/ingestor/client_uplink.go): tag must be 8 hex,
  pkt_hash 16 hex or dropped, echoes validated and capped at 32, GPS
  optional. Table client_uplink_pings, UNIQUE(rx_pubkey, tag), with the
  distinct first hops kept for the map.
- Without an echo the companion has no packet hash. The ingestor then learns
  it itself: noteUplinkTransmission decrypts GRP_TXT on the ping channel's
  hash byte (0x76 for #coredrive, shared with unrelated channels, so the MAC
  and the "cdrx <tag>" text decide) and stores tag -> hash in
  client_uplink_tx. The channel key is derived once from clientUplink.channel
  and is not added to the channel list.
- Retention retention.clientUplinkDays for both tables.
- Contract in docs/client-rx-coverage.md, keys in config.example.json.

Tests: handler with echoes, no-echo, rejects/sanitising, idempotency,
firmware channel hash (0x76 as seen on air), tag -> hash linking against a
packet encrypted like the firmware does, config default, gate on/off,
prune. Full ingestor suite passes. Field data: the beta ping
cdrx 78277c0e, hash b76c6a2777f9b0e9, received by 15 observers.

Server endpoint and map layer follow in a separate PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@efiten
efiten merged commit b146a87 into master Oct 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant