import 'dart:ffi'; import 'dart:io'; import 'package:ffi/ffi.dart'; import 'package:flutter/services.dart'; // C function typedefs from bridge.go typedef _StartNative = Pointer Function(Pointer, Pointer, Pointer); typedef _StartProxyNative = Pointer Function(Pointer, Int32, Pointer); typedef _StartProxyDart = Pointer Function(Pointer, int, Pointer); typedef _SimpleNative = Pointer Function(); typedef _OutParamNative = Pointer Function(Pointer>); typedef _FreeNative = Void Function(Pointer); typedef _FreeDart = void Function(Pointer); /// FFI-based implementation for Android and Linux. /// Loads the Go shared library and calls C functions directly. class TsnetFfi { late final DynamicLibrary _lib; late final _StartNative _start; late final _StartProxyDart _startProxy; late final _SimpleNative _stopProxy; late final _SimpleNative _stop; late final _OutParamNative _status; late final _OutParamNative _tailscaleIP; late final _FreeDart _free; TsnetFfi() { _lib = _loadLibrary(); _start = _lib.lookupFunction<_StartNative, _StartNative>('TailscaleStart'); _startProxy = _lib.lookupFunction<_StartProxyNative, _StartProxyDart>('TailscaleStartProxy'); _stopProxy = _lib.lookupFunction<_SimpleNative, _SimpleNative>('TailscaleStopProxy'); _stop = _lib.lookupFunction<_SimpleNative, _SimpleNative>('TailscaleStop'); _status = _lib.lookupFunction<_OutParamNative, _OutParamNative>('TailscaleStatus'); _tailscaleIP = _lib.lookupFunction<_OutParamNative, _OutParamNative>('TailscaleIP'); _free = _lib.lookupFunction<_FreeNative, _FreeDart>('TailscaleFreeString'); } static DynamicLibrary _loadLibrary() { if (Platform.isAndroid) { return DynamicLibrary.open('libtailscale.so'); } else if (Platform.isLinux) { // Look in the app's lib directory (where Flutter bundles native libs) return DynamicLibrary.open('libtailscale.so'); } throw UnsupportedError('FFI not supported on ${Platform.operatingSystem}'); } String? _consumeString(Pointer ptr) { if (ptr == nullptr) return null; final str = ptr.toDartString(); _free(ptr); return str; } void _checkError(Pointer errPtr, String code) { final err = _consumeString(errPtr); if (err != null) { throw PlatformException(code: code, message: err); } } Future start(String stateDir, String authKey, String hostname) async { final dirC = stateDir.toNativeUtf8(); final keyC = authKey.toNativeUtf8(); final hostC = hostname.toNativeUtf8(); try { final err = _start(dirC, keyC, hostC); _checkError(err, 'START_FAILED'); } finally { malloc.free(dirC); malloc.free(keyC); malloc.free(hostC); } } Future startProxy(String remoteIP, int remotePort) async { final ipC = remoteIP.toNativeUtf8(); final portOut = malloc(); try { final err = _startProxy(ipC, remotePort, portOut); _checkError(err, 'PROXY_FAILED'); return portOut.value; } finally { malloc.free(ipC); malloc.free(portOut); } } Future stopProxy() async { final err = _stopProxy(); _checkError(err, 'STOP_PROXY_FAILED'); } Future stop() async { final err = _stop(); _checkError(err, 'STOP_FAILED'); } Future status() async { final errOut = malloc>(); try { errOut.value = nullptr; final json = _status(errOut); final err = _consumeString(errOut.value); if (err != null) throw PlatformException(code: 'STATUS_FAILED', message: err); return _consumeString(json); } finally { malloc.free(errOut); } } Future tailscaleIP() async { final errOut = malloc>(); try { errOut.value = nullptr; final ip = _tailscaleIP(errOut); final err = _consumeString(errOut.value); if (err != null) throw PlatformException(code: 'IP_FAILED', message: err); return _consumeString(ip) ?? ''; } finally { malloc.free(errOut); } } }