- AndroidManifest: add CHANGE_NETWORK_STATE — requestNetwork threw SecurityException, multinetwork.request_and_bind could never run. - IcmpProbe: format rtt_ms with Locale.ROOT — Austrian locale produced "38,1" in the JSON report. - ShizukuRunner/ShizukuProbe: bind the UserService once per battery (execBatch) instead of per command; the per-command bind/unbind raced Shizuku and 3/7 commands died on SHIZUKU_BIND_TIMEOUT. Archive the report at echolot-prober/reports/, record findings in build-status.md. Notable: errqueue path fully reachable on Android 16 — the native shim may be unnecessary on modern devices. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
6.9 KiB
Markdown
97 lines
6.9 KiB
Markdown
# Echolot — build status & next steps
|
||
|
||
Last updated: 2026-07-29.
|
||
|
||
## Decided
|
||
- Name **Echolot**; domain echo-lot.app; scheme `echolot://`; namespace `app.echo_lot.*`
|
||
(hyphen→underscore; appIds/packages can't contain hyphens). Prober appId `app.echo_lot.prober`.
|
||
- Stack: native **Kotlin + Jetpack Compose**, no Flutter.
|
||
- Tiers: `app` (no root), **`shizuku` in v1** (wireless-ADB pairing), `root` future.
|
||
- **License (decided 2026-07-30):** all code **GPL-3.0-or-later**; the specs in `docs/` **CC-BY-4.0**.
|
||
Rationale: the moat is the no-root platform research, which is trivially liftable into a
|
||
proprietary repackage — copyleft is the only option that prevents that, and the F-Droid /
|
||
network-engineer audience reads GPL as a trust signal rather than friction. The specs go
|
||
permissive on purpose: a wire protocol only becomes a standard if anyone can implement it.
|
||
**Not AGPL for the server** — the ASP loophole it closes is speculative here, while blanket
|
||
corporate AGPL bans would hit exactly the enterprise network teams most likely to self-host.
|
||
Sole copyright holder, so relicensing the server to AGPL later stays possible.
|
||
Open: substitute a real legal copyright holder for "Echolot contributors" in the SPDX headers.
|
||
|
||
## Specs (in `docs/`, alongside this file)
|
||
- `feature-catalog-and-feasibility.md`, `measurement-schema.md`, `probe-protocol.md`. Considered draft-complete and reviewed by the user.
|
||
|
||
## Capability prober — DELIVERED (as zip, 2026-07-29)
|
||
Full Kotlin/Compose project scaffolded: `app.echo_lot.prober`, minSdk 26 / target+compile 35,
|
||
AGP 8.7.3, Kotlin 2.0.21, Compose BOM 2024.10, Shizuku api+provider 13.1.5, kotlinx-serialization.
|
||
Probes implemented: `link.snapshot`, `icmp.ping4/6` (unprivileged ICMP datagram), `sockopt.matrix`
|
||
(TTL/TOS/RECVERR/MTU_DISCOVER), `trace.errqueue_reachable`, `multinetwork.request_and_bind`,
|
||
`local.mdns_discover`, `peer.ble_advertise`, `shizuku.command_battery` (ip neigh / ip -6 route /
|
||
ip addr / ip monitor / dumpsys network_stack DHCP+IpClient / dumpsys wifi). JSON export via share
|
||
intent; results carry verdict + raw evidence.
|
||
|
||
Could NOT be compiled in the cloud sandbox: dl.google.com (Google Maven) and services.gradle.org
|
||
are proxy-blocked, and no device is reachable for on-device runs. Build + iterate locally
|
||
(Android Studio / Claude Code). Wrapper is pinned to Gradle 8.14.3.
|
||
|
||
## Website — `web/` (2026-07-30, LIVE at https://echo-lot.app)
|
||
Single-page site for echo-lot.app on Cloudflare Workers: static `public/` from the edge, a small
|
||
Worker for the stable short URLs (`/apk`, `/apk.sha256`, `/fdroid`, `/source`) and `/api/latest`.
|
||
Version info is resolved from the Gitea releases API at request time (edge-cached 5 min), so
|
||
tagging a release is the only publish step — the homepage shows the latest version/date/size via
|
||
progressive-enhancement JS and falls back to pre-release copy otherwise. Auto light/dark via
|
||
`prefers-color-scheme`: dark = sonar display, light = chart-recorder printout, same amber-accent
|
||
token system. Deploy: `npx wrangler deploy` from `web/`, or CI via
|
||
`.gitea/workflows/deploy-site.yml` (needs `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`
|
||
secrets); full story in `web/README.md`. Branding applied (2026-07-30): real favicon/wordmark
|
||
copied into `web/public/assets/` (copies — `assets/branding/` is the source of truth), palette
|
||
switched to abyss/teal/amber with teal-as-instrument semantics, sonar-sweep hero replaced by a
|
||
Focus-lattice panel (rogue-DHCP finding vignette), tagline "measure, don't guess".
|
||
|
||
## Branding — `assets/branding/` (2026-07-30)
|
||
Identity chosen: the **Focus** mark — a quiet lattice of nodes with exactly one under examination
|
||
(amber) inside teal viewfinder brackets. Tone is surgical measurement, not scanning (sonar-sweep
|
||
visuals and the "sound out your network" tagline were explicitly rejected); tagline is
|
||
**"measure, don't guess"**. Palette: abyss `#071522`, tile `#0E2433`, instrument teal `#35E0C4`,
|
||
finding amber `#FFB454`, foam `#E8F4F2` — teal is always the instrument, the single amber point is
|
||
the finding. The wordmark is hand-drawn monoline SVG paths (zero font dependency); the second "o"
|
||
is the signature teal ring + amber ping. Assets: `icon.svg` (+ `icon.png` 512² — Gitea avatars reject SVG), `icon-adaptive-{foreground,background}.svg`
|
||
(108dp adaptive-icon layers, art inside the 66dp safe circle), `wordmark-on-{dark,light}.svg`,
|
||
`banner.svg` (1200×300, wired into the repo README), `social-preview.svg`/`.png` (1280×640, for the
|
||
Gitea/GitHub social-preview slot). The website's placeholder favicon/wordmark can now be replaced
|
||
from these.
|
||
|
||
## First device report — OnePlus 15, Android 16/SDK 36 (2026-07-30)
|
||
Archived at `echolot-prober/reports/CPH2747-android16-sdk36.json`. Collection workflow that works:
|
||
deploy over (wireless) adb, but the **user** starts the run and exports the JSON manually — probe
|
||
runs reliably kill the adb-over-wifi session (multinetwork/multicast churn the link adb rides on).
|
||
|
||
OS findings:
|
||
- `link.snapshot`, `icmp.ping4` (38 ms), `sockopt.matrix` 4/4, `peer.ble_advertise`: SUPPORTED.
|
||
- **`trace.errqueue_reachable` SUPPORTED** — full Os-API errqueue path exists on Android 16;
|
||
the C-over-JNI shim (next-steps #2) may be unnecessary on modern devices. Still needs a real
|
||
errqueue round-trip test, and older devices may differ.
|
||
- `icmp.ping6` EAGAIN — topology, not capability: wifi was v4-only, v6 only on cellular, socket
|
||
used the default network. Probe should bind per-network (also covers the ping4 case properly).
|
||
- `shizuku.command_battery` SUPPORTED, shell(2000): real `ip -6 route` RA data (lifetimes,
|
||
per-table), `ip monitor` NEIGH events, wifi dump with full DhcpResults. Vendor formats look
|
||
parseable.
|
||
- `local.mdns_discover`: lock acquired, discovery ran, 0 services in the 4 s window — needs a
|
||
longer window / known-good reference network before calling it more than formally SUPPORTED.
|
||
|
||
Prober bugs the run exposed (all fixed same day):
|
||
- rotation wiped results + cancelled runs → state moved to a ViewModel;
|
||
- `multinetwork.request_and_bind` ERROR → missing `CHANGE_NETWORK_STATE` in the manifest;
|
||
- `rtt_ms: "38,1"` → device locale leaked into report numbers, now `Locale.ROOT`;
|
||
- 3/7 Shizuku commands `SHIZUKU_BIND_TIMEOUT` → bind/unbind-per-command raced Shizuku; now one
|
||
bind per battery (`execBatch`).
|
||
Open: re-run on-device to confirm the multinetwork + Shizuku fixes; then re-export.
|
||
|
||
## Next steps
|
||
1. Build locally, run on several physical devices (varied Android versions/vendors), collect the
|
||
JSON reports — especially the real per-device Shizuku dump formats.
|
||
2. If `trace.errqueue_reachable` = PARTIAL, add a C-over-JNI errqueue shim (`recvmsg`+cmsg parse)
|
||
as a `:native` module and a real `traceroute.udp4` probe.
|
||
3. Start the Go server skeleton (enrollment + profile + sessions + UDP echo with observation
|
||
blocks + canary-DNS reference records) per probe-protocol.md.
|
||
4. Fold confirmed capabilities into the production `core-probe` / `core-shizuku` modules.
|