From 5b04d74104daade0ca1532ddce5c05b012dc331a Mon Sep 17 00:00:00 2001 From: Skycoder42 Date: Thu, 15 Aug 2024 18:25:18 +0200 Subject: [PATCH] add SodiumSumo.runIsolated override (fixes #116) --- .github/workflows/sodium_ci.yaml | 4 +- packages/sodium/CHANGELOG.md | 5 ++ packages/sodium/dart_test.yaml | 8 +++ .../sodium/lib/src/api/sumo/sodium_sumo.dart | 22 ++++++ .../sodium/lib/src/ffi/api/sodium_ffi.dart | 57 +++++++++++----- .../lib/src/ffi/api/sumo/sodium_sumo_ffi.dart | 17 ++++- packages/sodium/lib/src/js/api/sodium_js.dart | 32 +++++++-- .../lib/src/js/api/sumo/sodium_sumo_js.dart | 15 ++++ packages/sodium/pubspec.yaml | 2 +- .../integration/cases/sodium_test_case.dart | 28 ++++++++ .../sodium/test/integration/test_runner.dart | 4 +- .../test/unit/ffi/api/sodium_ffi_test.dart | 8 +++ .../ffi/api/sumo/sodium_sumo_ffi_test.dart | 12 +++- .../test/unit/js/api/sodium_js_test.dart | 68 +++++++++++-------- .../unit/js/api/sumo/sodium_sumo_js_test.dart | 10 +++ 15 files changed, 235 insertions(+), 57 deletions(-) create mode 100644 packages/sodium/dart_test.yaml diff --git a/.github/workflows/sodium_ci.yaml b/.github/workflows/sodium_ci.yaml index 893a9c08..7cdd9bb4 100644 --- a/.github/workflows/sodium_ci.yaml +++ b/.github/workflows/sodium_ci.yaml @@ -19,12 +19,12 @@ jobs: with: workingDirectory: packages/sodium buildRunner: true - unitTestPaths: test/unit + unitTestPaths: -P unit coverageExclude: >- "**/*.freezed.dart" "**/*.ffi.dart" "**/*.js.dart" - integrationTestPaths: test/integration + integrationTestPaths: -P integration integrationTestSetup: >- { "linux": "dart run tool/integration/setup_unix.dart linux", diff --git a/packages/sodium/CHANGELOG.md b/packages/sodium/CHANGELOG.md index 1f9c47ba..d29f6406 100644 --- a/packages/sodium/CHANGELOG.md +++ b/packages/sodium/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.1.0] - 2024-08-15 +### Fixed +- Added override for SodiumSumo.runIsolated that passes a SodiumSumo instance to the callback (#116) + ## [3.0.1] - 2024-08-15 ### Changed - Updated min required dart SDK to 3.5.0 @@ -206,6 +210,7 @@ changed, only the name of the getter. (#61) ### Added - Initial stable release +[3.1.0]: https://github.com/Skycoder42/libsodium_dart_bindings/compare/sodium-v3.0.1...sodium-v3.1.0 [3.0.1]: https://github.com/Skycoder42/libsodium_dart_bindings/compare/sodium-v3.0.0...sodium-v3.0.1 [3.0.0]: https://github.com/Skycoder42/libsodium_dart_bindings/compare/sodium-v2.3.1+1...sodium-v3.0.0 [2.3.1+1]: https://github.com/Skycoder42/libsodium_dart_bindings/compare/sodium-v2.3.1...sodium-v2.3.1+1 diff --git a/packages/sodium/dart_test.yaml b/packages/sodium/dart_test.yaml new file mode 100644 index 00000000..9e0873aa --- /dev/null +++ b/packages/sodium/dart_test.yaml @@ -0,0 +1,8 @@ +presets: + unit: + paths: + - test/unit + + integration: + paths: + - test/integration diff --git a/packages/sodium/lib/src/api/sumo/sodium_sumo.dart b/packages/sodium/lib/src/api/sumo/sodium_sumo.dart index 67780ef6..3b7ed7b9 100644 --- a/packages/sodium/lib/src/api/sumo/sodium_sumo.dart +++ b/packages/sodium/lib/src/api/sumo/sodium_sumo.dart @@ -1,10 +1,32 @@ +import 'dart:async'; + +import '../key_pair.dart'; +import '../secure_key.dart'; import '../sodium.dart'; import 'crypto_sumo.dart'; +/// A callback to be executed on a separate isolate. +/// +/// The callback receives a fresh [sodium] sumo instance that only lives on the +/// new isolate, as well as the [secureKeys] and [keyPairs] that have been +/// transferred to it via the [SodiumSumo.runIsolated] method. +typedef SodiumSumoIsolateCallback = FutureOr Function( + SodiumSumo sodium, + List secureKeys, + List keyPairs, +); + /// A meta class that provides access to all toplevel libsodium sumo API groups. abstract class SodiumSumo implements Sodium { const SodiumSumo._(); // coverage:ignore-line @override CryptoSumo get crypto; + + @override + Future runIsolated( + SodiumSumoIsolateCallback callback, { + List secureKeys = const [], + List keyPairs = const [], + }); } diff --git a/packages/sodium/lib/src/ffi/api/sodium_ffi.dart b/packages/sodium/lib/src/ffi/api/sodium_ffi.dart index 2f127dbc..12718bb2 100644 --- a/packages/sodium/lib/src/ffi/api/sodium_ffi.dart +++ b/packages/sodium/lib/src/ffi/api/sodium_ffi.dart @@ -26,22 +26,28 @@ import 'secure_key_ffi.dart'; /// @nodoc @internal -class SodiumFFI implements Sodium { - // coverage:ignore-start - static LibSodiumFFI _unsupportedFactory() => throw UnsupportedError( - 'Sodium instance was not created with isolate support. ' - 'Please use SodiumInit.initWithIsolates or ' - 'SodiumInit.initFromSodiumFFIWithIsolates to initialize your instance.', - ); - // coverage:ignore-end +typedef SodiumFFIIsolateCallback + = FutureOr Function( + TSodium sodium, + List secureKeys, + List keyPairs, +); + +/// @nodoc +@internal +typedef SodiumFFIFactory = Future + Function(LibSodiumFFIFactory factory); +/// @nodoc +@internal +class SodiumFFI implements Sodium { final LibSodiumFFIFactory _sodiumFactory; /// @nodoc final LibSodiumFFI sodium; /// @nodoc - SodiumFFI(this.sodium, [this._sodiumFactory = _unsupportedFactory]); + SodiumFFI(this.sodium, this._sodiumFactory); /// @nodoc static Future fromFactory(LibSodiumFFIFactory factory) async => @@ -133,9 +139,24 @@ class SodiumFFI implements Sodium { SodiumIsolateCallback callback, { List secureKeys = const [], List keyPairs = const [], - }) async { - final isolateResult = await _isolateRun( + }) async => + await runIsolatedWithFactory( + SodiumFFI.fromFactory, + callback, + secureKeys, + keyPairs, + ); + + @protected + Future runIsolatedWithFactory( + SodiumFFIFactory fromFactory, + SodiumFFIIsolateCallback callback, + List secureKeys, + List keyPairs, + ) async { + final isolateResult = await _isolateRun( _sodiumFactory, + fromFactory, secureKeys.map(TransferableSecureKey.new).toList(), keyPairs.map(TransferableKeyPair.new).toList(), callback, @@ -143,16 +164,18 @@ class SodiumFFI implements Sodium { return isolateResult.extract(this); } - static Future> _isolateRun( + static Future> + _isolateRun( LibSodiumFFIFactory sodiumFactory, + SodiumFFIFactory fromFactory, List transferableSecureKeys, List transferableKeyPairs, - SodiumIsolateCallback callback, + SodiumFFIIsolateCallback callback, ) async { final isolateResult = await Isolate.run( debugName: 'SodiumFFI.runIsolated', () async { - final sodium = await SodiumFFI.fromFactory(sodiumFactory); + final sodium = await fromFactory(sodiumFactory); final restoredSecureKeys = transferableSecureKeys .map((transferKey) => transferKey.toSecureKey(sodium)) .toList(); @@ -167,11 +190,11 @@ class SodiumFFI implements Sodium { ); if (result is SecureKey) { - return IsolateResult.key(TransferableSecureKey(result)); + return IsolateResult.key(TransferableSecureKey(result)); } else if (result is KeyPair) { - return IsolateResult.keyPair(TransferableKeyPair(result)); + return IsolateResult.keyPair(TransferableKeyPair(result)); } else { - return IsolateResult(result); + return IsolateResult(result); } }, ); diff --git a/packages/sodium/lib/src/ffi/api/sumo/sodium_sumo_ffi.dart b/packages/sodium/lib/src/ffi/api/sumo/sodium_sumo_ffi.dart index 4f151d63..0b566776 100644 --- a/packages/sodium/lib/src/ffi/api/sumo/sodium_sumo_ffi.dart +++ b/packages/sodium/lib/src/ffi/api/sumo/sodium_sumo_ffi.dart @@ -1,5 +1,7 @@ import 'package:meta/meta.dart'; +import '../../../api/key_pair.dart'; +import '../../../api/secure_key.dart'; import '../../../api/sumo/crypto_sumo.dart'; import '../../../api/sumo/sodium_sumo.dart'; import '../helpers/isolates/libsodiumffi_factory.dart'; @@ -10,7 +12,7 @@ import 'crypto_sumo_ffi.dart'; @internal class SodiumSumoFFI extends SodiumFFI implements SodiumSumo { /// @nodoc - SodiumSumoFFI(super.sodium, [super.sodiumFactory]); + SodiumSumoFFI(super.sodium, super._sodiumFactory); /// @nodoc static Future fromFactory(LibSodiumFFIFactory factory) async => @@ -22,4 +24,17 @@ class SodiumSumoFFI extends SodiumFFI implements SodiumSumo { @override // ignore: overridden_fields late final CryptoSumo crypto = CryptoSumoFFI(sodium); + + @override + Future runIsolated( + SodiumSumoIsolateCallback callback, { + List secureKeys = const [], + List keyPairs = const [], + }) async => + await runIsolatedWithFactory( + SodiumSumoFFI.fromFactory, + callback, + secureKeys, + keyPairs, + ); } diff --git a/packages/sodium/lib/src/js/api/sodium_js.dart b/packages/sodium/lib/src/js/api/sodium_js.dart index b5ae8b01..8a0e351f 100644 --- a/packages/sodium/lib/src/js/api/sodium_js.dart +++ b/packages/sodium/lib/src/js/api/sodium_js.dart @@ -1,5 +1,6 @@ // ignore_for_file: unnecessary_lambdas +import 'dart:async'; import 'dart:js_interop'; import 'dart:typed_data'; @@ -17,6 +18,15 @@ import 'crypto_js.dart'; import 'randombytes_js.dart'; import 'secure_key_js.dart'; +/// @nodoc +@internal +typedef SodiumJSIsolateCallback + = FutureOr Function( + TSodium sodium, + List secureKeys, + List keyPairs, +); + /// @nodoc @internal class SodiumJS implements Sodium { @@ -66,17 +76,31 @@ class SodiumJS implements Sodium { SodiumIsolateCallback callback, { List secureKeys = const [], List keyPairs = const [], - }) async { + }) async => + await runIsolatedWithInstance( + this, + callback, + secureKeys, + keyPairs, + ); + + @protected + Future runIsolatedWithInstance( + TSodiumJS sodium, + SodiumJSIsolateCallback callback, + List secureKeys, + List keyPairs, + ) async { // ignore: prefer_asserts_with_message assert(() { // ignore: avoid_print print( - 'WARNING: Sodium.runIsolated does not actually run use parallel ' - 'execution on the web. Code is run on the same thread.', + 'WARNING: Sodium.runIsolated does not actually use parallel ' + 'execution on the web. Use service workers if needed!', ); return true; }()); - return await callback(this, secureKeys, keyPairs); + return await callback(sodium, secureKeys, keyPairs); } } diff --git a/packages/sodium/lib/src/js/api/sumo/sodium_sumo_js.dart b/packages/sodium/lib/src/js/api/sumo/sodium_sumo_js.dart index 9d9953fc..f377590d 100644 --- a/packages/sodium/lib/src/js/api/sumo/sodium_sumo_js.dart +++ b/packages/sodium/lib/src/js/api/sumo/sodium_sumo_js.dart @@ -1,5 +1,7 @@ import 'package:meta/meta.dart'; +import '../../../api/key_pair.dart'; +import '../../../api/secure_key.dart'; import '../../../api/sumo/crypto_sumo.dart'; import '../../../api/sumo/sodium_sumo.dart'; import '../sodium_js.dart'; @@ -14,4 +16,17 @@ class SodiumSumoJS extends SodiumJS implements SodiumSumo { @override // ignore: overridden_fields late final CryptoSumo crypto = CryptoSumoJS(sodium); + + @override + Future runIsolated( + SodiumSumoIsolateCallback callback, { + List secureKeys = const [], + List keyPairs = const [], + }) async => + await runIsolatedWithInstance( + this, + callback, + secureKeys, + keyPairs, + ); } diff --git a/packages/sodium/pubspec.yaml b/packages/sodium/pubspec.yaml index 85605a81..47423805 100644 --- a/packages/sodium/pubspec.yaml +++ b/packages/sodium/pubspec.yaml @@ -1,6 +1,6 @@ name: sodium description: Dart bindings for libsodium, for the Dart-VM and for the Web -version: 3.0.1 +version: 3.1.0 homepage: https://github.com/Skycoder42/libsodium_dart_bindings environment: diff --git a/packages/sodium/test/integration/cases/sodium_test_case.dart b/packages/sodium/test/integration/cases/sodium_test_case.dart index 9ccdaa8f..76700e00 100644 --- a/packages/sodium/test/integration/cases/sodium_test_case.dart +++ b/packages/sodium/test/integration/cases/sodium_test_case.dart @@ -186,5 +186,33 @@ class SodiumTestCase extends TestCase { expect(plain1, message); }); + + testSumo('runIsolated', (sodium) async { + final secureKey = sodium.crypto.secretBox.keygen(); + final keyPair = sodium.crypto.box.keyPair(); + + final result = await sodium.runIsolated( + secureKeys: [secureKey], + keyPairs: [keyPair], + (sodium, secureKeys, keyPairs) { + final [secureKey] = secureKeys; + final [keyPair] = keyPairs; + + final base = sodium.crypto.scalarmult.base(n: secureKey); + + return sodium.crypto.scalarmult.call( + n: keyPair.secretKey, + p: base, + ); + }, + ); + + final expected = sodium.crypto.scalarmult.call( + n: secureKey, + p: keyPair.publicKey, + ); + + expect(result, expected); + }); } } diff --git a/packages/sodium/test/integration/test_runner.dart b/packages/sodium/test/integration/test_runner.dart index b8c9c8ef..67f8f81a 100644 --- a/packages/sodium/test/integration/test_runner.dart +++ b/packages/sodium/test/integration/test_runner.dart @@ -87,7 +87,7 @@ abstract class TestRunner { @visibleForOverriding void testSumo(String description, dynamic Function(SodiumSumo sodium) body) => t.test( - description, + '[sumo] $description', () => t.fail('This test only works with the sodium.js sumo variant'), skip: 'This test only works with the sodium.js sumo variant', ); @@ -132,7 +132,7 @@ abstract class SumoTestRunner extends TestRunner { @visibleForOverriding void testSumo(String description, dynamic Function(SodiumSumo sodium) body) => t.test( - description, + '[sumo] $description', () => body(sodium), ); } diff --git a/packages/sodium/test/unit/ffi/api/sodium_ffi_test.dart b/packages/sodium/test/unit/ffi/api/sodium_ffi_test.dart index ccdc8e49..128ec7b6 100644 --- a/packages/sodium/test/unit/ffi/api/sodium_ffi_test.dart +++ b/packages/sodium/test/unit/ffi/api/sodium_ffi_test.dart @@ -266,6 +266,14 @@ void main() { expect(callbackIsolateName, isNot(currentIsolateName)); }); + test('invokes the given callback with a sodium instance', () async { + final isSodium = await sut.runIsolated( + (sodium, secureKeys, keyPairs) => sodium is SodiumFFI, + ); + + expect(isSodium, isTrue); + }); + test('passes over keys via the transferable secure key', () async { mockAllocArray(mockSodium); diff --git a/packages/sodium/test/unit/ffi/api/sumo/sodium_sumo_ffi_test.dart b/packages/sodium/test/unit/ffi/api/sumo/sodium_sumo_ffi_test.dart index ded5b29f..9ef80fa2 100644 --- a/packages/sodium/test/unit/ffi/api/sumo/sodium_sumo_ffi_test.dart +++ b/packages/sodium/test/unit/ffi/api/sumo/sodium_sumo_ffi_test.dart @@ -25,7 +25,7 @@ void main() { setUp(() { reset(mockSodium); - sut = SodiumSumoFFI(mockSodium); + sut = SodiumSumoFFI(mockSodium, () => mockSodium); }); test('fromFactory returns instance created by the factory', () async { @@ -43,4 +43,14 @@ void main() { ), ); }); + + group('runIsolated', () { + test('invokes the given callback with a sodium sumo instance', () async { + final isSodiumSumo = await sut.runIsolated( + (sodium, secureKeys, keyPairs) => sodium is SodiumSumoFFI, + ); + + expect(isSodiumSumo, isTrue); + }); + }); } diff --git a/packages/sodium/test/unit/js/api/sodium_js_test.dart b/packages/sodium/test/unit/js/api/sodium_js_test.dart index 3d74e39c..c7a065c8 100644 --- a/packages/sodium/test/unit/js/api/sodium_js_test.dart +++ b/packages/sodium/test/unit/js/api/sodium_js_test.dart @@ -156,36 +156,46 @@ void main() { ); }); - test('runIsolated prints warning and runs callback synchronously', () async { - final secureKey = SecureKeyJS( - mockSodium.asLibSodiumJS, - Uint8List.fromList(List.filled(10, 10)).toJS, - ); - final keyPair = KeyPair( - publicKey: Uint8List.fromList(List.filled(20, 20)), - secretKey: SecureKeyJS( + group('runIsolated', () { + test('invokes the given callback with a sodium instance', () async { + final isSodium = await sut.runIsolated( + (sodium, secureKeys, keyPairs) => sodium is SodiumJS, + ); + + expect(isSodium, isTrue); + }); + + test('prints warning and runs callback synchronously', () async { + final secureKey = SecureKeyJS( mockSodium.asLibSodiumJS, - Uint8List.fromList(List.filled(30, 30)).toJS, - ), - ); + Uint8List.fromList(List.filled(10, 10)).toJS, + ); + final keyPair = KeyPair( + publicKey: Uint8List.fromList(List.filled(20, 20)), + secretKey: SecureKeyJS( + mockSodium.asLibSodiumJS, + Uint8List.fromList(List.filled(30, 30)).toJS, + ), + ); - expect( - () async { - final result = await sut.runIsolated( - secureKeys: [secureKey], - keyPairs: [keyPair], - (sodium, secureKeys, keyPairs) { - expect(secureKeys, hasLength(1)); - expect(secureKeys.single, same(secureKey)); - expect(keyPairs, hasLength(1)); - expect(keyPairs.single, same(keyPair)); - return secureKeys.single; - }, - ); - - expect(result, same(secureKey)); - }, - prints(startsWith('WARNING: Sodium.runIsolated')), - ); + expect( + () async { + final result = await sut.runIsolated( + secureKeys: [secureKey], + keyPairs: [keyPair], + (sodium, secureKeys, keyPairs) { + expect(secureKeys, hasLength(1)); + expect(secureKeys.single, same(secureKey)); + expect(keyPairs, hasLength(1)); + expect(keyPairs.single, same(keyPair)); + return secureKeys.single; + }, + ); + + expect(result, same(secureKey)); + }, + prints(startsWith('WARNING: Sodium.runIsolated')), + ); + }); }); } diff --git a/packages/sodium/test/unit/js/api/sumo/sodium_sumo_js_test.dart b/packages/sodium/test/unit/js/api/sumo/sodium_sumo_js_test.dart index aa0cada8..e67e92e1 100644 --- a/packages/sodium/test/unit/js/api/sumo/sodium_sumo_js_test.dart +++ b/packages/sodium/test/unit/js/api/sumo/sodium_sumo_js_test.dart @@ -35,4 +35,14 @@ void main() { ), ); }); + + group('runIsolated', () { + test('invokes the given callback with a sodium sumo instance', () async { + final isSodium = await sut.runIsolated( + (sodium, secureKeys, keyPairs) => sodium is SodiumSumoJS, + ); + + expect(isSodium, isTrue); + }); + }); }