Skip to content

Commit

Permalink
add SodiumSumo.runIsolated override (fixes #116)
Browse files Browse the repository at this point in the history
  • Loading branch information
Skycoder42 committed Aug 15, 2024
1 parent d15bbb2 commit 5b04d74
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 57 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/sodium_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions packages/sodium/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/sodium/dart_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
presets:
unit:
paths:
- test/unit

integration:
paths:
- test/integration
22 changes: 22 additions & 0 deletions packages/sodium/lib/src/api/sumo/sodium_sumo.dart
Original file line number Diff line number Diff line change
@@ -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<T> = FutureOr<T> Function(
SodiumSumo sodium,
List<SecureKey> secureKeys,
List<KeyPair> 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<T> runIsolated<T>(
SodiumSumoIsolateCallback<T> callback, {
List<SecureKey> secureKeys = const [],
List<KeyPair> keyPairs = const [],
});
}
57 changes: 40 additions & 17 deletions packages/sodium/lib/src/ffi/api/sodium_ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<TResult, TSodium extends SodiumFFI>
= FutureOr<TResult> Function(
TSodium sodium,
List<SecureKey> secureKeys,
List<KeyPair> keyPairs,
);

/// @nodoc
@internal
typedef SodiumFFIFactory<TSodiumFFI extends SodiumFFI> = Future<TSodiumFFI>
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<SodiumFFI> fromFactory(LibSodiumFFIFactory factory) async =>
Expand Down Expand Up @@ -133,26 +139,43 @@ class SodiumFFI implements Sodium {
SodiumIsolateCallback<T> callback, {
List<SecureKey> secureKeys = const [],
List<KeyPair> keyPairs = const [],
}) async {
final isolateResult = await _isolateRun(
}) async =>
await runIsolatedWithFactory<T, SodiumFFI>(
SodiumFFI.fromFactory,
callback,
secureKeys,
keyPairs,
);

@protected
Future<TResult> runIsolatedWithFactory<TResult, TSodiumFFI extends SodiumFFI>(
SodiumFFIFactory<TSodiumFFI> fromFactory,
SodiumFFIIsolateCallback<TResult, TSodiumFFI> callback,
List<SecureKey> secureKeys,
List<KeyPair> keyPairs,
) async {
final isolateResult = await _isolateRun<TResult, TSodiumFFI>(
_sodiumFactory,
fromFactory,
secureKeys.map(TransferableSecureKey.new).toList(),
keyPairs.map(TransferableKeyPair.new).toList(),
callback,
);
return isolateResult.extract(this);
}

static Future<IsolateResult<T>> _isolateRun<T>(
static Future<IsolateResult<TResult>>
_isolateRun<TResult, TSodiumFFI extends SodiumFFI>(
LibSodiumFFIFactory sodiumFactory,
SodiumFFIFactory<TSodiumFFI> fromFactory,
List<TransferableSecureKey> transferableSecureKeys,
List<TransferableKeyPair> transferableKeyPairs,
SodiumIsolateCallback<T> callback,
SodiumFFIIsolateCallback<TResult, TSodiumFFI> 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();
Expand All @@ -167,11 +190,11 @@ class SodiumFFI implements Sodium {
);

if (result is SecureKey) {
return IsolateResult<T>.key(TransferableSecureKey(result));
return IsolateResult<TResult>.key(TransferableSecureKey(result));
} else if (result is KeyPair) {
return IsolateResult<T>.keyPair(TransferableKeyPair(result));
return IsolateResult<TResult>.keyPair(TransferableKeyPair(result));
} else {
return IsolateResult<T>(result);
return IsolateResult<TResult>(result);
}
},
);
Expand Down
17 changes: 16 additions & 1 deletion packages/sodium/lib/src/ffi/api/sumo/sodium_sumo_ffi.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<SodiumSumoFFI> fromFactory(LibSodiumFFIFactory factory) async =>
Expand All @@ -22,4 +24,17 @@ class SodiumSumoFFI extends SodiumFFI implements SodiumSumo {
@override
// ignore: overridden_fields
late final CryptoSumo crypto = CryptoSumoFFI(sodium);

@override
Future<T> runIsolated<T>(
SodiumSumoIsolateCallback<T> callback, {
List<SecureKey> secureKeys = const [],
List<KeyPair> keyPairs = const [],
}) async =>
await runIsolatedWithFactory<T, SodiumSumoFFI>(
SodiumSumoFFI.fromFactory,
callback,
secureKeys,
keyPairs,
);
}
32 changes: 28 additions & 4 deletions packages/sodium/lib/src/js/api/sodium_js.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// ignore_for_file: unnecessary_lambdas

import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';

Expand All @@ -17,6 +18,15 @@ import 'crypto_js.dart';
import 'randombytes_js.dart';
import 'secure_key_js.dart';

/// @nodoc
@internal
typedef SodiumJSIsolateCallback<TResult, TSodium extends SodiumJS>
= FutureOr<TResult> Function(
TSodium sodium,
List<SecureKey> secureKeys,
List<KeyPair> keyPairs,
);

/// @nodoc
@internal
class SodiumJS implements Sodium {
Expand Down Expand Up @@ -66,17 +76,31 @@ class SodiumJS implements Sodium {
SodiumIsolateCallback<T> callback, {
List<SecureKey> secureKeys = const [],
List<KeyPair> keyPairs = const [],
}) async {
}) async =>
await runIsolatedWithInstance<T, SodiumJS>(
this,
callback,
secureKeys,
keyPairs,
);

@protected
Future<TResult> runIsolatedWithInstance<TResult, TSodiumJS extends SodiumJS>(
TSodiumJS sodium,
SodiumJSIsolateCallback<TResult, TSodiumJS> callback,
List<SecureKey> secureKeys,
List<KeyPair> 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);
}
}
15 changes: 15 additions & 0 deletions packages/sodium/lib/src/js/api/sumo/sodium_sumo_js.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,4 +16,17 @@ class SodiumSumoJS extends SodiumJS implements SodiumSumo {
@override
// ignore: overridden_fields
late final CryptoSumo crypto = CryptoSumoJS(sodium);

@override
Future<T> runIsolated<T>(
SodiumSumoIsolateCallback<T> callback, {
List<SecureKey> secureKeys = const [],
List<KeyPair> keyPairs = const [],
}) async =>
await runIsolatedWithInstance<T, SodiumSumoJS>(
this,
callback,
secureKeys,
keyPairs,
);
}
2 changes: 1 addition & 1 deletion packages/sodium/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
28 changes: 28 additions & 0 deletions packages/sodium/test/integration/cases/sodium_test_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
4 changes: 2 additions & 2 deletions packages/sodium/test/integration/test_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
Expand Down Expand Up @@ -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),
);
}
8 changes: 8 additions & 0 deletions packages/sodium/test/unit/ffi/api/sodium_ffi_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
12 changes: 11 additions & 1 deletion packages/sodium/test/unit/ffi/api/sumo/sodium_sumo_ffi_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
});
});
}
Loading

0 comments on commit 5b04d74

Please sign in to comment.