Skip to content

Sync API proposal #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
openpgp: ce9352861c10d155d698d2b3bf78f8b6e04fb43a
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
openpgp: 937a8b18f330c97bf3612d6ab4ad5ccd89268e29

PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
2 changes: 1 addition & 1 deletion example/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
openpgp: 74f1193a4edb7b732b71576beca8d5bd3783a1b8
openpgp: aa2bcc0718d86e19ef9972022a60c5838750fa9a

PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
4 changes: 4 additions & 0 deletions example/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
44 changes: 37 additions & 7 deletions example/test/app_test.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import 'package:test/test.dart';
import 'package:flutter/foundation.dart';
import 'package:openpgp/openpgp.dart';
import 'package:openpgp/openpgp_sync.dart';
import 'package:test/test.dart';

void main() {
var keyOptions = KeyOptions()..rsaBits = 2048;
var options = Options()
..email = "sample@sample.com"
..keyOptions = keyOptions;

test('Generate Keypair', () async {
var keyOptions = KeyOptions()..rsaBits = 1024;
var keyPair = await OpenPGP.generate(
options: Options()
..email = "sample@sample.com"
..keyOptions = keyOptions);
var keyPair = await OpenPGP.generate(options: options);

expect(keyPair, isNotNull, reason: "Key pair generation failed.");
expect(keyPair.publicKey, isNotEmpty,
reason: "Public key should not be empty.");
expect(keyPair.privateKey, isNotEmpty,
reason: "Private key should not be empty.");
});

test('Encrypt/Decrypt', () async {
var keyPair = await OpenPGP.generate(options: options);

var encrypted = await OpenPGP.encrypt("hello", keyPair.publicKey);
var decrypted = await OpenPGP.decrypt(encrypted, keyPair.privateKey, "");
expect(decrypted, equals("hello"));
});

test('Generate Keypair Sync/Compute', () async {
final keyPair = await compute(
(options) => OpenPGPSync.generate(options: options),
options,
);

expect(keyPair, isNotNull, reason: "Key pair generation failed.");
expect(keyPair.publicKey, isNotEmpty,
reason: "Public key should not be empty.");
expect(keyPair.privateKey, isNotEmpty,
reason: "Private key should not be empty.");

print(keyPair.privateKey);
expect(keyPair.publicKey != "", true);
});
}
151 changes: 74 additions & 77 deletions lib/bridge/binding.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'dart:async';
import 'dart:ffi' as ffi;
import 'dart:ffi';
import 'dart:io' show Platform;
import 'dart:io';
import 'dart:isolate';
Expand All @@ -15,101 +15,98 @@ import 'package:path/path.dart' as Path;
class Binding {
static final String _callFuncName = 'OpenPGPBridgeCall';
static final String _libraryName = 'libopenpgp_bridge';
static final Binding _singleton = Binding._internal();
static final Binding _instance = Binding._internal();

late ffi.DynamicLibrary _library;
late final DynamicLibrary _library;

late final BridgeCallDart _bridgeCall;

factory Binding() {
return _singleton;
return _instance;
}

Binding._internal() {
_library = openLib();
_bridgeCall =
_library.lookupFunction<BridgeCallC, BridgeCallDart>(_callFuncName);
}

static void callBridge(IsolateArguments args) async {
var result = await Binding().call(args.name, args.payload);
@pragma('vm:entry-point')
static void _callBridge(IsolateArguments args) {
var result = _instance.call(args.name, args.payload);
args.port.send(result);
}

Future<Uint8List> callAsync(String name, Uint8List payload) async {
final port = ReceivePort('${_libraryName}_port');
final args = IsolateArguments(name, payload, port.sendPort);
final completer = new Completer<Uint8List>();

final isolate = await Isolate.spawn(
callBridge,
args,
errorsAreFatal: false,
debugName: '${_libraryName}_isolate',
onError: port.sendPort,
);

port.listen(
(message) async {
if (message is Uint8List) {
completer.complete(message);
} else if (message is List) {
completer.completeError(message.firstOrNull ?? "internal error");
} else {
completer.completeError(message ?? "spawn error");
final port = ReceivePort();
final completer = Completer<Uint8List>();

try {
final isolate = await Isolate.spawn(
_callBridge,
IsolateArguments(name, payload, port.sendPort),
errorsAreFatal: false,
debugName: '${_libraryName}_isolate',
onError: port.sendPort,
);

port.listen((message) {
try {
if (message is Uint8List) {
completer.complete(message);
} else if (message is List && message.isNotEmpty) {
completer.completeError(message.first ?? "internal error");
} else {
completer.completeError("spawn error");
}
} finally {
port.close();
isolate.kill(priority: Isolate.beforeNextEvent);
}
port.close();
},
onDone: () {
isolate.kill(priority: Isolate.beforeNextEvent);
},
);

return completer.future;
});

return completer.future;
} catch (e) {
port.close();
throw OpenPGPException("Failed to start isolate: $e");
}
}

Future<Uint8List> call(String name, Uint8List payload) async {
final callable = _library
.lookup<ffi.NativeFunction<call_func>>(_callFuncName)
.asFunction<Call>();
Uint8List call(String name, Uint8List payload) {
if (_bridgeCall == null) {
throw OpenPGPException(
"FFI function ${_callFuncName} is not initialized. Check library loading.");
}

final pointer = malloc<ffi.Uint8>(payload.length);
final namePointer = name.toNativeUtf8();
final payloadPointer = malloc.allocate<Uint8>(payload.length);
payloadPointer.asTypedList(payload.length).setAll(0, payload);

// https://github.com/dart-lang/ffi/issues/27
// https://github.com/objectbox/objectbox-dart/issues/69
for (var i = 0; i < payload.length; i++) {
pointer[i] = payload[i];
final result =
_bridgeCall(namePointer, payloadPointer.cast<Void>(), payload.length);
if (result.address == 0) {
throw OpenPGPException(
"FFI function ${_callFuncName} returned null pointer. Check openpgp-mobile implementation.");
}
final payloadPointer = pointer.cast<ffi.Void>();
final namePointer = toUtf8(name);

final result = callable(namePointer, payloadPointer, payload.length);

malloc.free(namePointer);
malloc.free(pointer);

handleError(result.ref.error, result);
malloc.free(payloadPointer);

final output =
result.ref.message.cast<ffi.Uint8>().asTypedList(result.ref.size);
handleError(result.ref.errorMessage, result);
final output = result.ref.toUint8List();
freeResult(result);

return output;
}

void handleError(
ffi.Pointer<Utf8> error, ffi.Pointer<FFIBytesReturn> result) {
if (error.address != ffi.nullptr.address) {
var message = fromUtf8(error);
void handleError(String? error, Pointer<BytesReturn> result) {
if (error != null && error.isNotEmpty) {
freeResult(result);
throw new OpenPGPException(message);
throw OpenPGPException(error);
}
}

ffi.Pointer<Utf8> toUtf8(String? text) {
return text == null ? "".toNativeUtf8() : text.toNativeUtf8();
}

String fromUtf8(ffi.Pointer<Utf8>? text) {
return text == null ? "" : text.toDartString();
}

void freeResult(ffi.Pointer<FFIBytesReturn> result) {
void freeResult(Pointer<BytesReturn> result) {
if (!Platform.isWindows) {
malloc.free(result);
}
Expand Down Expand Up @@ -143,7 +140,7 @@ class Binding {
}
}

ffi.DynamicLibrary openLib() {
DynamicLibrary openLib() {
var isFlutterTest = Platform.environment.containsKey('FLUTTER_TEST');

if (Platform.isMacOS || Platform.isIOS) {
Expand All @@ -153,13 +150,13 @@ class Binding {
var ffiFile = Path.join(
appDirectory.path, "Contents", "Frameworks", "$_libraryName.dylib");
validateTestFFIFile(ffiFile);
return ffi.DynamicLibrary.open(ffiFile);
return DynamicLibrary.open(ffiFile);
}
if (Platform.isMacOS) {
return ffi.DynamicLibrary.open("$_libraryName.dylib");
return DynamicLibrary.open("$_libraryName.dylib");
}
if (Platform.isIOS) {
return ffi.DynamicLibrary.process();
return DynamicLibrary.process();
}
}

Expand All @@ -170,24 +167,24 @@ class Binding {

var ffiFile = 'build/linux/$arch/debug/bundle/lib/$_libraryName.so';
validateTestFFIFile(ffiFile);
return ffi.DynamicLibrary.open(ffiFile);
return DynamicLibrary.open(ffiFile);
}

if (Platform.isLinux) {
try {
return ffi.DynamicLibrary.open("$_libraryName.so");
return DynamicLibrary.open("$_libraryName.so");
} catch (e) {
print(e);
var binary = File("/proc/self/cmdline").readAsStringSync();
var suggestedFile =
Path.join(Path.dirname(binary), "lib", "$_libraryName.so");
return ffi.DynamicLibrary.open(suggestedFile);
return DynamicLibrary.open(suggestedFile);
}
}

if (Platform.isAndroid) {
try {
return ffi.DynamicLibrary.open("$_libraryName.so");
return DynamicLibrary.open("$_libraryName.so");
} catch (e) {
print("fallback to open DynamicLibrary on older devices");
//fallback for devices that cannot load dynamic libraries by name: load the library with an absolute path
Expand All @@ -198,7 +195,7 @@ class Binding {
appid = String.fromCharCodes(
appid.codeUnits.where((element) => element != 0));
final loadPath = "/data/data/$appid/lib/$_libraryName.so";
return ffi.DynamicLibrary.open(loadPath);
return DynamicLibrary.open(loadPath);
}
}
}
Expand All @@ -211,9 +208,9 @@ class Binding {
var ffiFile = Path.canonicalize(Path.join(
r'build\windows', arch, r'runner\Debug', '$_libraryName.dll'));
validateTestFFIFile(ffiFile);
return ffi.DynamicLibrary.open(ffiFile);
return DynamicLibrary.open(ffiFile);
}
return ffi.DynamicLibrary.open("$_libraryName.dll");
return DynamicLibrary.open("$_libraryName.dll");
}

throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
Expand Down
19 changes: 8 additions & 11 deletions lib/bridge/binding_stub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ import 'dart:async';
import 'dart:typed_data';

class Binding {
static final Binding _singleton = Binding._internal();

factory Binding() {
return _singleton;
}
static final Binding _instance = Binding._internal();

Binding._internal();

Future<Uint8List> callAsync(String name, Uint8List payload) async {
return Uint8List.fromList(''.codeUnits);
}
factory Binding() => _instance;

Future<Uint8List> callAsync(String name, Uint8List payload) async =>
Uint8List(0);

Uint8List call(String name, Uint8List payload) => Uint8List(0);

bool isSupported() {
return false;
}
bool isSupported() => false;
}
Loading
Loading