veilid/veilid-flutter/lib/veilid_ffi.dart

346 lines
11 KiB
Dart
Raw Normal View History

2022-02-07 02:18:42 +00:00
import 'dart:async';
2022-02-09 14:47:36 +00:00
import 'dart:ffi';
2022-02-07 02:18:42 +00:00
import 'dart:io';
2022-02-09 14:47:36 +00:00
import 'dart:isolate';
import 'dart:convert';
2022-02-07 02:18:42 +00:00
import 'package:ffi/ffi.dart';
2022-02-09 14:47:36 +00:00
import 'veilid.dart';
2022-02-07 02:18:42 +00:00
//////////////////////////////////////////////////////////
// Load the veilid_flutter library once
const _base = 'veilid_flutter';
final _path = Platform.isWindows
? '$_base.dll'
: Platform.isMacOS
? 'lib$_base.dylib'
: 'lib$_base.so';
2022-02-09 14:47:36 +00:00
late final _dylib =
Platform.isIOS ? DynamicLibrary.process() : DynamicLibrary.open(_path);
2022-02-07 02:18:42 +00:00
// Linkage for initialization
2022-02-14 02:09:43 +00:00
typedef _DartPostCObject
2022-02-09 14:47:36 +00:00
= NativeFunction<Int8 Function(Int64, Pointer<Dart_CObject>)>;
2022-02-07 02:18:42 +00:00
// fn free_string(s: *mut std::os::raw::c_char)
2022-02-14 02:09:43 +00:00
typedef _FreeStringC = Void Function(Pointer<Utf8>);
typedef _FreeStringDart = void Function(Pointer<Utf8>);
2022-02-07 02:18:42 +00:00
// fn initialize_veilid_flutter(dart_post_c_object_ptr: ffi::DartPostCObjectFnType)
2022-02-14 02:09:43 +00:00
typedef _InitializeVeilidFlutterC = Void Function(Pointer<_DartPostCObject>);
typedef _InitializeVeilidFlutterDart = void Function(Pointer<_DartPostCObject>);
2022-02-07 02:18:42 +00:00
// fn startup_veilid_core(port: i64, config: FfiStr)
2022-02-14 02:09:43 +00:00
typedef _StartupVeilidCoreC = Void Function(Int64, Pointer<Utf8>);
typedef _StartupVeilidCoreDart = void Function(int, Pointer<Utf8>);
2022-02-07 02:18:42 +00:00
// fn get_veilid_state(port: i64)
2022-02-14 02:09:43 +00:00
typedef _GetVeilidStateC = Void Function(Int64);
typedef _GetVeilidStateDart = void Function(int);
2022-02-07 02:18:42 +00:00
// fn change_api_log_level(port: i64, log_level: FfiStr)
2022-02-14 02:09:43 +00:00
typedef _ChangeApiLogLevelC = Void Function(Int64, Pointer<Utf8>);
typedef _ChangeApiLogLevelDart = void Function(int, Pointer<Utf8>);
2022-03-04 01:45:39 +00:00
// fn debug(port: i64, log_level: FfiStr)
typedef _DebugC = Void Function(Int64, Pointer<Utf8>);
typedef _DebugDart = void Function(int, Pointer<Utf8>);
2022-02-07 02:18:42 +00:00
// fn shutdown_veilid_core(port: i64)
2022-02-14 02:09:43 +00:00
typedef _ShutdownVeilidCoreC = Void Function(Int64);
typedef _ShutdownVeilidCoreDart = void Function(int);
2022-02-07 02:18:42 +00:00
// fn veilid_version_string() -> *mut c_char
2022-02-14 02:09:43 +00:00
typedef _VeilidVersionStringC = Pointer<Utf8> Function();
typedef _VeilidVersionStringDart = Pointer<Utf8> Function();
2022-02-09 14:47:36 +00:00
2022-02-07 02:18:42 +00:00
// fn veilid_version() -> VeilidVersion
2022-02-09 14:47:36 +00:00
class VeilidVersionFFI extends Struct {
2022-02-07 02:18:42 +00:00
@Uint32()
external int major;
@Uint32()
external int minor;
@Uint32()
external int patch;
}
2022-02-09 14:47:36 +00:00
2022-02-14 02:09:43 +00:00
typedef _VeilidVersionC = VeilidVersionFFI Function();
typedef _VeilidVersionDart = VeilidVersionFFI Function();
2022-02-09 14:47:36 +00:00
// Async message types
2022-02-14 02:09:43 +00:00
const int messageOk = 0;
const int messageErr = 1;
const int messageOkJson = 2;
const int messageErrJson = 3;
const int messageStreamItem = 4;
const int messageStreamItemJson = 5;
const int messageStreamAbort = 6;
const int messageStreamAbortJson = 7;
const int messageStreamClose = 8;
2022-02-07 02:18:42 +00:00
// Interface factory for high level Veilid API
Veilid getVeilid() => VeilidFFI(_dylib);
2022-02-09 14:47:36 +00:00
// Parse handle async returns
2022-03-04 01:45:39 +00:00
Future<T> processFuturePlain<T>(Future<dynamic> future) {
2022-02-09 14:47:36 +00:00
return future.then((value) {
final list = value as List<dynamic>;
switch (list[0] as int) {
2022-02-14 02:09:43 +00:00
case messageOk:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
if (list[1] == null) {
throw VeilidAPIExceptionInternal("Null MESSAGE_OK value");
2022-02-09 14:47:36 +00:00
}
return list[1] as T;
}
2022-02-14 02:09:43 +00:00
case messageErr:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
throw VeilidAPIExceptionInternal("Internal API Error: ${list[1]}");
}
2022-02-14 02:09:43 +00:00
case messageErrJson:
2022-02-10 02:40:01 +00:00
{
throw VeilidAPIException.fromJson(jsonDecode(list[1]));
}
default:
{
throw VeilidAPIExceptionInternal(
"Unexpected async return message type: ${list[0]}");
}
}
}).catchError((e) {
// Wrap all other errors in VeilidAPIExceptionInternal
throw VeilidAPIExceptionInternal(e.toString());
}, test: (e) {
// Pass errors that are already VeilidAPIException through without wrapping
return e is! VeilidAPIException;
});
}
Future<T> processFutureJson<T>(
2022-02-14 02:09:43 +00:00
T Function(Map<String, dynamic>) jsonConstructor, Future<dynamic> future) {
2022-02-10 02:40:01 +00:00
return future.then((value) {
final list = value as List<dynamic>;
switch (list[0] as int) {
2022-02-14 02:09:43 +00:00
case messageErr:
2022-02-10 02:40:01 +00:00
{
throw VeilidAPIExceptionInternal("Internal API Error: ${list[1]}");
2022-02-09 14:47:36 +00:00
}
2022-02-14 02:09:43 +00:00
case messageOkJson:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
if (list[1] == null) {
throw VeilidAPIExceptionInternal("Null MESSAGE_OK_JSON value");
2022-02-09 14:47:36 +00:00
}
2022-02-10 02:40:01 +00:00
var ret = jsonDecode(list[1] as String);
return jsonConstructor(ret);
2022-02-09 14:47:36 +00:00
}
2022-02-14 02:09:43 +00:00
case messageErrJson:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
throw VeilidAPIException.fromJson(jsonDecode(list[1]));
2022-02-09 14:47:36 +00:00
}
default:
{
throw VeilidAPIExceptionInternal(
2022-02-10 02:40:01 +00:00
"Unexpected async return message type: ${list[0]}");
2022-02-09 14:47:36 +00:00
}
}
}).catchError((e) {
// Wrap all other errors in VeilidAPIExceptionInternal
throw VeilidAPIExceptionInternal(e.toString());
}, test: (e) {
// Pass errors that are already VeilidAPIException through without wrapping
return e is! VeilidAPIException;
});
}
2022-02-10 02:40:01 +00:00
Future<void> processFutureVoid(Future<dynamic> future) {
2022-02-09 14:47:36 +00:00
return future.then((value) {
final list = value as List<dynamic>;
switch (list[0] as int) {
2022-02-14 02:09:43 +00:00
case messageOk:
2022-02-09 14:47:36 +00:00
{
if (list[1] != null) {
throw VeilidAPIExceptionInternal(
"Unexpected MESSAGE_OK value '${list[1]}' where null expected");
}
return;
}
2022-02-14 02:09:43 +00:00
case messageErr:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
throw VeilidAPIExceptionInternal("Internal API Error: ${list[1]}");
2022-02-09 14:47:36 +00:00
}
2022-02-14 02:09:43 +00:00
case messageOkJson:
2022-02-09 14:47:36 +00:00
{
var ret = jsonDecode(list[1] as String);
if (ret != null) {
throw VeilidAPIExceptionInternal(
"Unexpected MESSAGE_OK_JSON value '$ret' where null expected");
}
return;
}
2022-02-14 02:09:43 +00:00
case messageErrJson:
2022-02-09 14:47:36 +00:00
{
2022-02-10 02:40:01 +00:00
throw VeilidAPIException.fromJson(jsonDecode(list[1] as String));
2022-02-09 14:47:36 +00:00
}
default:
{
throw VeilidAPIExceptionInternal(
2022-02-10 02:40:01 +00:00
"Unexpected async return message type: ${list[0]}");
2022-02-09 14:47:36 +00:00
}
}
}).catchError((e) {
// Wrap all other errors in VeilidAPIExceptionInternal
throw VeilidAPIExceptionInternal(e.toString());
}, test: (e) {
// Pass errors that are already VeilidAPIException through without wrapping
return e is! VeilidAPIException;
});
}
2022-02-14 02:09:43 +00:00
Stream<T> processStreamJson<T>(
2022-03-09 03:32:12 +00:00
T Function(Map<String, dynamic>) jsonConstructor, ReceivePort port) async* {
try {
await for (var value in port) {
final list = value as List<dynamic>;
switch (list[0] as int) {
case messageStreamItemJson:
{
if (list[1] == null) {
throw VeilidAPIExceptionInternal(
"Null MESSAGE_STREAM_ITEM_JSON value");
}
var ret = jsonDecode(list[1] as String);
yield jsonConstructor(ret);
break;
2022-02-14 02:09:43 +00:00
}
2022-03-09 03:32:12 +00:00
case messageStreamAbort:
{
port.close();
throw VeilidAPIExceptionInternal("Internal API Error: ${list[1]}");
}
case messageStreamAbortJson:
{
port.close();
throw VeilidAPIException.fromJson(jsonDecode(list[1]));
}
case messageStreamClose:
{
port.close();
break;
}
default:
{
throw VeilidAPIExceptionInternal(
"Unexpected async return message type: ${list[0]}");
}
}
2022-02-14 02:09:43 +00:00
}
2022-03-09 03:32:12 +00:00
} catch (e) {
2022-02-14 02:09:43 +00:00
// Wrap all other errors in VeilidAPIExceptionInternal
throw VeilidAPIExceptionInternal(e.toString());
2022-03-09 03:32:12 +00:00
}
2022-02-14 02:09:43 +00:00
}
2022-02-07 02:18:42 +00:00
// FFI implementation of high level Veilid API
2022-02-09 14:47:36 +00:00
class VeilidFFI implements Veilid {
2022-02-07 02:18:42 +00:00
// veilid_core shared library
final DynamicLibrary _dylib;
// Shared library functions
2022-02-14 02:09:43 +00:00
final _FreeStringDart _freeString;
final _StartupVeilidCoreDart _startupVeilidCore;
final _GetVeilidStateDart _getVeilidState;
final _ChangeApiLogLevelDart _changeApiLogLevel;
final _ShutdownVeilidCoreDart _shutdownVeilidCore;
2022-03-04 01:45:39 +00:00
final _DebugDart _debug;
2022-02-14 02:09:43 +00:00
final _VeilidVersionStringDart _veilidVersionString;
final _VeilidVersionDart _veilidVersion;
2022-02-07 02:18:42 +00:00
2022-02-09 14:47:36 +00:00
VeilidFFI(DynamicLibrary dylib)
: _dylib = dylib,
2022-02-14 02:09:43 +00:00
_freeString =
dylib.lookupFunction<_FreeStringC, _FreeStringDart>('free_string'),
_startupVeilidCore =
dylib.lookupFunction<_StartupVeilidCoreC, _StartupVeilidCoreDart>(
'startup_veilid_core'),
2022-02-09 14:47:36 +00:00
_getVeilidState =
2022-02-14 02:09:43 +00:00
dylib.lookupFunction<_GetVeilidStateC, _GetVeilidStateDart>(
2022-02-09 14:47:36 +00:00
'get_veilid_state'),
2022-02-14 02:09:43 +00:00
_changeApiLogLevel =
dylib.lookupFunction<_ChangeApiLogLevelC, _ChangeApiLogLevelDart>(
'change_api_log_level'),
_shutdownVeilidCore =
dylib.lookupFunction<_ShutdownVeilidCoreC, _ShutdownVeilidCoreDart>(
'shutdown_veilid_core'),
2022-03-04 01:45:39 +00:00
_debug = dylib.lookupFunction<_DebugC, _DebugDart>('debug'),
2022-02-14 02:09:43 +00:00
_veilidVersionString = dylib.lookupFunction<_VeilidVersionStringC,
_VeilidVersionStringDart>('veilid_version_string'),
2022-02-09 14:47:36 +00:00
_veilidVersion =
2022-02-14 02:09:43 +00:00
dylib.lookupFunction<_VeilidVersionC, _VeilidVersionDart>(
2022-02-09 14:47:36 +00:00
'veilid_version') {
// Get veilid_flutter initializer
var initializeVeilidFlutter = _dylib.lookupFunction<
2022-02-14 02:09:43 +00:00
_InitializeVeilidFlutterC,
_InitializeVeilidFlutterDart>('initialize_veilid_flutter');
2022-02-07 02:18:42 +00:00
initializeVeilidFlutter(NativeApi.postCObject);
2022-02-09 14:47:36 +00:00
}
@override
2022-02-10 02:40:01 +00:00
Stream<VeilidUpdate> startupVeilidCore(VeilidConfig config) {
2022-02-14 02:09:43 +00:00
var nativeConfig =
jsonEncode(config.json, toEncodable: veilidApiToEncodable)
.toNativeUtf8();
2022-02-10 02:40:01 +00:00
final recvPort = ReceivePort("startup_veilid_core");
final sendPort = recvPort.sendPort;
_startupVeilidCore(sendPort.nativePort, nativeConfig);
malloc.free(nativeConfig);
return processStreamJson(VeilidUpdate.fromJson, recvPort);
}
2022-02-07 02:18:42 +00:00
2022-02-09 14:47:36 +00:00
@override
Future<VeilidState> getVeilidState() async {
2022-02-10 02:40:01 +00:00
final recvPort = ReceivePort("get_veilid_state");
final sendPort = recvPort.sendPort;
2022-02-14 02:09:43 +00:00
_getVeilidState(sendPort.nativePort);
2022-03-09 03:32:12 +00:00
return processFutureJson(VeilidState.fromJson, recvPort.first);
2022-02-07 02:18:42 +00:00
}
2022-02-09 14:47:36 +00:00
@override
2022-02-10 02:40:01 +00:00
Future<void> changeApiLogLevel(VeilidConfigLogLevel logLevel) async {
var nativeLogLevel = logLevel.json.toNativeUtf8();
final recvPort = ReceivePort("change_api_log_level");
final sendPort = recvPort.sendPort;
_changeApiLogLevel(sendPort.nativePort, nativeLogLevel);
2022-02-09 14:47:36 +00:00
malloc.free(nativeLogLevel);
2022-03-09 03:32:12 +00:00
return processFutureVoid(recvPort.first);
2022-02-07 02:18:42 +00:00
}
2022-02-09 14:47:36 +00:00
@override
Future<void> shutdownVeilidCore() async {
2022-02-10 02:40:01 +00:00
final recvPort = ReceivePort("shutdown_veilid_core");
final sendPort = recvPort.sendPort;
_shutdownVeilidCore(sendPort.nativePort);
2022-03-09 03:32:12 +00:00
return processFutureVoid(recvPort.first);
2022-02-09 14:47:36 +00:00
}
2022-03-04 01:45:39 +00:00
@override
Future<String> debug(String command) async {
var nativeCommand = command.toNativeUtf8();
final recvPort = ReceivePort("debug");
final sendPort = recvPort.sendPort;
_debug(sendPort.nativePort, nativeCommand);
2022-03-09 03:32:12 +00:00
return processFuturePlain(recvPort.first);
2022-03-04 01:45:39 +00:00
}
2022-02-09 14:47:36 +00:00
@override
2022-02-07 02:18:42 +00:00
String veilidVersionString() {
2022-02-09 14:47:36 +00:00
final versionString = _veilidVersionString();
String ret = versionString.toDartString();
_freeString(versionString);
return ret;
2022-02-07 02:18:42 +00:00
}
2022-02-09 14:47:36 +00:00
@override
2022-02-07 02:18:42 +00:00
VeilidVersion veilidVersion() {
2022-02-09 14:47:36 +00:00
final version = _veilidVersion();
return VeilidVersion(
version.major,
version.minor,
version.patch,
);
2022-02-07 02:18:42 +00:00
}
}