docs: document RU/EN localization, the mk_datastore config injector, Wi-Fi→cellular handover QA, and the harness ChaCha shim / stand-in caveats

This commit is contained in:
heaven 2026-08-23 04:08:25 +03:00
parent 3373cd72d4
commit 57317a62ed
4 changed files with 47 additions and 5 deletions

View file

@ -44,4 +44,7 @@ the agent map. It exists so an agent session can load context fast. Read in this
| Persistence / app list | `data/{ConfigStore,AppRepository}.kt` | | Persistence / app list | `data/{ConfigStore,AppRepository}.kt` |
| UI | `ui/` (`MainScreen`, `ConnectionScreen`, `ServersScreen`, `AppsScreen`, `SettingsScreen`, `MainViewModel`) | | UI | `ui/` (`MainScreen`, `ConnectionScreen`, `ServersScreen`, `AppsScreen`, `SettingsScreen`, `MainViewModel`) |
| Native (JNI bridge + engine) | `core/Tun2Socks.kt`, `app/src/main/jni/tun2socks.c`, `app/src/main/jni/hev-socks5-tunnel/` | | Native (JNI bridge + engine) | `core/Tun2Socks.kt`, `app/src/main/jni/tun2socks.c`, `app/src/main/jni/hev-socks5-tunnel/` |
| Domain routing (matcher, geosite, direct dial) | `core/route/`, `core/upstream/DirectDialer.kt` |
| UI language (RU/EN, no AppCompat) | `core/LocaleManager.kt`, `res/values{,-ru}/strings.xml` |
| Dev tools (geosite refresh, device config injector) | `tools/fetch-geosite.sh`, `tools/mk_datastore.py` |
| Unit tests | `app/src/test/` | | Unit tests | `app/src/test/` |

View file

