0dc747edef
The root README still documented the abandoned approach: iperf3 3.21 binaries bundled in assets and run through a Process runner, and a lib/ layout that no longer matched. It now covers the JNI design, the current feature set and how to test against a local iperf3 server, and says plainly that the binaries still sitting in assets/iperf3/ are undeclared in pubspec.yaml and therefore never reach the APK. docs/README.md gains the threading, pipe-shutdown and stop-handling constraints that the fixes on this branch depend on, so the next change does not have to rediscover why the join order and the worker thread matter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
125 lines
5.5 KiB
Markdown
125 lines
5.5 KiB
Markdown
# 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<Iperf3Result>` 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.
|