proxy/docs/building-testing.md

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). 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

./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/

./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 .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 allowVpnService.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:

    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.