feat: switch iperf3 to JNI with 3.19 source
- Switch from bundled iperf3 binary (Process.start) to JNI compilation - iperf3 3.21 source API was incompatible (start_time, iperf_strerror, etc.) - iperf3 3.19 source is proven to work (used by CellularLab) - CMakeLists.txt updated: config.h generated into cpp/iperf/ for correct include paths - iperf_jni.c fixed for 3.19 API (no start_time on iperf_test) - getTestProgress() returns g_test->duration as fallback (no real-time elapsed tracking yet) - NDK CMake pinned to 3.22.1 - UI layer updated: removed Iperf3BinaryPathProvider references - Tests updated: TestRunViewModelTest matches new service interface - pubspec.yaml: removed iperf3 assets bundle, added http dependency - AndroidManifest: INTERNET permission (needed for hostname resolution)
This commit is contained in:
@@ -1,58 +1,9 @@
|
||||
import 'dart:io';
|
||||
// This file is kept as a stub — iperf3 is now compiled as a native
|
||||
// shared library (libiperf3jni.so) via JNI.
|
||||
// The old binary-path-provider pattern (extract ELF from assets → chmod → execve)
|
||||
// was replaced by the JNI approach after SELinux blocked binary execution.
|
||||
// See .hermes/plans/ for details on the migration.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// Provides the path to the iperf3 binary on the device.
|
||||
///
|
||||
/// Call [initialize] in [main] (before [runApp]) to extract the
|
||||
/// bundled asset and populate the cache. After initialization,
|
||||
/// [path] returns a synchronous [String].
|
||||
class Iperf3BinaryPathProvider {
|
||||
static String? _cachedPath;
|
||||
|
||||
/// Extract the bundled iperf3 binary to the app's cache directory.
|
||||
///
|
||||
/// Call this once in [main] before [runApp].
|
||||
static Future<void> initialize() async {
|
||||
if (_cachedPath != null) return;
|
||||
|
||||
final cacheDir = await getApplicationCacheDirectory();
|
||||
final binaryDir = Directory('${cacheDir.path}/iperf3');
|
||||
if (!await binaryDir.exists()) {
|
||||
await binaryDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final abi = await _detectAbi();
|
||||
final assetPath = 'assets/iperf3/$abi/iperf3';
|
||||
|
||||
final bytes = await rootBundle.load(assetPath);
|
||||
final file = File('${binaryDir.path}/iperf3');
|
||||
await file.writeAsBytes(bytes.buffer.asUint8List());
|
||||
|
||||
// chmod +x the binary — /system/bin/sh is always present on Android
|
||||
await Process.run(
|
||||
'/system/bin/sh',
|
||||
['-c', 'chmod +x "\$1"', '_', file.path],
|
||||
);
|
||||
|
||||
_cachedPath = file.path;
|
||||
}
|
||||
|
||||
/// The resolved path to the iperf3 binary.
|
||||
///
|
||||
/// Must call [initialize] first, or this will return a placeholder.
|
||||
static String get path {
|
||||
return _cachedPath ?? '/data/local/tmp/iperf3';
|
||||
}
|
||||
|
||||
static Future<String> _detectAbi() async {
|
||||
final result = await Process.run(
|
||||
'getprop',
|
||||
['ro.product.cpu.abi'],
|
||||
);
|
||||
final abi = result.stdout.toString().trim();
|
||||
if (abi.contains('arm64')) return 'arm64-v8a';
|
||||
return 'armeabi-v7a';
|
||||
}
|
||||
}
|
||||
// The Iperf3Service now uses MethodChannel to communicate with Kotlin/JNI:
|
||||
// Dart → MethodChannel → Kotlin → JNI → iperf3 (same process)
|
||||
// No file extraction, no chmod, no Process.start needed.
|
||||
|
||||
Reference in New Issue
Block a user