91 lines
5.2 KiB
Markdown
91 lines
5.2 KiB
Markdown
# Building & testing
|
|
|
|
## Prerequisites & first run
|
|
|
|
- Android SDK **platform-35** + **build-tools 35**, and the **NDK** version pinned in
|
|
`app/build.gradle.kts` (`ndkVersion`). Point `local.properties` (`sdk.dir=…`) at the SDK.
|
|
- The Gradle wrapper is bundled (`./gradlew`, currently Gradle 8.10.2) — no system Gradle needed.
|
|
- First device run: `./gradlew :app:installDebug` (or `assembleDebug` then `adb install`). The
|
|
tunnel itself can't be shell-started (see on-device QA) — tap «Подключиться» in the UI.
|
|
|
|
## Build
|
|
|
|
```bash
|
|
./gradlew :app:assembleDebug # debug APK (id chat.vojo.proxy.debug)
|
|
./gradlew :app:assembleRelease # minified + shrunk; signed with the bundled debug keystore
|
|
```
|
|
|
|
- `minSdk 28`, `targetSdk`/`compileSdk 35`, single ABI **arm64-v8a**.
|
|
- Native build is **ndk-build** via `app/src/main/jni/Android.mk`, which includes the vendored
|
|
`hev-socks5-tunnel/Android.mk` and builds `tun2socks.c` into `libtun2socks.so`. `ndkVersion`
|
|
is pinned in `app/build.gradle.kts`. `jniLibs` are kept uncompressed + 16 KB-aligned.
|
|
|
|
## Three test layers
|
|
|
|
### 1. JVM unit tests (committed, CI-friendly) — `app/src/test/`
|
|
|
|
```bash
|
|
./gradlew :app:testDebugUnitTest
|
|
```
|
|
|
|
- `ShadowsocksCryptoTest` — SIP004 **known-answer tests** (master key, HKDF subkey, AES-128/256
|
|
GCM ciphertext — all cross-checked against shadowsocks-rust/sing-shadowsocks and recomputed in
|
|
python), round-trips, wrong-key failure, the zero-length-chunk regression.
|
|
- `ConfigImportTest` — share-link parser (the percent-decode and bare-IPv6 fixes, scheme
|
|
dispatch). Base64 `ss://` paths are stubbed on the JVM; those are covered by the harness/device.
|
|
- `unitTests.isReturnDefaultValues = true` lets the few `android.util.Base64` calls return null
|
|
instead of throwing the "Stub!" error. ChaCha20 is **not** unit-tested (desktop SunJCE has no
|
|
`ChaCha20/Poly1305` provider — it runs via Conscrypt on device + in the harness).
|
|
|
|
### 2. Interop harness (local only, gitignored) — `.harness/`
|
|
|
|
A standalone JVM rig that compiles **vendored copies** of the upstream Kotlin and runs them
|
|
against Python reference servers (real SOCKS5 / HTTP(S) / Shadowsocks).
|
|
|
|
```bash
|
|
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
|
|
UDP ASSOCIATE incl. wrong-source-drop, HTTP CONNECT 407/bare-LF, HTTPS hostname verification,
|
|
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
|
|
code (`core/upstream`, `core/crypto`, `core/net`), copy the changed file into `.harness/src/`
|
|
before trusting a green run — a stale copy once made a green run vouch for code that had
|
|
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
|
|
|
|
The debug build is debuggable, so config can be injected without tapping through the UI:
|
|
|
|
- **VPN consent without the dialog:** `adb shell appops set chat.vojo.proxy.debug ACTIVATE_VPN allow`
|
|
→ `VpnService.prepare()` returns null.
|
|
- **Inject a config:** `tools/mk_datastore.py` builds the Preferences-DataStore protobuf (one
|
|
entry, key `state` = the `ProxyState` JSON) from stdin and prints it base64:
|
|
|
|
```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) —
|
|
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.
|
|
Through the tunnel it's the proxy's exit IP; after disconnect it must return to the WAN IP.
|
|
Use `curl -w '%{time_namelookup}'` to watch DNS latency (the old UDP-relay bug showed as ~6 s).
|
|
- **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
|
|
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
|
|
awake (`settings put global stay_on_while_plugged_in 3`) or use USB via usbipd.
|