proxy/docs/building-testing.md

4.1 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.
  • 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. .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: build the Preferences-DataStore protobuf yourself (one entry, key state = the ProxyState JSON) and write it via run-as, piping base64 through stdin (run-as can't read /sdcard). Force-stop the app first (it caches DataStore in memory).
  • 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.
  • 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.