@ -157,3 +157,22 @@ UI, call `VpnState.event(label, detail?, tone)` — `tone ∈ {NEUTRAL, OK, WARN
a ring buffer (`MAX_EVENTS = 30`); `ConnectionScreen` renders them newest-first with auto-scroll. a ring buffer (`MAX_EVENTS = 30`); `ConnectionScreen` renders them newest-first with auto-scroll.
This is the *only* channel the service uses to report progress (connect steps, the probe verdict, This is the *only* channel the service uses to report progress (connect steps, the probe verdict,
errors) — use it rather than inventing a parallel one. errors) — use it rather than inventing a parallel one.
## Localization (RU / EN)
Base language is **English** (`res/values/strings.xml`); the Russian override is
`res/values-ru/strings.xml`, and `resourceConfigurations += setOf("en", "ru")` keeps other
locales out of the APK (they fall back to English). The in-app switcher (Система / Русский /
English) is `LanguageSelector` in `ui/SettingsScreen.kt`.
The per-app locale is applied **without AppCompat** — this app is Compose + `ComponentActivity`
only and the tiny dependency surface is a hard constraint, so localization uses the classic
framework-only pattern. `core/LocaleManager` persists the tag in its own small SharedPreferences
and applies it as a `Configuration` override from `attachBaseContext` of *every* component that
resolves strings: `MainActivity`, `ProxyVpnService`, `ProxyTileService`. Switching persists the
tag and `recreate()`s the activity. On `SYSTEM` the process-default `Locale` is restored, so a
once-picked UI language can't leak into date/number formatting elsewhere.
**Language is deliberately not in `AppSettings`/DataStore.** It is a presentation concern, it has
to be read *synchronously* at `attachBaseContext` (before any coroutine or DataStore exists), and
it must not run through `reconnectIfRunning()` the way a routing or app-filter change does.

View file

@ -49,10 +49,15 @@ bash .harness/run.sh # all green (0 FAIL); run.sh is the source of truth
- Covers things unit tests can't: real AEAD round trips through a reference SS server, SOCKS5 - Covers things unit tests can't: real AEAD round trips through a reference SS server, SOCKS5
UDP ASSOCIATE incl. wrong-source-drop, HTTP CONNECT 407/bare-LF, HTTPS hostname verification, UDP ASSOCIATE incl. wrong-source-drop, HTTP CONNECT 407/bare-LF, HTTPS hostname verification,
the post-connect probe classification. the post-connect probe classification.
- **ChaCha20 on a desktop JVM:** OpenJDK's SunJCE registers `ChaCha20-Poly1305`, not the slash
name Conscrypt uses on device, so the harness installs a small JCA alias shim. The *unmodified*
upstream code then runs, which is what proves its framing byte-correct.
- **Drift caveat:** `.harness/src/*.kt` are *copies* of the real source. If you change upstream - **Drift caveat:** `.harness/src/*.kt` are *copies* of the real source. If you change upstream
code (`core/upstream`, `core/crypto`, `core/net`), copy the changed file into `.harness/src/` code (`core/upstream`, `core/crypto`, `core/net`), copy the changed file into `.harness/src/`
before trusting a green run. `.harness/` is gitignored (it ships throwaway test-CA keys and before trusting a green run — a stale copy once made a green run vouch for code that had
build artifacts). already changed. The one file you must **not** overwrite is `.harness/src/ProxyConfig.kt`: it is
a deliberate `@Serializable`-stripped stand-in so the rig compiles without the serialization
plugin. `.harness/` is gitignored (it ships throwaway test-CA keys and build artifacts).
### 3. On-device QA — Galaxy S23 over network ADB ### 3. On-device QA — Galaxy S23 over network ADB
@ -60,9 +65,16 @@ The debug build is debuggable, so config can be injected without tapping through
- **VPN consent without the dialog:** `adb shell appops set chat.vojo.proxy.debug ACTIVATE_VPN allow` - **VPN consent without the dialog:** `adb shell appops set chat.vojo.proxy.debug ACTIVATE_VPN allow`
`VpnService.prepare()` returns null. `VpnService.prepare()` returns null.
- **Inject a config:** build the Preferences-DataStore protobuf yourself (one entry, key `state` - **Inject a config:** `tools/mk_datastore.py` builds the Preferences-DataStore protobuf (one
= the `ProxyState` JSON) and write it via `run-as`, piping base64 through stdin (run-as can't entry, key `state` = the `ProxyState` JSON) from stdin and prints it base64:
read `/sdcard`). Force-stop the app first (it caches DataStore in memory).
```bash
B64=$(printf %s "$JSON" | python3 tools/mk_datastore.py)
adb shell am force-stop chat.vojo.proxy.debug # DataStore is cached in memory
adb shell "run-as chat.vojo.proxy.debug sh -c 'echo $B64 | base64 -d > files/datastore/vojo_proxy.preferences_pb'"
```
Pipe the base64 through stdin — `run-as` cannot read `/sdcard`.
- **Start the tunnel:** the service is not shell-startable (`BIND_VPN_SERVICE`, not exported) — - **Start the tunnel:** the service is not shell-startable (`BIND_VPN_SERVICE`, not exported) —
tap «Подключиться» in the UI (`adb shell input tap …`). tap «Подключиться» in the UI (`adb shell input tap …`).
- **Exit-IP / leak probe:** `adb shell curl -s https://1.1.1.1/cdn-cgi/trace` → the `ip=` line. - **Exit-IP / leak probe:** `adb shell curl -s https://1.1.1.1/cdn-cgi/trace` → the `ip=` line.
@ -71,5 +83,9 @@ The debug build is debuggable, so config can be injected without tapping through
- **Per-app gotcha:** to probe via `adb shell curl`, the shell UID must be routed — i.e. set - **Per-app gotcha:** to probe via `adb shell curl`, the shell UID must be routed — i.e. set
`allowedApps` empty (route all) for the test, or the probe bypasses the tunnel and looks like a `allowedApps` empty (route all) for the test, or the probe bypasses the tunnel and looks like a
leak. leak.
- **Wi-Fi → cellular handover:** ADB rides Wi-Fi, so you can't watch the switch live. Push a
script that logs the exit IP, `svc wifi disable`, sleeps, logs again (now cellular),
`svc wifi enable`, logs once more, all into `/sdcard`; start it with `nohup … &`, then
re-`adb connect` and pull the file. The exit IP must stay the proxy's across the switch.
- **WiFi-ADB drops** are the phone's Wi-Fi power-save on screen-sleep, not WSL. Keep the screen - **WiFi-ADB drops** are the phone's Wi-Fi power-save on screen-sleep, not WSL. Keep the screen
awake (`settings put global stay_on_while_plugged_in 3`) or use USB via usbipd. awake (`settings put global stay_on_while_plugged_in 3`) or use USB via usbipd.

View file

@ -9,6 +9,10 @@
Shadowsocks key but usually can't *name* it — say exactly that). Shadowsocks key but usually can't *name* it — say exactly that).
- No new dependencies without a strong reason — a deliberate property of this app is a tiny - No new dependencies without a strong reason — a deliberate property of this app is a tiny
dependency surface (no crypto lib; AEAD is on platform JCA). dependency surface (no crypto lib; AEAD is on platform JCA).
- **Human-readable text lives in string resources** — English base + full `values-ru` parity
(see architecture.md). Language-neutral tokens (IPs, ports, `MTU`, `IPv6`, `example.com`,
protocol names — `ProxyType.label`/`badge` included) stay code literals. Russian developer
*comments* are fine as they are; only user-visible text gets translated.
## Commits ## Commits