5.2 KiB
Building & testing
Prerequisites & first run
- Android SDK platform-35 + build-tools 35, and the NDK version pinned in
app/build.gradle.kts(ndkVersion). Pointlocal.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(orassembleDebugthenadb install). The tunnel itself can't be shell-started (see on-device QA) — tap «Подключиться» in the UI.
Build
./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 vendoredhev-socks5-tunnel/Android.mkand buildstun2socks.cintolibtun2socks.so.ndkVersionis pinned inapp/build.gradle.kts.jniLibsare kept uncompressed + 16 KB-aligned.
Three test layers
1. JVM unit tests (committed, CI-friendly) — app/src/test/
./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). Base64ss://paths are stubbed on the JVM; those are covered by the harness/device.unitTests.isReturnDefaultValues = truelets the fewandroid.util.Base64calls return null instead of throwing the "Stub!" error. ChaCha20 is not unit-tested (desktop SunJCE has noChaCha20/Poly1305provider — 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 .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/*.ktare 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.pybuilds the Preferences-DataStore protobuf (one entry, keystate= theProxyStateJSON) from stdin and prints it base64: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-ascannot 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→ theip=line. Through the tunnel it's the proxy's exit IP; after disconnect it must return to the WAN IP. Usecurl -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. setallowedAppsempty (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 withnohup … &, then re-adb connectand 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.