# iperf3-gui A Flutter-based Android GUI wrapper for iperf3, the popular network performance measurement tool. ## Architecture ``` lib/ # Flutter/Dart application layer ├── main.dart # App entry point (MaterialApp) ├── domain/ # Business logic (platform-independent) │ ├── models/ │ │ ├── test_configuration.dart # Server, port, duration, interval, parallel streams │ │ └── iperf3_result.dart # Raw output + exit code ├── data/ │ └── services/ │ └── iperf3_service.dart # Dart → MethodChannel bridge (async with Completer) └── ui/ # Presentation layer └── features/ └── test_run/ ├── test_run_screen.dart # Server/port inputs + GO button └── test_run_view_model.dart # State machine: idle → running → completed/error android/app/src/main/ ├── kotlin/ # Kotlin plugin layer │ └── com/novoyuuparosk/iperf3gui/ │ ├── MainActivity.kt # Registers IperfRunner as FlutterPlugin │ └── IperfRunner.kt # MethodChannel handler → native JNI calls └── cpp/ # Native iperf3 library ├── CMakeLists.txt # Compiles iperf3 + cJSON + JNI into libiperf3jni.so ├── iperf_jni.c # JNI bridge: Java String[] → iperf3 API ├── iperf/ # iperf3 3.19 C source tree (see below) ├── iperf_config.h # Generated Android-compatible config header └── iperf_config_android.h # Android-specific compile options ``` ## Data Flow ``` User taps GO → Dart TestRunViewModel.runTest(config) → Iperf3Service.run(config) via MethodChannel → Kotlin IperfRunner: submits to the iperf3-run worker thread, returns at once → JNI C: build_argv() (prepends argv[0]) → iperf_parse_arguments() → iperf_run_client(test) [blocks on the worker thread] → Reader thread pipes output → CallVoidMethod → main-thread hop → Dart onOutput → Dart streams each line to the UI via Iperf3Service.outputLines → On completion: Dart Completer resolves with Iperf3Result ``` ## Key Design Decisions ### JNI Over Process Execution Android SELinux Enforcing mode blocks `execve()` from the `untrusted_app` domain, making Process.start() of external binaries fail with Permission denied. Compiling iperf3 as a shared library linked into the app process eliminates this restriction entirely — iperf3 code runs in-process with no domain transitions needed. ### Threading `runIperfLive()` blocks for the whole test, so Kotlin submits it to a dedicated single-thread executor and hands control straight back to Dart. Running it on the Flutter platform thread froze the UI and — because output is delivered by posting to the main looper — stalled every callback until the test was already over. ### Callback Mechanism The JNI bridge uses `CallVoidMethod` on stored `jmethodID`s to invoke the Kotlin `IperfCallback` interface, NOT function pointer casts. Direct function pointer casts caused SIGBUS crashes on ARM due to JNINativeInterface method ID layout differences. ### Output Streaming iperf3 output is redirected via pipe to a background reader thread that attaches to the Java VM via `AttachCurrentThread` and invokes callbacks via `CallVoidMethod`. Dart re-publishes each line on `Iperf3Service.outputLines`, so the transcript grows while the test runs. The reader blocks in `fgets()` and only unblocks on EOF, so the pipe's write end must be closed **before** `pthread_join()` — joining first deadlocks the worker thread. The JNI caller solely owns the run-state struct and frees it after the join; the reader must never free it. ### Stop Handling A stop shuts down the control socket, so `iperf_run_client()` returns -1 with "Broken pipe". That is the expected outcome of a stop, not a failure, so the bridge checks its own `g_stop` flag and reports a stop rather than an error; the flag travels up to the UI as `Iperf3Result.stopped`. ### Async Pattern Dart uses `Completer` to await native completion. The MethodChannel callback handler resolves the completer on `onComplete`, with a timeout fallback that calls `stopIperf` on the native layer. ## iperf3 Source - Version: 3.19 - Location: `android/app/src/main/cpp/iperf/` - Files: Full C source tree including cJSON, headers, man pages, PEM keys - Build: CMake compiles into `libiperf3jni.so` for `armeabi-v7a` and `arm64-v8a` - Config: `iperf_config.h` generated from `iperf_config.h.in` with Android-compatible POSIX/threading macros ## Native JNI API | Kotlin Method | JNI Signature | Purpose | |---|---|---| | `runIperfLive(String[], IperfCallback)` | `runIperfLive` | Run a test to completion — blocks, call off the platform thread | | `forceStopIperfTest(IperfCallback)` | `forceStopIperfTest` | Graceful stop via shutdown() | | `isTestRunning()` | `isTestRunning` | Check if test is in progress | | `setTempDir(String)` | `setTempDir` | Point `mkstemp()` at the app cache dir | ## Building Prerequisites: - Flutter 3.47+ - Android SDK with platform android-36 - JDK 17 - NDK r28+ - CMake 3.22.1 ```bash flutter pub get flutter build apk --debug adb install build/app/outputs/flutter-apk/app-debug.apk ``` ## Dependencies - Dart: `provider` - Android: CMake external native build - Native: iperf3 3.19, cJSON (single file) ## License MIT — bundled iperf3 3.19 is BSD-3-Clause licensed.