Two faults found by watching it run. The heartbeat was the only cadence, so the first pass ran before mDNS discovery finished, saw nothing, and then sat silent for two minutes - precisely when someone has just switched the relay on and is watching for it to work. It now polls every five seconds until the first successful report, then settles. And nothing restarted the service after a reboot or an app update, which is the most common way it dies during development. A relay that has quietly stopped is worse than one never switched on: the endpoint it last published keeps looking authoritative while pointing at a port nothing is listening on. RelayBootReceiver handles BOOT_COMPLETED and MY_PACKAGE_REPLACED, debug builds only, and does nothing unless the relay was already on. Verified end to end: installing this build triggered the receiver and the phone reported its endpoint 7 seconds later, unattended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Echolot app
The production Android client (spec). Native Kotlin + Jetpack Compose. Multi-module; built bottom-up from a verifiable protocol spine.
Modules
| Module | Type | Status |
|---|---|---|
core-protocol |
pure Kotlin/JVM | done — client half of probe-protocol.md, verified live against the server |
core-measurement |
pure Kotlin/JVM | planned — measurement-schema.md types |
core-probe |
Android lib | planned — app-tier probes, ported from echolot-prober |
core-shizuku |
Android lib | planned — dual-path executor (UserService + newProcess fallback) |
app |
Android app | planned — Compose UI |
core-protocol is deliberately Android-free so it builds and unit-tests on any JDK (no Android
SDK) and can run integration tests against a live server.
core-protocol
Implements the control plane (SPKI-pinned enrollment/profile/sessions via HttpsURLConnection —
Android-API-1 compatible, hostname verification off because trust is the pin), the HKDF-SHA256
session-key schedule, and the binary ELT1 UDP data plane (HMAC gate, ECHO + observation block,
MTU probe) — byte-compatible with the Go server.
./gradlew :core-protocol:test # unit tests (crypto vectors, wire round-trip)
scripts/test-fmr.sh # live end-to-end test against the deployed server
test-fmr.sh mints an enrollment token over SSH, enrolls via the public control plane, computes
the SPKI pin from the served cert, and runs LiveServerTest — proving the client speaks the wire
protocol to the real server (enroll → profile → session → echo+observation → MTU → observations).
The live test self-skips when ECHOLOT_LIVE_* env vars are absent, so unit runs and CI stay green
offline.