Long mode starts listeners at t=0 and keeps them running past the battery: a network-change watcher that finally fills networks[].changes[] (defined since the schema's first draft, never populated), an RSSI log, a ping series giving loss and jitter over minutes, and mDNS listening for the whole window. This is the class of fault a short run cannot see - a link that drops for four seconds between two probes is reported healthy by both of them. run.mode records which question was asked, because silence means different things in the two modes. The adb relay replaces the retired beacon: AdbRelay watches adbd's own mDNS with the resolve-once discipline the beacon learned the hard way (resolving re-arms adbd and pops a notification), a foreground service keeps it alive with the screen off, and the heartbeat re-posts the cached endpoint rather than re-resolving. It exists because mDNS does not cross subnets and the wireless-debug port rotates every few minutes. Also records why LLDP/CDP cannot follow SSDP into long mode: both are raw L2 frames, so they need CAP_NET_RAW - root tier, not app, and Shizuku's shell user does not have it either. 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.