Compare commits

...

9 Commits

92 changed files with 12504 additions and 407 deletions

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"chat.tools.terminal.autoApprove": {
"cargo check": true,
"flutter_rust_bridge_codegen": true,
"rustup": true
}
}

View File

@ -18,7 +18,7 @@ This repository contains:
* A small Rust bridge crate referencing ``ccc_rust`` via a git tag
* Generated ``flutter_rust_bridge`` bindings
* A Flutter plugin scaffold (iOS / Android / macOS)
* A Flutter plugin scaffold (iOS / Android / macOS / Linux / Windows)
* A clean Dart API surface for applications
This plugin does **not** implement cryptography itself.

View File

@ -1,7 +1,7 @@
// The Android Gradle Plugin builds the native code with the Android NDK.
group = "com.example.ccc_cryptography"
version = "1.0"
group 'com.flutter_rust_bridge.ccc_cryptography'
version '1.0'
buildscript {
repositories {
@ -11,7 +11,7 @@ buildscript {
dependencies {
// The Android Gradle Plugin knows how to build native code with the NDK.
classpath("com.android.tools.build:gradle:8.11.1")
classpath 'com.android.tools.build:gradle:7.3.0'
}
}
@ -22,42 +22,35 @@ rootProject.allprojects {
}
}
apply plugin: "com.android.library"
apply plugin: 'com.android.library'
android {
namespace = "com.example.ccc_cryptography"
if (project.android.hasProperty("namespace")) {
namespace 'com.flutter_rust_bridge.ccc_cryptography'
}
// Bumping the plugin compileSdk version requires all clients of this plugin
// Bumping the plugin compileSdkVersion requires all clients of this plugin
// to bump the version in their app.
compileSdk = 36
compileSdkVersion 33
// Use the NDK version
// declared in /android/app/build.gradle file of the Flutter project.
// Replace it with a version number if this plugin requires a specific NDK version.
// Replace it with a version number if this plugin requires a specfic NDK version.
// (e.g. ndkVersion "23.1.7779620")
ndkVersion = android.ndkVersion
// Invoke the shared CMake build with the Android Gradle Plugin.
externalNativeBuild {
cmake {
path = "../src/CMakeLists.txt"
// The default CMake version for the Android Gradle Plugin is 3.10.2.
// https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
//
// The Flutter tooling requires that developers have CMake 3.10 or later
// installed. You should not increase this version, as doing so will cause
// the plugin to fail to compile for some customers of the plugin.
// version "3.10.2"
}
}
ndkVersion android.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdk = 24
minSdkVersion 19
}
}
apply from: "../cargokit/gradle/plugin.gradle"
cargokit {
manifestDir = "../rust"
libname = "ccc_cryptography"
}

View File

@ -1,3 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ccc_cryptography">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

4
cargokit/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target
.dart_tool
*.iml
!pubspec.lock

42
cargokit/LICENSE Normal file
View File

@ -0,0 +1,42 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
Copyright 2022 Matej Knopp
================================================================================
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
APACHE LICENSE, VERSION 2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

11
cargokit/README Normal file
View File

@ -0,0 +1,11 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
Experimental repository to provide glue for seamlessly integrating cargo build
with flutter plugins and packages.
See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/
for a tutorial on how to use Cargokit.
Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin.

58
cargokit/build_pod.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/sh
set -e
BASEDIR=$(dirname "$0")
# Workaround for https://github.com/dart-lang/pub/issues/4010
BASEDIR=$(cd "$BASEDIR" ; pwd -P)
# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project
NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"`
export PATH=${NEW_PATH%?} # remove trailing :
env
# Platform name (macosx, iphoneos, iphonesimulator)
export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME
# Arctive architectures (arm64, armv7, x86_64), space separated.
export CARGOKIT_DARWIN_ARCHS=$ARCHS
# Current build configuration (Debug, Release)
export CARGOKIT_CONFIGURATION=$CONFIGURATION
# Path to directory containing Cargo.toml.
export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1
# Temporary directory for build artifacts.
export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR
# Output directory for final artifacts.
export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME
# Directory to store built tool artifacts.
export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool
# Directory inside root project. Not necessarily the top level directory of root project.
export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT
FLUTTER_EXPORT_BUILD_ENVIRONMENT=(
"$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS
"$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS
)
for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}"
do
if [[ -f "$path" ]]; then
source "$path"
fi
done
sh "$BASEDIR/run_build_tool.sh" build-pod "$@"
# Make a symlink from built framework to phony file, which will be used as input to
# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate
# attribute on custom build phase)
ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony"
ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out"

View File

@ -0,0 +1,5 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
A sample command-line application with an entrypoint in `bin/`, library code
in `lib/`, and example unit test in `test/`.

View File

@ -0,0 +1,34 @@
# This is copied from Cargokit (which is the official way to use it currently)
# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
linter:
rules:
- prefer_relative_imports
- directives_ordering
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,8 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'package:build_tool/build_tool.dart' as build_tool;
void main(List<String> arguments) {
build_tool.runMain(arguments);
}

View File

@ -0,0 +1,8 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'src/build_tool.dart' as build_tool;
Future<void> runMain(List<String> args) async {
return build_tool.runMain(args);
}

View File

@ -0,0 +1,211 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'dart:isolate';
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
import 'package:version/version.dart';
import 'target.dart';
import 'util.dart';
class AndroidEnvironment {
AndroidEnvironment({
required this.sdkPath,
required this.ndkVersion,
required this.minSdkVersion,
required this.targetTempDir,
required this.target,
});
static void clangLinkerWrapper(List<String> args) {
final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG'];
if (clang == null) {
throw Exception(
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var");
}
final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET'];
if (target == null) {
throw Exception(
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var");
}
runCommand(clang, [
target,
...args,
]);
}
/// Full path to Android SDK.
final String sdkPath;
/// Full version of Android NDK.
final String ndkVersion;
/// Minimum supported SDK version.
final int minSdkVersion;
/// Target directory for build artifacts.
final String targetTempDir;
/// Target being built.
final Target target;
bool ndkIsInstalled() {
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
final ndkPackageXml = File(path.join(ndkPath, 'package.xml'));
return ndkPackageXml.existsSync();
}
void installNdk({
required String javaHome,
}) {
final sdkManagerExtension = Platform.isWindows ? '.bat' : '';
final sdkManager = path.join(
sdkPath,
'cmdline-tools',
'latest',
'bin',
'sdkmanager$sdkManagerExtension',
);
log.info('Installing NDK $ndkVersion');
runCommand(sdkManager, [
'--install',
'ndk;$ndkVersion',
], environment: {
'JAVA_HOME': javaHome,
});
}
Future<Map<String, String>> buildEnvironment() async {
final hostArch = Platform.isMacOS
? "darwin-x86_64"
: (Platform.isLinux ? "linux-x86_64" : "windows-x86_64");
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
final toolchainPath = path.join(
ndkPath,
'toolchains',
'llvm',
'prebuilt',
hostArch,
'bin',
);
final minSdkVersion =
math.max(target.androidMinSdkVersion!, this.minSdkVersion);
final exe = Platform.isWindows ? '.exe' : '';
final arKey = 'AR_${target.rust}';
final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe']
.map((e) => path.join(toolchainPath, e))
.firstWhereOrNull((element) => File(element).existsSync());
if (arValue == null) {
throw Exception('Failed to find ar for $target in $toolchainPath');
}
final targetArg = '--target=${target.rust}$minSdkVersion';
final ccKey = 'CC_${target.rust}';
final ccValue = path.join(toolchainPath, 'clang$exe');
final cfFlagsKey = 'CFLAGS_${target.rust}';
final cFlagsValue = targetArg;
final cxxKey = 'CXX_${target.rust}';
final cxxValue = path.join(toolchainPath, 'clang++$exe');
final cxxFlagsKey = 'CXXFLAGS_${target.rust}';
final cxxFlagsValue = targetArg;
final linkerKey =
'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase();
final ranlibKey = 'RANLIB_${target.rust}';
final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe');
// Tell bindgen (libclang) where to find Android system headers like
// <pthread.h>. Without this, cross-compilation of crates that use
// bindgen will fail with "fatal error: 'pthread.h' file not found".
final sysroot = path.join(
ndkPath,
'toolchains',
'llvm',
'prebuilt',
hostArch,
'sysroot',
);
final bindgenKey =
'BINDGEN_EXTRA_CLANG_ARGS_${target.rust.replaceAll('-', '_')}';
final bindgenValue = '--sysroot=$sysroot $targetArg';
final ndkVersionParsed = Version.parse(ndkVersion);
final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS';
final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed);
final runRustTool =
Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh';
final packagePath = (await Isolate.resolvePackageUri(
Uri.parse('package:build_tool/buildtool.dart')))!
.toFilePath();
final selfPath = path.canonicalize(path.join(
packagePath,
'..',
'..',
'..',
runRustTool,
));
// Make sure that run_build_tool is working properly even initially launched directly
// through dart run.
final toolTempDir =
Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir;
return {
arKey: arValue,
ccKey: ccValue,
cfFlagsKey: cFlagsValue,
cxxKey: cxxValue,
cxxFlagsKey: cxxFlagsValue,
ranlibKey: ranlibValue,
rustFlagsKey: rustFlagsValue,
linkerKey: selfPath,
bindgenKey: bindgenValue,
// Recognized by main() so we know when we're acting as a wrapper
'_CARGOKIT_NDK_LINK_TARGET': targetArg,
'_CARGOKIT_NDK_LINK_CLANG': ccValue,
'CARGOKIT_TOOL_TEMP_DIR': toolTempDir,
};
}
// Workaround for libgcc missing in NDK23, inspired by cargo-ndk
String _libGccWorkaround(String buildDir, Version ndkVersion) {
final workaroundDir = path.join(
buildDir,
'cargokit',
'libgcc_workaround',
'${ndkVersion.major}',
);
Directory(workaroundDir).createSync(recursive: true);
if (ndkVersion.major >= 23) {
File(path.join(workaroundDir, 'libgcc.a'))
.writeAsStringSync('INPUT(-lunwind)');
} else {
// Other way around, untested, forward libgcc.a from libunwind once Rust
// gets updated for NDK23+.
File(path.join(workaroundDir, 'libunwind.a'))
.writeAsStringSync('INPUT(-lgcc)');
}
var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? '';
if (rustFlags.isNotEmpty) {
rustFlags = '$rustFlags\x1f';
}
rustFlags = '$rustFlags-L\x1f$workaroundDir';
return rustFlags;
}
}

View File

@ -0,0 +1,266 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:http/http.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'builder.dart';
import 'crate_hash.dart';
import 'options.dart';
import 'precompile_binaries.dart';
import 'rustup.dart';
import 'target.dart';
class Artifact {
/// File system location of the artifact.
final String path;
/// Actual file name that the artifact should have in destination folder.
final String finalFileName;
AritifactType get type {
if (finalFileName.endsWith('.dll') ||
finalFileName.endsWith('.dll.lib') ||
finalFileName.endsWith('.pdb') ||
finalFileName.endsWith('.so') ||
finalFileName.endsWith('.dylib')) {
return AritifactType.dylib;
} else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) {
return AritifactType.staticlib;
} else {
throw Exception('Unknown artifact type for $finalFileName');
}
}
Artifact({
required this.path,
required this.finalFileName,
});
}
final _log = Logger('artifacts_provider');
class ArtifactProvider {
ArtifactProvider({
required this.environment,
required this.userOptions,
});
final BuildEnvironment environment;
final CargokitUserOptions userOptions;
Future<Map<Target, List<Artifact>>> getArtifacts(List<Target> targets) async {
final result = await _getPrecompiledArtifacts(targets);
final pendingTargets = List.of(targets);
pendingTargets.removeWhere((element) => result.containsKey(element));
if (pendingTargets.isEmpty) {
return result;
}
final rustup = Rustup();
for (final target in targets) {
final builder = RustBuilder(target: target, environment: environment);
builder.prepare(rustup);
_log.info('Building ${environment.crateInfo.packageName} for $target');
final targetDir = await builder.build();
// For local build accept both static and dynamic libraries.
final artifactNames = <String>{
...getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
aritifactType: AritifactType.dylib,
remote: false,
),
...getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
aritifactType: AritifactType.staticlib,
remote: false,
)
};
final artifacts = artifactNames
.map((artifactName) => Artifact(
path: path.join(targetDir, artifactName),
finalFileName: artifactName,
))
.where((element) => File(element.path).existsSync())
.toList();
result[target] = artifacts;
}
return result;
}
Future<Map<Target, List<Artifact>>> _getPrecompiledArtifacts(
List<Target> targets) async {
if (userOptions.usePrecompiledBinaries == false) {
_log.info('Precompiled binaries are disabled');
return {};
}
if (environment.crateOptions.precompiledBinaries == null) {
_log.fine('Precompiled binaries not enabled for this crate');
return {};
}
final start = Stopwatch()..start();
final crateHash = CrateHash.compute(environment.manifestDir,
tempStorage: environment.targetTempDir);
_log.fine(
'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms');
final downloadedArtifactsDir =
path.join(environment.targetTempDir, 'precompiled', crateHash);
Directory(downloadedArtifactsDir).createSync(recursive: true);
final res = <Target, List<Artifact>>{};
for (final target in targets) {
final requiredArtifacts = getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
remote: true,
);
final artifactsForTarget = <Artifact>[];
for (final artifact in requiredArtifacts) {
final fileName = PrecompileBinaries.fileName(target, artifact);
final downloadedPath = path.join(downloadedArtifactsDir, fileName);
if (!File(downloadedPath).existsSync()) {
final signatureFileName =
PrecompileBinaries.signatureFileName(target, artifact);
await _tryDownloadArtifacts(
crateHash: crateHash,
fileName: fileName,
signatureFileName: signatureFileName,
finalPath: downloadedPath,
);
}
if (File(downloadedPath).existsSync()) {
artifactsForTarget.add(Artifact(
path: downloadedPath,
finalFileName: artifact,
));
} else {
break;
}
}
// Only provide complete set of artifacts.
if (artifactsForTarget.length == requiredArtifacts.length) {
_log.fine('Found precompiled artifacts for $target');
res[target] = artifactsForTarget;
}
}
return res;
}
static Future<Response> _get(Uri url, {Map<String, String>? headers}) async {
int attempt = 0;
const maxAttempts = 10;
while (true) {
try {
return await get(url, headers: headers);
} on SocketException catch (e) {
// Try to detect reset by peer error and retry.
if (attempt++ < maxAttempts &&
(e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) {
_log.severe(
'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...');
await Future.delayed(Duration(seconds: 1));
continue;
} else {
rethrow;
}
}
}
}
Future<void> _tryDownloadArtifacts({
required String crateHash,
required String fileName,
required String signatureFileName,
required String finalPath,
}) async {
final precompiledBinaries = environment.crateOptions.precompiledBinaries!;
final prefix = precompiledBinaries.uriPrefix;
final url = Uri.parse('$prefix$crateHash/$fileName');
final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName');
_log.fine('Downloading signature from $signatureUrl');
final signature = await _get(signatureUrl);
if (signature.statusCode == 404) {
_log.warning(
'Precompiled binaries not available for crate hash $crateHash ($fileName)');
return;
}
if (signature.statusCode != 200) {
_log.severe(
'Failed to download signature $signatureUrl: status ${signature.statusCode}');
return;
}
_log.fine('Downloading binary from $url');
final res = await _get(url);
if (res.statusCode != 200) {
_log.severe('Failed to download binary $url: status ${res.statusCode}');
return;
}
if (verify(
precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) {
File(finalPath).writeAsBytesSync(res.bodyBytes);
} else {
_log.shout('Signature verification failed! Ignoring binary.');
}
}
}
enum AritifactType {
staticlib,
dylib,
}
AritifactType artifactTypeForTarget(Target target) {
if (target.darwinPlatform != null) {
return AritifactType.staticlib;
} else {
return AritifactType.dylib;
}
}
List<String> getArtifactNames({
required Target target,
required String libraryName,
required bool remote,
AritifactType? aritifactType,
}) {
aritifactType ??= artifactTypeForTarget(target);
if (target.darwinArch != null) {
if (aritifactType == AritifactType.staticlib) {
return ['lib$libraryName.a'];
} else {
return ['lib$libraryName.dylib'];
}
} else if (target.rust.contains('-windows-')) {
if (aritifactType == AritifactType.staticlib) {
return ['$libraryName.lib'];
} else {
return [
'$libraryName.dll',
'$libraryName.dll.lib',
if (!remote) '$libraryName.pdb'
];
}
} else if (target.rust.contains('-linux-')) {
if (aritifactType == AritifactType.staticlib) {
return ['lib$libraryName.a'];
} else {
return ['lib$libraryName.so'];
}
} else {
throw Exception("Unsupported target: ${target.rust}");
}
}

View File

@ -0,0 +1,40 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:path/path.dart' as path;
import 'artifacts_provider.dart';
import 'builder.dart';
import 'environment.dart';
import 'options.dart';
import 'target.dart';
class BuildCMake {
final CargokitUserOptions userOptions;
BuildCMake({required this.userOptions});
Future<void> build() async {
final targetPlatform = Environment.targetPlatform;
final target = Target.forFlutterName(Environment.targetPlatform);
if (target == null) {
throw Exception("Unknown target platform: $targetPlatform");
}
final environment = BuildEnvironment.fromEnvironment(isAndroid: false);
final provider =
ArtifactProvider(environment: environment, userOptions: userOptions);
final artifacts = await provider.getArtifacts([target]);
final libs = artifacts[target]!;
for (final lib in libs) {
if (lib.type == AritifactType.dylib) {
File(lib.path)
.copySync(path.join(Environment.outputDir, lib.finalFileName));
}
}
}
}

View File

@ -0,0 +1,49 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'artifacts_provider.dart';
import 'builder.dart';
import 'environment.dart';
import 'options.dart';
import 'target.dart';
final log = Logger('build_gradle');
class BuildGradle {
BuildGradle({required this.userOptions});
final CargokitUserOptions userOptions;
Future<void> build() async {
final targets = Environment.targetPlatforms.map((arch) {
final target = Target.forFlutterName(arch);
if (target == null) {
throw Exception(
"Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}");
}
return target;
}).toList();
final environment = BuildEnvironment.fromEnvironment(isAndroid: true);
final provider =
ArtifactProvider(environment: environment, userOptions: userOptions);
final artifacts = await provider.getArtifacts(targets);
for (final target in targets) {
final libs = artifacts[target]!;
final outputDir = path.join(Environment.outputDir, target.android!);
Directory(outputDir).createSync(recursive: true);
for (final lib in libs) {
if (lib.type == AritifactType.dylib) {
File(lib.path).copySync(path.join(outputDir, lib.finalFileName));
}
}
}
}
}

View File

@ -0,0 +1,89 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:path/path.dart' as path;
import 'artifacts_provider.dart';
import 'builder.dart';
import 'environment.dart';
import 'options.dart';
import 'target.dart';
import 'util.dart';
class BuildPod {
BuildPod({required this.userOptions});
final CargokitUserOptions userOptions;
Future<void> build() async {
final targets = Environment.darwinArchs.map((arch) {
final target = Target.forDarwin(
platformName: Environment.darwinPlatformName, darwinAarch: arch);
if (target == null) {
throw Exception(
"Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}");
}
return target;
}).toList();
final environment = BuildEnvironment.fromEnvironment(isAndroid: false);
final provider =
ArtifactProvider(environment: environment, userOptions: userOptions);
final artifacts = await provider.getArtifacts(targets);
void performLipo(String targetFile, Iterable<String> sourceFiles) {
runCommand("lipo", [
'-create',
...sourceFiles,
'-output',
targetFile,
]);
}
final outputDir = Environment.outputDir;
Directory(outputDir).createSync(recursive: true);
final staticLibs = artifacts.values
.expand((element) => element)
.where((element) => element.type == AritifactType.staticlib)
.toList();
final dynamicLibs = artifacts.values
.expand((element) => element)
.where((element) => element.type == AritifactType.dylib)
.toList();
final libName = environment.crateInfo.packageName;
// If there is static lib, use it and link it with pod
if (staticLibs.isNotEmpty) {
final finalTargetFile = path.join(outputDir, "lib$libName.a");
performLipo(finalTargetFile, staticLibs.map((e) => e.path));
} else {
// Otherwise try to replace bundle dylib with our dylib
final bundlePaths = [
'$libName.framework/Versions/A/$libName',
'$libName.framework/$libName',
];
for (final bundlePath in bundlePaths) {
final targetFile = path.join(outputDir, bundlePath);
if (File(targetFile).existsSync()) {
performLipo(targetFile, dynamicLibs.map((e) => e.path));
// Replace absolute id with @rpath one so that it works properly
// when moved to Frameworks.
runCommand("install_name_tool", [
'-id',
'@rpath/$bundlePath',
targetFile,
]);
return;
}
}
throw Exception('Unable to find bundle for dynamic library');
}
}
}

View File

@ -0,0 +1,271 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:github/github.dart';
import 'package:hex/hex.dart';
import 'package:logging/logging.dart';
import 'android_environment.dart';
import 'build_cmake.dart';
import 'build_gradle.dart';
import 'build_pod.dart';
import 'logging.dart';
import 'options.dart';
import 'precompile_binaries.dart';
import 'target.dart';
import 'util.dart';
import 'verify_binaries.dart';
final log = Logger('build_tool');
abstract class BuildCommand extends Command {
Future<void> runBuildCommand(CargokitUserOptions options);
@override
Future<void> run() async {
final options = CargokitUserOptions.load();
if (options.verboseLogging ||
Platform.environment['CARGOKIT_VERBOSE'] == '1') {
enableVerboseLogging();
}
await runBuildCommand(options);
}
}
class BuildPodCommand extends BuildCommand {
@override
final name = 'build-pod';
@override
final description = 'Build cocoa pod library';
@override
Future<void> runBuildCommand(CargokitUserOptions options) async {
final build = BuildPod(userOptions: options);
await build.build();
}
}
class BuildGradleCommand extends BuildCommand {
@override
final name = 'build-gradle';
@override
final description = 'Build android library';
@override
Future<void> runBuildCommand(CargokitUserOptions options) async {
final build = BuildGradle(userOptions: options);
await build.build();
}
}
class BuildCMakeCommand extends BuildCommand {
@override
final name = 'build-cmake';
@override
final description = 'Build CMake library';
@override
Future<void> runBuildCommand(CargokitUserOptions options) async {
final build = BuildCMake(userOptions: options);
await build.build();
}
}
class GenKeyCommand extends Command {
@override
final name = 'gen-key';
@override
final description = 'Generate key pair for signing precompiled binaries';
@override
void run() {
final kp = generateKey();
final private = HEX.encode(kp.privateKey.bytes);
final public = HEX.encode(kp.publicKey.bytes);
print("Private Key: $private");
print("Public Key: $public");
}
}
class PrecompileBinariesCommand extends Command {
PrecompileBinariesCommand() {
argParser
..addOption(
'repository',
mandatory: true,
help: 'Github repository slug in format owner/name',
)
..addOption(
'manifest-dir',
mandatory: true,
help: 'Directory containing Cargo.toml',
)
..addMultiOption('target',
help: 'Rust target triple of artifact to build.\n'
'Can be specified multiple times or omitted in which case\n'
'all targets for current platform will be built.')
..addOption(
'android-sdk-location',
help: 'Location of Android SDK (if available)',
)
..addOption(
'android-ndk-version',
help: 'Android NDK version (if available)',
)
..addOption(
'android-min-sdk-version',
help: 'Android minimum rquired version (if available)',
)
..addOption(
'temp-dir',
help: 'Directory to store temporary build artifacts',
)
..addFlag(
"verbose",
abbr: "v",
defaultsTo: false,
help: "Enable verbose logging",
);
}
@override
final name = 'precompile-binaries';
@override
final description = 'Prebuild and upload binaries\n'
'Private key must be passed through PRIVATE_KEY environment variable. '
'Use gen_key through generate priave key.\n'
'Github token must be passed as GITHUB_TOKEN environment variable.\n';
@override
Future<void> run() async {
final verbose = argResults!['verbose'] as bool;
if (verbose) {
enableVerboseLogging();
}
final privateKeyString = Platform.environment['PRIVATE_KEY'];
if (privateKeyString == null) {
throw ArgumentError('Missing PRIVATE_KEY environment variable');
}
final githubToken = Platform.environment['GITHUB_TOKEN'];
if (githubToken == null) {
throw ArgumentError('Missing GITHUB_TOKEN environment variable');
}
final privateKey = HEX.decode(privateKeyString);
if (privateKey.length != 64) {
throw ArgumentError('Private key must be 64 bytes long');
}
final manifestDir = argResults!['manifest-dir'] as String;
if (!Directory(manifestDir).existsSync()) {
throw ArgumentError('Manifest directory does not exist: $manifestDir');
}
String? androidMinSdkVersionString =
argResults!['android-min-sdk-version'] as String?;
int? androidMinSdkVersion;
if (androidMinSdkVersionString != null) {
androidMinSdkVersion = int.tryParse(androidMinSdkVersionString);
if (androidMinSdkVersion == null) {
throw ArgumentError(
'Invalid android-min-sdk-version: $androidMinSdkVersionString');
}
}
final targetStrigns = argResults!['target'] as List<String>;
final targets = targetStrigns.map((target) {
final res = Target.forRustTriple(target);
if (res == null) {
throw ArgumentError('Invalid target: $target');
}
return res;
}).toList(growable: false);
final precompileBinaries = PrecompileBinaries(
privateKey: PrivateKey(privateKey),
githubToken: githubToken,
manifestDir: manifestDir,
repositorySlug: RepositorySlug.full(argResults!['repository'] as String),
targets: targets,
androidSdkLocation: argResults!['android-sdk-location'] as String?,
androidNdkVersion: argResults!['android-ndk-version'] as String?,
androidMinSdkVersion: androidMinSdkVersion,
tempDir: argResults!['temp-dir'] as String?,
);
await precompileBinaries.run();
}
}
class VerifyBinariesCommand extends Command {
VerifyBinariesCommand() {
argParser.addOption(
'manifest-dir',
mandatory: true,
help: 'Directory containing Cargo.toml',
);
}
@override
final name = "verify-binaries";
@override
final description = 'Verifies published binaries\n'
'Checks whether there is a binary published for each targets\n'
'and checks the signature.';
@override
Future<void> run() async {
final manifestDir = argResults!['manifest-dir'] as String;
final verifyBinaries = VerifyBinaries(
manifestDir: manifestDir,
);
await verifyBinaries.run();
}
}
Future<void> runMain(List<String> args) async {
try {
// Init logging before options are loaded
initLogging();
if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) {
return AndroidEnvironment.clangLinkerWrapper(args);
}
final runner = CommandRunner('build_tool', 'Cargokit built_tool')
..addCommand(BuildPodCommand())
..addCommand(BuildGradleCommand())
..addCommand(BuildCMakeCommand())
..addCommand(GenKeyCommand())
..addCommand(PrecompileBinariesCommand())
..addCommand(VerifyBinariesCommand());
await runner.run(args);
} on ArgumentError catch (e) {
stderr.writeln(e.toString());
exit(1);
} catch (e, s) {
log.severe(kDoubleSeparator);
log.severe('Cargokit BuildTool failed with error:');
log.severe(kSeparator);
log.severe(e);
// This tells user to install Rust, there's no need to pollute the log with
// stack trace.
if (e is! RustupNotFoundException) {
log.severe(kSeparator);
log.severe(s);
log.severe(kSeparator);
log.severe('BuildTool arguments: $args');
}
log.severe(kDoubleSeparator);
exit(1);
}
}

View File

@ -0,0 +1,198 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'android_environment.dart';
import 'cargo.dart';
import 'environment.dart';
import 'options.dart';
import 'rustup.dart';
import 'target.dart';
import 'util.dart';
final _log = Logger('builder');
enum BuildConfiguration {
debug,
release,
profile,
}
extension on BuildConfiguration {
bool get isDebug => this == BuildConfiguration.debug;
String get rustName => switch (this) {
BuildConfiguration.debug => 'debug',
BuildConfiguration.release => 'release',
BuildConfiguration.profile => 'release',
};
}
class BuildException implements Exception {
final String message;
BuildException(this.message);
@override
String toString() {
return 'BuildException: $message';
}
}
class BuildEnvironment {
final BuildConfiguration configuration;
final CargokitCrateOptions crateOptions;
final String targetTempDir;
final String manifestDir;
final CrateInfo crateInfo;
final bool isAndroid;
final String? androidSdkPath;
final String? androidNdkVersion;
final int? androidMinSdkVersion;
final String? javaHome;
BuildEnvironment({
required this.configuration,
required this.crateOptions,
required this.targetTempDir,
required this.manifestDir,
required this.crateInfo,
required this.isAndroid,
this.androidSdkPath,
this.androidNdkVersion,
this.androidMinSdkVersion,
this.javaHome,
});
static BuildConfiguration parseBuildConfiguration(String value) {
// XCode configuration adds the flavor to configuration name.
final firstSegment = value.split('-').first;
final buildConfiguration = BuildConfiguration.values.firstWhereOrNull(
(e) => e.name == firstSegment,
);
if (buildConfiguration == null) {
_log.warning('Unknown build configuraiton $value, will assume release');
return BuildConfiguration.release;
}
return buildConfiguration;
}
static BuildEnvironment fromEnvironment({
required bool isAndroid,
}) {
final buildConfiguration =
parseBuildConfiguration(Environment.configuration);
final manifestDir = Environment.manifestDir;
final crateOptions = CargokitCrateOptions.load(
manifestDir: manifestDir,
);
final crateInfo = CrateInfo.load(manifestDir);
return BuildEnvironment(
configuration: buildConfiguration,
crateOptions: crateOptions,
targetTempDir: Environment.targetTempDir,
manifestDir: manifestDir,
crateInfo: crateInfo,
isAndroid: isAndroid,
androidSdkPath: isAndroid ? Environment.sdkPath : null,
androidNdkVersion: isAndroid ? Environment.ndkVersion : null,
androidMinSdkVersion:
isAndroid ? int.parse(Environment.minSdkVersion) : null,
javaHome: isAndroid ? Environment.javaHome : null,
);
}
}
class RustBuilder {
final Target target;
final BuildEnvironment environment;
RustBuilder({
required this.target,
required this.environment,
});
void prepare(
Rustup rustup,
) {
final toolchain = _toolchain;
if (rustup.installedTargets(toolchain) == null) {
rustup.installToolchain(toolchain);
}
if (toolchain == 'nightly') {
rustup.installRustSrcForNightly();
}
if (!rustup.installedTargets(toolchain)!.contains(target.rust)) {
rustup.installTarget(target.rust, toolchain: toolchain);
}
}
CargoBuildOptions? get _buildOptions =>
environment.crateOptions.cargo[environment.configuration];
String get _toolchain => _buildOptions?.toolchain.name ?? 'stable';
/// Returns the path of directory containing build artifacts.
Future<String> build() async {
final extraArgs = _buildOptions?.flags ?? [];
final manifestPath = path.join(environment.manifestDir, 'Cargo.toml');
runCommand(
'rustup',
[
'run',
_toolchain,
'cargo',
'build',
...extraArgs,
'--manifest-path',
manifestPath,
'-p',
environment.crateInfo.packageName,
if (!environment.configuration.isDebug) '--release',
'--target',
target.rust,
'--target-dir',
environment.targetTempDir,
],
environment: await _buildEnvironment(),
);
return path.join(
environment.targetTempDir,
target.rust,
environment.configuration.rustName,
);
}
Future<Map<String, String>> _buildEnvironment() async {
if (target.android == null) {
return {};
} else {
final sdkPath = environment.androidSdkPath;
final ndkVersion = environment.androidNdkVersion;
final minSdkVersion = environment.androidMinSdkVersion;
if (sdkPath == null) {
throw BuildException('androidSdkPath is not set');
}
if (ndkVersion == null) {
throw BuildException('androidNdkVersion is not set');
}
if (minSdkVersion == null) {
throw BuildException('androidMinSdkVersion is not set');
}
final env = AndroidEnvironment(
sdkPath: sdkPath,
ndkVersion: ndkVersion,
minSdkVersion: minSdkVersion,
targetTempDir: environment.targetTempDir,
target: target,
);
if (!env.ndkIsInstalled() && environment.javaHome != null) {
env.installNdk(javaHome: environment.javaHome!);
}
return env.buildEnvironment();
}
}
}

View File

@ -0,0 +1,48 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:toml/toml.dart';
class ManifestException {
ManifestException(this.message, {required this.fileName});
final String? fileName;
final String message;
@override
String toString() {
if (fileName != null) {
return 'Failed to parse package manifest at $fileName: $message';
} else {
return 'Failed to parse package manifest: $message';
}
}
}
class CrateInfo {
CrateInfo({required this.packageName});
final String packageName;
static CrateInfo parseManifest(String manifest, {final String? fileName}) {
final toml = TomlDocument.parse(manifest);
final package = toml.toMap()['package'];
if (package == null) {
throw ManifestException('Missing package section', fileName: fileName);
}
final name = package['name'];
if (name == null) {
throw ManifestException('Missing package name', fileName: fileName);
}
return CrateInfo(packageName: name);
}
static CrateInfo load(String manifestDir) {
final manifestFile = File(path.join(manifestDir, 'Cargo.toml'));
final manifest = manifestFile.readAsStringSync();
return parseManifest(manifest, fileName: manifestFile.path);
}
}

View File

@ -0,0 +1,124 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:path/path.dart' as path;
class CrateHash {
/// Computes a hash uniquely identifying crate content. This takes into account
/// content all all .rs files inside the src directory, as well as Cargo.toml,
/// Cargo.lock, build.rs and cargokit.yaml.
///
/// If [tempStorage] is provided, computed hash is stored in a file in that directory
/// and reused on subsequent calls if the crate content hasn't changed.
static String compute(String manifestDir, {String? tempStorage}) {
return CrateHash._(
manifestDir: manifestDir,
tempStorage: tempStorage,
)._compute();
}
CrateHash._({
required this.manifestDir,
required this.tempStorage,
});
String _compute() {
final files = getFiles();
final tempStorage = this.tempStorage;
if (tempStorage != null) {
final quickHash = _computeQuickHash(files);
final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash'));
quickHashFolder.createSync(recursive: true);
final quickHashFile = File(path.join(quickHashFolder.path, quickHash));
if (quickHashFile.existsSync()) {
return quickHashFile.readAsStringSync();
}
final hash = _computeHash(files);
quickHashFile.writeAsStringSync(hash);
return hash;
} else {
return _computeHash(files);
}
}
/// Computes a quick hash based on files stat (without reading contents). This
/// is used to cache the real hash, which is slower to compute since it involves
/// reading every single file.
String _computeQuickHash(List<File> files) {
final output = AccumulatorSink<Digest>();
final input = sha256.startChunkedConversion(output);
final data = ByteData(8);
for (final file in files) {
input.add(utf8.encode(file.path));
final stat = file.statSync();
data.setUint64(0, stat.size);
input.add(data.buffer.asUint8List());
data.setUint64(0, stat.modified.millisecondsSinceEpoch);
input.add(data.buffer.asUint8List());
}
input.close();
return base64Url.encode(output.events.single.bytes);
}
String _computeHash(List<File> files) {
final output = AccumulatorSink<Digest>();
final input = sha256.startChunkedConversion(output);
void addTextFile(File file) {
// text Files are hashed by lines in case we're dealing with github checkout
// that auto-converts line endings.
final splitter = LineSplitter();
if (file.existsSync()) {
final data = file.readAsStringSync();
final lines = splitter.convert(data);
for (final line in lines) {
input.add(utf8.encode(line));
}
}
}
for (final file in files) {
addTextFile(file);
}
input.close();
final res = output.events.single;
// Truncate to 128bits.
final hash = res.bytes.sublist(0, 16);
return hex.encode(hash);
}
List<File> getFiles() {
final src = Directory(path.join(manifestDir, 'src'));
final files = src
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.toList();
files.sortBy((element) => element.path);
void addFile(String relative) {
final file = File(path.join(manifestDir, relative));
if (file.existsSync()) {
files.add(file);
}
}
addFile('Cargo.toml');
addFile('Cargo.lock');
addFile('build.rs');
addFile('cargokit.yaml');
return files;
}
final String manifestDir;
final String? tempStorage;
}

View File

@ -0,0 +1,68 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
extension on String {
String resolveSymlink() => File(this).resolveSymbolicLinksSync();
}
class Environment {
/// Current build configuration (debug or release).
static String get configuration =>
_getEnv("CARGOKIT_CONFIGURATION").toLowerCase();
static bool get isDebug => configuration == 'debug';
static bool get isRelease => configuration == 'release';
/// Temporary directory where Rust build artifacts are placed.
static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR");
/// Final output directory where the build artifacts are placed.
static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR');
/// Path to the crate manifest (containing Cargo.toml).
static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR');
/// Directory inside root project. Not necessarily root folder. Symlinks are
/// not resolved on purpose.
static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR');
// Pod
/// Platform name (macosx, iphoneos, iphonesimulator).
static String get darwinPlatformName =>
_getEnv("CARGOKIT_DARWIN_PLATFORM_NAME");
/// List of architectures to build for (arm64, armv7, x86_64).
static List<String> get darwinArchs =>
_getEnv("CARGOKIT_DARWIN_ARCHS").split(' ');
// Gradle
static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION");
static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION");
static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR");
static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME");
static List<String> get targetPlatforms =>
_getEnv("CARGOKIT_TARGET_PLATFORMS").split(',');
// CMAKE
static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM");
static String _getEnv(String key) {
final res = Platform.environment[key];
if (res == null) {
throw Exception("Missing environment variable $key");
}
return res;
}
static String _getEnvPath(String key) {
final res = _getEnv(key);
if (Directory(res).existsSync()) {
return res.resolveSymlink();
} else {
return res;
}
}
}

View File

@ -0,0 +1,52 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:logging/logging.dart';
const String kSeparator = "--";
const String kDoubleSeparator = "==";
bool _lastMessageWasSeparator = false;
void _log(LogRecord rec) {
final prefix = '${rec.level.name}: ';
final out = rec.level == Level.SEVERE ? stderr : stdout;
if (rec.message == kSeparator) {
if (!_lastMessageWasSeparator) {
out.write(prefix);
out.writeln('-' * 80);
_lastMessageWasSeparator = true;
}
return;
} else if (rec.message == kDoubleSeparator) {
out.write(prefix);
out.writeln('=' * 80);
_lastMessageWasSeparator = true;
return;
}
out.write(prefix);
out.writeln(rec.message);
_lastMessageWasSeparator = false;
}
void initLogging() {
Logger.root.level = Level.INFO;
Logger.root.onRecord.listen((LogRecord rec) {
final lines = rec.message.split('\n');
for (final line in lines) {
if (line.isNotEmpty || lines.length == 1 || line != lines.last) {
_log(LogRecord(
rec.level,
line,
rec.loggerName,
));
}
}
});
}
void enableVerboseLogging() {
Logger.root.level = Level.ALL;
}

View File

@ -0,0 +1,309 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:hex/hex.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'package:source_span/source_span.dart';
import 'package:yaml/yaml.dart';
import 'builder.dart';
import 'environment.dart';
import 'rustup.dart';
final _log = Logger('options');
/// A class for exceptions that have source span information attached.
class SourceSpanException implements Exception {
// This is a getter so that subclasses can override it.
/// A message describing the exception.
String get message => _message;
final String _message;
// This is a getter so that subclasses can override it.
/// The span associated with this exception.
///
/// This may be `null` if the source location can't be determined.
SourceSpan? get span => _span;
final SourceSpan? _span;
SourceSpanException(this._message, this._span);
/// Returns a string representation of `this`.
///
/// [color] may either be a [String], a [bool], or `null`. If it's a string,
/// it indicates an ANSI terminal color escape that should be used to
/// highlight the span's text. If it's `true`, it indicates that the text
/// should be highlighted using the default color. If it's `false` or `null`,
/// it indicates that the text shouldn't be highlighted.
@override
String toString({Object? color}) {
if (span == null) return message;
return 'Error on ${span!.message(message, color: color)}';
}
}
enum Toolchain {
stable,
beta,
nightly,
}
class CargoBuildOptions {
final Toolchain toolchain;
final List<String> flags;
CargoBuildOptions({
required this.toolchain,
required this.flags,
});
static Toolchain _toolchainFromNode(YamlNode node) {
if (node case YamlScalar(value: String name)) {
final toolchain =
Toolchain.values.firstWhereOrNull((element) => element.name == name);
if (toolchain != null) {
return toolchain;
}
}
throw SourceSpanException(
'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.',
node.span);
}
static CargoBuildOptions parse(YamlNode node) {
if (node is! YamlMap) {
throw SourceSpanException('Cargo options must be a map', node.span);
}
Toolchain toolchain = Toolchain.stable;
List<String> flags = [];
for (final MapEntry(:key, :value) in node.nodes.entries) {
if (key case YamlScalar(value: 'toolchain')) {
toolchain = _toolchainFromNode(value);
} else if (key case YamlScalar(value: 'extra_flags')) {
if (value case YamlList(nodes: List<YamlNode> list)) {
if (list.every((element) {
if (element case YamlScalar(value: String _)) {
return true;
}
return false;
})) {
flags = list.map((e) => e.value as String).toList();
continue;
}
}
throw SourceSpanException(
'Extra flags must be a list of strings', value.span);
} else {
throw SourceSpanException(
'Unknown cargo option type. Must be "toolchain" or "extra_flags".',
key.span);
}
}
return CargoBuildOptions(toolchain: toolchain, flags: flags);
}
}
extension on YamlMap {
/// Map that extracts keys so that we can do map case check on them.
Map<dynamic, YamlNode> get valueMap =>
nodes.map((key, value) => MapEntry(key.value, value));
}
class PrecompiledBinaries {
final String uriPrefix;
final PublicKey publicKey;
PrecompiledBinaries({
required this.uriPrefix,
required this.publicKey,
});
static PublicKey _publicKeyFromHex(String key, SourceSpan? span) {
final bytes = HEX.decode(key);
if (bytes.length != 32) {
throw SourceSpanException(
'Invalid public key. Must be 32 bytes long.', span);
}
return PublicKey(bytes);
}
static PrecompiledBinaries parse(YamlNode node) {
if (node case YamlMap(valueMap: Map<dynamic, YamlNode> map)) {
if (map
case {
'url_prefix': YamlNode urlPrefixNode,
'public_key': YamlNode publicKeyNode,
}) {
final urlPrefix = switch (urlPrefixNode) {
YamlScalar(value: String urlPrefix) => urlPrefix,
_ => throw SourceSpanException(
'Invalid URL prefix value.', urlPrefixNode.span),
};
final publicKey = switch (publicKeyNode) {
YamlScalar(value: String publicKey) =>
_publicKeyFromHex(publicKey, publicKeyNode.span),
_ => throw SourceSpanException(
'Invalid public key value.', publicKeyNode.span),
};
return PrecompiledBinaries(
uriPrefix: urlPrefix,
publicKey: publicKey,
);
}
}
throw SourceSpanException(
'Invalid precompiled binaries value. '
'Expected Map with "url_prefix" and "public_key".',
node.span);
}
}
/// Cargokit options specified for Rust crate.
class CargokitCrateOptions {
CargokitCrateOptions({
this.cargo = const {},
this.precompiledBinaries,
});
final Map<BuildConfiguration, CargoBuildOptions> cargo;
final PrecompiledBinaries? precompiledBinaries;
static CargokitCrateOptions parse(YamlNode node) {
if (node is! YamlMap) {
throw SourceSpanException('Cargokit options must be a map', node.span);
}
final options = <BuildConfiguration, CargoBuildOptions>{};
PrecompiledBinaries? precompiledBinaries;
for (final entry in node.nodes.entries) {
if (entry
case MapEntry(
key: YamlScalar(value: 'cargo'),
value: YamlNode node,
)) {
if (node is! YamlMap) {
throw SourceSpanException('Cargo options must be a map', node.span);
}
for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) {
if (key case YamlScalar(value: String name)) {
final configuration = BuildConfiguration.values
.firstWhereOrNull((element) => element.name == name);
if (configuration != null) {
options[configuration] = CargoBuildOptions.parse(value);
continue;
}
}
throw SourceSpanException(
'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.',
key.span);
}
} else if (entry.key case YamlScalar(value: 'precompiled_binaries')) {
precompiledBinaries = PrecompiledBinaries.parse(entry.value);
} else {
throw SourceSpanException(
'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".',
entry.key.span);
}
}
return CargokitCrateOptions(
cargo: options,
precompiledBinaries: precompiledBinaries,
);
}
static CargokitCrateOptions load({
required String manifestDir,
}) {
final uri = Uri.file(path.join(manifestDir, "cargokit.yaml"));
final file = File.fromUri(uri);
if (file.existsSync()) {
final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri);
return parse(contents);
} else {
return CargokitCrateOptions();
}
}
}
class CargokitUserOptions {
// When Rustup is installed always build locally unless user opts into
// using precompiled binaries.
static bool defaultUsePrecompiledBinaries() {
return Rustup.executablePath() == null;
}
CargokitUserOptions({
required this.usePrecompiledBinaries,
required this.verboseLogging,
});
CargokitUserOptions._()
: usePrecompiledBinaries = defaultUsePrecompiledBinaries(),
verboseLogging = false;
static CargokitUserOptions parse(YamlNode node) {
if (node is! YamlMap) {
throw SourceSpanException('Cargokit options must be a map', node.span);
}
bool usePrecompiledBinaries = defaultUsePrecompiledBinaries();
bool verboseLogging = false;
for (final entry in node.nodes.entries) {
if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) {
if (entry.value case YamlScalar(value: bool value)) {
usePrecompiledBinaries = value;
continue;
}
throw SourceSpanException(
'Invalid value for "use_precompiled_binaries". Must be a boolean.',
entry.value.span);
} else if (entry.key case YamlScalar(value: 'verbose_logging')) {
if (entry.value case YamlScalar(value: bool value)) {
verboseLogging = value;
continue;
}
throw SourceSpanException(
'Invalid value for "verbose_logging". Must be a boolean.',
entry.value.span);
} else {
throw SourceSpanException(
'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".',
entry.key.span);
}
}
return CargokitUserOptions(
usePrecompiledBinaries: usePrecompiledBinaries,
verboseLogging: verboseLogging,
);
}
static CargokitUserOptions load() {
String fileName = "cargokit_options.yaml";
var userProjectDir = Directory(Environment.rootProjectDir);
while (userProjectDir.parent.path != userProjectDir.path) {
final configFile = File(path.join(userProjectDir.path, fileName));
if (configFile.existsSync()) {
final contents = loadYamlNode(
configFile.readAsStringSync(),
sourceUrl: configFile.uri,
);
final res = parse(contents);
if (res.verboseLogging) {
_log.info('Found user options file at ${configFile.path}');
}
return res;
}
userProjectDir = userProjectDir.parent;
}
return CargokitUserOptions._();
}
final bool usePrecompiledBinaries;
final bool verboseLogging;
}

View File

@ -0,0 +1,202 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:github/github.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'artifacts_provider.dart';
import 'builder.dart';
import 'cargo.dart';
import 'crate_hash.dart';
import 'options.dart';
import 'rustup.dart';
import 'target.dart';
final _log = Logger('precompile_binaries');
class PrecompileBinaries {
PrecompileBinaries({
required this.privateKey,
required this.githubToken,
required this.repositorySlug,
required this.manifestDir,
required this.targets,
this.androidSdkLocation,
this.androidNdkVersion,
this.androidMinSdkVersion,
this.tempDir,
});
final PrivateKey privateKey;
final String githubToken;
final RepositorySlug repositorySlug;
final String manifestDir;
final List<Target> targets;
final String? androidSdkLocation;
final String? androidNdkVersion;
final int? androidMinSdkVersion;
final String? tempDir;
static String fileName(Target target, String name) {
return '${target.rust}_$name';
}
static String signatureFileName(Target target, String name) {
return '${target.rust}_$name.sig';
}
Future<void> run() async {
final crateInfo = CrateInfo.load(manifestDir);
final targets = List.of(this.targets);
if (targets.isEmpty) {
targets.addAll([
...Target.buildableTargets(),
if (androidSdkLocation != null) ...Target.androidTargets(),
]);
}
_log.info('Precompiling binaries for $targets');
final hash = CrateHash.compute(manifestDir);
_log.info('Computed crate hash: $hash');
final String tagName = 'precompiled_$hash';
final github = GitHub(auth: Authentication.withToken(githubToken));
final repo = github.repositories;
final release = await _getOrCreateRelease(
repo: repo,
tagName: tagName,
packageName: crateInfo.packageName,
hash: hash,
);
final tempDir = this.tempDir != null
? Directory(this.tempDir!)
: Directory.systemTemp.createTempSync('precompiled_');
tempDir.createSync(recursive: true);
final crateOptions = CargokitCrateOptions.load(
manifestDir: manifestDir,
);
final buildEnvironment = BuildEnvironment(
configuration: BuildConfiguration.release,
crateOptions: crateOptions,
targetTempDir: tempDir.path,
manifestDir: manifestDir,
crateInfo: crateInfo,
isAndroid: androidSdkLocation != null,
androidSdkPath: androidSdkLocation,
androidNdkVersion: androidNdkVersion,
androidMinSdkVersion: androidMinSdkVersion,
);
final rustup = Rustup();
for (final target in targets) {
final artifactNames = getArtifactNames(
target: target,
libraryName: crateInfo.packageName,
remote: true,
);
if (artifactNames.every((name) {
final fileName = PrecompileBinaries.fileName(target, name);
return (release.assets ?? []).any((e) => e.name == fileName);
})) {
_log.info("All artifacts for $target already exist - skipping");
continue;
}
_log.info('Building for $target');
final builder =
RustBuilder(target: target, environment: buildEnvironment);
builder.prepare(rustup);
final res = await builder.build();
final assets = <CreateReleaseAsset>[];
for (final name in artifactNames) {
final file = File(path.join(res, name));
if (!file.existsSync()) {
throw Exception('Missing artifact: ${file.path}');
}
final data = file.readAsBytesSync();
final create = CreateReleaseAsset(
name: PrecompileBinaries.fileName(target, name),
contentType: "application/octet-stream",
assetData: data,
);
final signature = sign(privateKey, data);
final signatureCreate = CreateReleaseAsset(
name: signatureFileName(target, name),
contentType: "application/octet-stream",
assetData: signature,
);
bool verified = verify(public(privateKey), data, signature);
if (!verified) {
throw Exception('Signature verification failed');
}
assets.add(create);
assets.add(signatureCreate);
}
_log.info('Uploading assets: ${assets.map((e) => e.name)}');
for (final asset in assets) {
// This seems to be failing on CI so do it one by one
int retryCount = 0;
while (true) {
try {
await repo.uploadReleaseAssets(release, [asset]);
break;
} on Exception catch (e) {
if (retryCount == 10) {
rethrow;
}
++retryCount;
_log.shout(
'Upload failed (attempt $retryCount, will retry): ${e.toString()}');
await Future.delayed(Duration(seconds: 2));
}
}
}
}
_log.info('Cleaning up');
tempDir.deleteSync(recursive: true);
}
Future<Release> _getOrCreateRelease({
required RepositoriesService repo,
required String tagName,
required String packageName,
required String hash,
}) async {
Release release;
try {
_log.info('Fetching release $tagName');
release = await repo.getReleaseByTagName(repositorySlug, tagName);
} on ReleaseNotFound {
_log.info('Release not found - creating release $tagName');
release = await repo.createRelease(
repositorySlug,
CreateRelease.from(
tagName: tagName,
name: 'Precompiled binaries ${hash.substring(0, 8)}',
targetCommitish: null,
isDraft: false,
isPrerelease: false,
body: 'Precompiled binaries for crate $packageName, '
'crate hash $hash.',
));
}
return release;
}
}

View File

@ -0,0 +1,136 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
import 'util.dart';
class _Toolchain {
_Toolchain(
this.name,
this.targets,
);
final String name;
final List<String> targets;
}
class Rustup {
List<String>? installedTargets(String toolchain) {
final targets = _installedTargets(toolchain);
return targets != null ? List.unmodifiable(targets) : null;
}
void installToolchain(String toolchain) {
log.info("Installing Rust toolchain: $toolchain");
runCommand("rustup", ['toolchain', 'install', toolchain]);
_installedToolchains
.add(_Toolchain(toolchain, _getInstalledTargets(toolchain)));
}
void installTarget(
String target, {
required String toolchain,
}) {
log.info("Installing Rust target: $target");
runCommand("rustup", [
'target',
'add',
'--toolchain',
toolchain,
target,
]);
_installedTargets(toolchain)?.add(target);
}
final List<_Toolchain> _installedToolchains;
Rustup() : _installedToolchains = _getInstalledToolchains();
List<String>? _installedTargets(String toolchain) => _installedToolchains
.firstWhereOrNull(
(e) => e.name == toolchain || e.name.startsWith('$toolchain-'))
?.targets;
static List<_Toolchain> _getInstalledToolchains() {
String extractToolchainName(String line) {
// ignore (default) after toolchain name
final parts = line.split(' ');
return parts[0];
}
final res = runCommand("rustup", ['toolchain', 'list']);
// To list all non-custom toolchains, we need to filter out lines that
// don't start with "stable", "beta", or "nightly".
Pattern nonCustom = RegExp(r"^(stable|beta|nightly)");
final lines = res.stdout
.toString()
.split('\n')
.where((e) => e.isNotEmpty && e.startsWith(nonCustom))
.map(extractToolchainName)
.toList(growable: true);
return lines
.map(
(name) => _Toolchain(
name,
_getInstalledTargets(name),
),
)
.toList(growable: true);
}
static List<String> _getInstalledTargets(String toolchain) {
final res = runCommand("rustup", [
'target',
'list',
'--toolchain',
toolchain,
'--installed',
]);
final lines = res.stdout
.toString()
.split('\n')
.where((e) => e.isNotEmpty)
.toList(growable: true);
return lines;
}
bool _didInstallRustSrcForNightly = false;
void installRustSrcForNightly() {
if (_didInstallRustSrcForNightly) {
return;
}
// Useful for -Z build-std
runCommand(
"rustup",
['component', 'add', 'rust-src', '--toolchain', 'nightly'],
);
_didInstallRustSrcForNightly = true;
}
static String? executablePath() {
final envPath = Platform.environment['PATH'];
final envPathSeparator = Platform.isWindows ? ';' : ':';
final home = Platform.isWindows
? Platform.environment['USERPROFILE']
: Platform.environment['HOME'];
final paths = [
if (home != null) path.join(home, '.cargo', 'bin'),
if (envPath != null) ...envPath.split(envPathSeparator),
];
for (final p in paths) {
final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup';
final rustupPath = path.join(p, rustup);
if (File(rustupPath).existsSync()) {
return rustupPath;
}
}
return null;
}
}

View File

@ -0,0 +1,140 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:collection/collection.dart';
import 'util.dart';
class Target {
Target({
required this.rust,
this.flutter,
this.android,
this.androidMinSdkVersion,
this.darwinPlatform,
this.darwinArch,
});
static final all = [
Target(
rust: 'armv7-linux-androideabi',
flutter: 'android-arm',
android: 'armeabi-v7a',
androidMinSdkVersion: 16,
),
Target(
rust: 'aarch64-linux-android',
flutter: 'android-arm64',
android: 'arm64-v8a',
androidMinSdkVersion: 21,
),
Target(
rust: 'i686-linux-android',
flutter: 'android-x86',
android: 'x86',
androidMinSdkVersion: 16,
),
Target(
rust: 'x86_64-linux-android',
flutter: 'android-x64',
android: 'x86_64',
androidMinSdkVersion: 21,
),
Target(
rust: 'x86_64-pc-windows-msvc',
flutter: 'windows-x64',
),
Target(
rust: 'x86_64-unknown-linux-gnu',
flutter: 'linux-x64',
),
Target(
rust: 'aarch64-unknown-linux-gnu',
flutter: 'linux-arm64',
),
Target(
rust: 'x86_64-apple-darwin',
darwinPlatform: 'macosx',
darwinArch: 'x86_64',
),
Target(
rust: 'aarch64-apple-darwin',
darwinPlatform: 'macosx',
darwinArch: 'arm64',
),
Target(
rust: 'aarch64-apple-ios',
darwinPlatform: 'iphoneos',
darwinArch: 'arm64',
),
Target(
rust: 'aarch64-apple-ios-sim',
darwinPlatform: 'iphonesimulator',
darwinArch: 'arm64',
),
Target(
rust: 'x86_64-apple-ios',
darwinPlatform: 'iphonesimulator',
darwinArch: 'x86_64',
),
];
static Target? forFlutterName(String flutterName) {
return all.firstWhereOrNull((element) => element.flutter == flutterName);
}
static Target? forDarwin({
required String platformName,
required String darwinAarch,
}) {
return all.firstWhereOrNull((element) => //
element.darwinPlatform == platformName &&
element.darwinArch == darwinAarch);
}
static Target? forRustTriple(String triple) {
return all.firstWhereOrNull((element) => element.rust == triple);
}
static List<Target> androidTargets() {
return all
.where((element) => element.android != null)
.toList(growable: false);
}
/// Returns buildable targets on current host platform ignoring Android targets.
static List<Target> buildableTargets() {
if (Platform.isLinux) {
// Right now we don't support cross-compiling on Linux. So we just return
// the host target.
final arch = runCommand('arch', []).stdout as String;
if (arch.trim() == 'aarch64') {
return [Target.forRustTriple('aarch64-unknown-linux-gnu')!];
} else {
return [Target.forRustTriple('x86_64-unknown-linux-gnu')!];
}
}
return all.where((target) {
if (Platform.isWindows) {
return target.rust.contains('-windows-');
} else if (Platform.isMacOS) {
return target.darwinPlatform != null;
}
return false;
}).toList(growable: false);
}
@override
String toString() {
return rust;
}
final String? flutter;
final String rust;
final String? android;
final int? androidMinSdkVersion;
final String? darwinPlatform;
final String? darwinArch;
}

View File

@ -0,0 +1,172 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'logging.dart';
import 'rustup.dart';
final log = Logger("process");
class CommandFailedException implements Exception {
final String executable;
final List<String> arguments;
final ProcessResult result;
CommandFailedException({
required this.executable,
required this.arguments,
required this.result,
});
@override
String toString() {
final stdout = result.stdout.toString().trim();
final stderr = result.stderr.toString().trim();
return [
"External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}",
"Returned Exit Code: ${result.exitCode}",
kSeparator,
"STDOUT:",
if (stdout.isNotEmpty) stdout,
kSeparator,
"STDERR:",
if (stderr.isNotEmpty) stderr,
].join('\n');
}
}
class TestRunCommandArgs {
final String executable;
final List<String> arguments;
final String? workingDirectory;
final Map<String, String>? environment;
final bool includeParentEnvironment;
final bool runInShell;
final Encoding? stdoutEncoding;
final Encoding? stderrEncoding;
TestRunCommandArgs({
required this.executable,
required this.arguments,
this.workingDirectory,
this.environment,
this.includeParentEnvironment = true,
this.runInShell = false,
this.stdoutEncoding,
this.stderrEncoding,
});
}
class TestRunCommandResult {
TestRunCommandResult({
this.pid = 1,
this.exitCode = 0,
this.stdout = '',
this.stderr = '',
});
final int pid;
final int exitCode;
final String stdout;
final String stderr;
}
TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride;
ProcessResult runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding? stdoutEncoding = systemEncoding,
Encoding? stderrEncoding = systemEncoding,
}) {
if (testRunCommandOverride != null) {
final result = testRunCommandOverride!(TestRunCommandArgs(
executable: executable,
arguments: arguments,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
));
return ProcessResult(
result.pid,
result.exitCode,
result.stdout,
result.stderr,
);
}
log.finer('Running command $executable ${arguments.join(' ')}');
final res = Process.runSync(
_resolveExecutable(executable),
arguments,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
stderrEncoding: stderrEncoding,
stdoutEncoding: stdoutEncoding,
);
if (res.exitCode != 0) {
throw CommandFailedException(
executable: executable,
arguments: arguments,
result: res,
);
} else {
return res;
}
}
class RustupNotFoundException implements Exception {
@override
String toString() {
return [
' ',
'rustup not found in PATH.',
' ',
'Maybe you need to install Rust? It only takes a minute:',
' ',
if (Platform.isWindows) 'https://www.rust-lang.org/tools/install',
if (hasHomebrewRustInPath()) ...[
'\$ brew unlink rust # Unlink homebrew Rust from PATH',
],
if (!Platform.isWindows)
"\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh",
' ',
].join('\n');
}
static bool hasHomebrewRustInPath() {
if (!Platform.isMacOS) {
return false;
}
final envPath = Platform.environment['PATH'] ?? '';
final paths = envPath.split(':');
return paths.any((p) {
return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync();
});
}
}
String _resolveExecutable(String executable) {
if (executable == 'rustup') {
final resolved = Rustup.executablePath();
if (resolved != null) {
return resolved;
}
throw RustupNotFoundException();
} else {
return executable;
}
}

View File

@ -0,0 +1,84 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import 'dart:io';
import 'package:ed25519_edwards/ed25519_edwards.dart';
import 'package:http/http.dart';
import 'artifacts_provider.dart';
import 'cargo.dart';
import 'crate_hash.dart';
import 'options.dart';
import 'precompile_binaries.dart';
import 'target.dart';
class VerifyBinaries {
VerifyBinaries({
required this.manifestDir,
});
final String manifestDir;
Future<void> run() async {
final crateInfo = CrateInfo.load(manifestDir);
final config = CargokitCrateOptions.load(manifestDir: manifestDir);
final precompiledBinaries = config.precompiledBinaries;
if (precompiledBinaries == null) {
stdout.writeln('Crate does not support precompiled binaries.');
} else {
final crateHash = CrateHash.compute(manifestDir);
stdout.writeln('Crate hash: $crateHash');
for (final target in Target.all) {
final message = 'Checking ${target.rust}...';
stdout.write(message.padRight(40));
stdout.flush();
final artifacts = getArtifactNames(
target: target,
libraryName: crateInfo.packageName,
remote: true,
);
final prefix = precompiledBinaries.uriPrefix;
bool ok = true;
for (final artifact in artifacts) {
final fileName = PrecompileBinaries.fileName(target, artifact);
final signatureFileName =
PrecompileBinaries.signatureFileName(target, artifact);
final url = Uri.parse('$prefix$crateHash/$fileName');
final signatureUrl =
Uri.parse('$prefix$crateHash/$signatureFileName');
final signature = await get(signatureUrl);
if (signature.statusCode != 200) {
stdout.writeln('MISSING');
ok = false;
break;
}
final asset = await get(url);
if (asset.statusCode != 200) {
stdout.writeln('MISSING');
ok = false;
break;
}
if (!verify(precompiledBinaries.publicKey, asset.bodyBytes,
signature.bodyBytes)) {
stdout.writeln('INVALID SIGNATURE');
ok = false;
}
}
if (ok) {
stdout.writeln('OK');
}
}
}
}
}

View File

@ -0,0 +1,453 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
url: "https://pub.dev"
source: hosted
version: "64.0.0"
adaptive_number:
dependency: transitive
description:
name: adaptive_number
sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
args:
dependency: "direct main"
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.2"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
collection:
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: "direct main"
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
ed25519_edwards:
dependency: "direct main"
description:
name: ed25519_edwards
sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
github:
dependency: "direct main"
description:
name: github
sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411"
url: "https://pub.dev"
source: hosted
version: "9.17.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
hex:
dependency: "direct main"
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
http:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
lints:
dependency: "direct dev"
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
logging:
dependency: "direct main"
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: "direct main"
description:
name: path
sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev"
source: hosted
version: "5.4.0"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
url: "https://pub.dev"
source: hosted
version: "1.1.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.dev"
source: hosted
version: "0.10.12"
source_span:
dependency: "direct main"
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9"
url: "https://pub.dev"
source: hosted
version: "1.24.6"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
test_core:
dependency: transitive
description:
name: test_core
sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265"
url: "https://pub.dev"
source: hosted
version: "0.5.6"
toml:
dependency: "direct main"
description:
name: toml
sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44"
url: "https://pub.dev"
source: hosted
version: "0.14.0"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version:
dependency: "direct main"
description:
name: version
sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d"
url: "https://pub.dev"
source: hosted
version: "11.9.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
yaml:
dependency: "direct main"
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"

View File

@ -0,0 +1,33 @@
# This is copied from Cargokit (which is the official way to use it currently)
# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
name: build_tool
description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build.
publish_to: none
version: 1.0.0
environment:
sdk: ">=3.0.0 <4.0.0"
# Add regular dependencies here.
dependencies:
# these are pinned on purpose because the bundle_tool_runner doesn't have
# pubspec.lock. See run_build_tool.sh
logging: 1.2.0
path: 1.8.0
version: 3.0.0
collection: 1.18.0
ed25519_edwards: 0.3.1
hex: 0.2.0
yaml: 3.1.2
source_span: 1.10.0
github: 9.17.0
args: 2.4.2
crypto: 3.0.3
convert: 3.1.1
http: 1.1.0
toml: 0.14.0
dev_dependencies:
lints: ^2.1.0
test: ^1.24.0

View File

@ -0,0 +1,99 @@
SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..")
# Workaround for https://github.com/dart-lang/pub/issues/4010
get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH)
if(WIN32)
# REALPATH does not properly resolve symlinks on windows :-/
execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
# Arguments
# - target: CMAKE target to which rust library is linked
# - manifest_dir: relative path from current folder to directory containing cargo manifest
# - lib_name: cargo package name
# - any_symbol_name: name of any exported symbol from the library.
# used on windows to force linking with library.
function(apply_cargokit target manifest_dir lib_name any_symbol_name)
set(CARGOKIT_LIB_NAME "${lib_name}")
set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}")
if (CMAKE_CONFIGURATION_TYPES)
set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>")
set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/${CARGOKIT_LIB_FULL_NAME}")
else()
set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}")
endif()
set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build")
if (FLUTTER_TARGET_PLATFORM)
set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}")
else()
set(CARGOKIT_TARGET_PLATFORM "windows-x64")
endif()
set(CARGOKIT_ENV
"CARGOKIT_CMAKE=${CMAKE_COMMAND}"
"CARGOKIT_CONFIGURATION=$<CONFIG>"
"CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}"
"CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}"
"CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}"
"CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}"
"CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool"
"CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}"
)
if (WIN32)
set(SCRIPT_EXTENSION ".cmd")
set(IMPORT_LIB_EXTENSION ".lib")
else()
set(SCRIPT_EXTENSION ".sh")
set(IMPORT_LIB_EXTENSION "")
execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}")
endif()
# Using generators in custom command is only supported in CMake 3.20+
if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0")
foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
add_custom_command(
OUTPUT
"${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}"
"${CMAKE_CURRENT_BINARY_DIR}/_phony_"
COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV}
"${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake
VERBATIM
)
endforeach()
else()
add_custom_command(
OUTPUT
${OUTPUT_LIB}
"${CMAKE_CURRENT_BINARY_DIR}/_phony_"
COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV}
"${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake
VERBATIM
)
endif()
set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE)
if (TARGET ${target})
# If we have actual cmake target provided create target and make existing
# target depend on it
add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB})
add_dependencies("${target}" "${target}_cargokit")
target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}")
if(WIN32)
target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}")
endif()
else()
# Otherwise (FFI) just use ALL to force building always
add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB})
endif()
# Allow adding the output library to plugin bundled libraries
set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE)
endfunction()

View File

@ -0,0 +1,34 @@
function Resolve-Symlinks {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string] $Path
)
[string] $separator = '/'
[string[]] $parts = $Path.Split($separator)
[string] $realPath = ''
foreach ($part in $parts) {
if ($realPath -and !$realPath.EndsWith($separator)) {
$realPath += $separator
}
$realPath += $part.Replace('\', '/')
# The slash is important when using Get-Item on Drive letters in pwsh.
if (-not($realPath.Contains($separator)) -and $realPath.EndsWith(':')) {
$realPath += '/'
}
$item = Get-Item $realPath
if ($item.LinkTarget) {
$realPath = $item.LinkTarget.Replace('\', '/')
}
}
$realPath
}
$path = Resolve-Symlinks -Path $args[0]
Write-Host $path

View File

@ -0,0 +1,179 @@
/// This is copied from Cargokit (which is the official way to use it currently)
/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin
import java.nio.file.Paths
import org.apache.tools.ant.taskdefs.condition.Os
CargoKitPlugin.file = buildscript.sourceFile
apply plugin: CargoKitPlugin
class CargoKitExtension {
String manifestDir; // Relative path to folder containing Cargo.toml
String libname; // Library name within Cargo.toml. Must be a cdylib
}
abstract class CargoKitBuildTask extends DefaultTask {
@Input
String buildMode
@Input
String buildDir
@Input
String outputDir
@Input
String ndkVersion
@Input
String sdkDirectory
@Input
int compileSdkVersion;
@Input
int minSdkVersion;
@Input
String pluginFile
@Input
List<String> targetPlatforms
@TaskAction
def build() {
if (project.cargokit.manifestDir == null) {
throw new GradleException("Property 'manifestDir' must be set on cargokit extension");
}
if (project.cargokit.libname == null) {
throw new GradleException("Property 'libname' must be set on cargokit extension");
}
def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh"
def path = Paths.get(new File(pluginFile).parent, "..", executableName);
def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir)
def rootProjectDir = project.rootProject.projectDir
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
project.exec {
commandLine 'chmod', '+x', path
}
}
project.exec {
executable path
args "build-gradle"
environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir
environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool"
environment "CARGOKIT_MANIFEST_DIR", manifestDir
environment "CARGOKIT_CONFIGURATION", buildMode
environment "CARGOKIT_TARGET_TEMP_DIR", buildDir
environment "CARGOKIT_OUTPUT_DIR", outputDir
environment "CARGOKIT_NDK_VERSION", ndkVersion
environment "CARGOKIT_SDK_DIR", sdkDirectory
environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion
environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion
environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",")
environment "CARGOKIT_JAVA_HOME", System.properties['java.home']
}
}
}
class CargoKitPlugin implements Plugin<Project> {
static String file;
private Plugin findFlutterPlugin(Project rootProject) {
_findFlutterPlugin(rootProject.childProjects)
}
private Plugin _findFlutterPlugin(Map projects) {
for (project in projects) {
for (plugin in project.value.getPlugins()) {
if (plugin.class.name == "com.flutter.gradle.FlutterPlugin") {
return plugin;
}
}
def plugin = _findFlutterPlugin(project.value.childProjects);
if (plugin != null) {
return plugin;
}
}
return null;
}
@Override
void apply(Project project) {
def plugin = findFlutterPlugin(project.rootProject);
project.extensions.create("cargokit", CargoKitExtension)
if (plugin == null) {
print("Flutter plugin not found, CargoKit plugin will not be applied.")
return;
}
def cargoBuildDir = "${project.buildDir}/build"
// Determine if the project is an application or library
def isApplication = plugin.project.plugins.hasPlugin('com.android.application')
def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants
variants.all { variant ->
final buildType = variant.buildType.name
def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}";
def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs;
jniLibs.srcDir(new File(cargoOutputDir))
def platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect()
// Same thing addFlutterDependencies does in flutter.gradle
if (buildType == "debug") {
platforms.add("android-x86")
platforms.add("android-x64")
}
// The task name depends on plugin properties, which are not available
// at this point
project.getGradle().afterProject {
def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}";
if (project.tasks.findByName(taskName)) {
return
}
if (plugin.project.android.ndkVersion == null) {
throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.")
}
def task = project.tasks.create(taskName, CargoKitBuildTask.class) {
buildMode = variant.buildType.name
buildDir = cargoBuildDir
outputDir = cargoOutputDir
ndkVersion = plugin.project.android.ndkVersion
sdkDirectory = plugin.project.android.sdkDirectory
minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int
compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int
targetPlatforms = platforms
pluginFile = CargoKitPlugin.file
}
def onTask = { newTask ->
if (newTask.name == "merge${buildType.capitalize()}NativeLibs") {
newTask.dependsOn task
// Fix gradle 7.4.2 not picking up JNI library changes
newTask.outputs.upToDateWhen { false }
}
}
project.tasks.each onTask
project.tasks.whenTaskAdded onTask
}
}
}
}

91
cargokit/run_build_tool.cmd Executable file
View File

@ -0,0 +1,91 @@
@echo off
setlocal
setlocal ENABLEDELAYEDEXPANSION
SET BASEDIR=%~dp0
if not exist "%CARGOKIT_TOOL_TEMP_DIR%" (
mkdir "%CARGOKIT_TOOL_TEMP_DIR%"
)
cd /D "%CARGOKIT_TOOL_TEMP_DIR%"
SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool
SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart
set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/%
(
echo name: build_tool_runner
echo version: 1.0.0
echo publish_to: none
echo.
echo environment:
echo sdk: '^>=3.0.0 ^<4.0.0'
echo.
echo dependencies:
echo build_tool:
echo path: %BUILD_TOOL_PKG_DIR_POSIX%
) >pubspec.yaml
if not exist bin (
mkdir bin
)
(
echo import 'package:build_tool/build_tool.dart' as build_tool;
echo void main^(List^<String^> args^) ^{
echo build_tool.runMain^(args^);
echo ^}
) >bin\build_tool_runner.dart
SET PRECOMPILED=bin\build_tool_runner.dill
REM To detect changes in package we compare output of DIR /s (recursive)
set PREV_PACKAGE_INFO=.dart_tool\package_info.prev
set CUR_PACKAGE_INFO=.dart_tool\package_info.cur
DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig"
REM Last line in dir output is free space on harddrive. That is bound to
REM change between invocation so we need to remove it
(
Set "Line="
For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do (
SetLocal EnableDelayedExpansion
If Defined Line Echo !Line!
EndLocal
Set "Line=%%A")
) >"%CUR_PACKAGE_INFO%"
DEL "%CUR_PACKAGE_INFO%_orig"
REM Compare current directory listing with previous
FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1
If %ERRORLEVEL% neq 0 (
REM Changed - copy current to previous and remove precompiled kernel
if exist "%PREV_PACKAGE_INFO%" (
DEL "%PREV_PACKAGE_INFO%"
)
MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%"
if exist "%PRECOMPILED%" (
DEL "%PRECOMPILED%"
)
)
REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO%
REM which means we need to do pub get and precompile
if not exist "%PRECOMPILED%" (
echo Running pub get in "%cd%"
"%DART%" pub get --no-precompile
"%DART%" compile kernel bin/build_tool_runner.dart
)
"%DART%" "%PRECOMPILED%" %*
REM 253 means invalid snapshot version.
If %ERRORLEVEL% equ 253 (
"%DART%" pub get --no-precompile
"%DART%" compile kernel bin/build_tool_runner.dart
"%DART%" "%PRECOMPILED%" %*
)

99
cargokit/run_build_tool.sh Executable file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -e
BASEDIR=$(dirname "$0")
mkdir -p "$CARGOKIT_TOOL_TEMP_DIR"
cd "$CARGOKIT_TOOL_TEMP_DIR"
# Write a very simple bin package in temp folder that depends on build_tool package
# from Cargokit. This is done to ensure that we don't pollute Cargokit folder
# with .dart_tool contents.
BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool"
if [[ -z $FLUTTER_ROOT ]]; then # not defined
DART=dart
else
DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart"
fi
cat << EOF > "pubspec.yaml"
name: build_tool_runner
version: 1.0.0
publish_to: none
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
build_tool:
path: "$BUILD_TOOL_PKG_DIR"
EOF
mkdir -p "bin"
cat << EOF > "bin/build_tool_runner.dart"
import 'package:build_tool/build_tool.dart' as build_tool;
void main(List<String> args) {
build_tool.runMain(args);
}
EOF
# Create alias for `shasum` if it does not exist and `sha1sum` exists
if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then
shopt -s expand_aliases
alias shasum="sha1sum"
fi
# Dart run will not cache any package that has a path dependency, which
# is the case for our build_tool_runner. So instead we precompile the package
# ourselves.
# To invalidate the cached kernel we use the hash of ls -LR of the build_tool
# package directory. This should be good enough, as the build_tool package
# itself is not meant to have any path dependencies.
if [[ "$OSTYPE" == "darwin"* ]]; then
PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum)
else
PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum)
fi
PACKAGE_HASH_FILE=".package_hash"
if [ -f "$PACKAGE_HASH_FILE" ]; then
EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE")
if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then
rm "$PACKAGE_HASH_FILE"
fi
fi
# Run pub get if needed.
if [ ! -f "$PACKAGE_HASH_FILE" ]; then
"$DART" pub get --no-precompile
"$DART" compile kernel bin/build_tool_runner.dart
echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE"
fi
# Rebuild the tool if it was deleted by Android Studio
if [ ! -f "bin/build_tool_runner.dill" ]; then
"$DART" compile kernel bin/build_tool_runner.dart
fi
set +e
"$DART" bin/build_tool_runner.dill "$@"
exit_code=$?
# 253 means invalid snapshot version.
if [ $exit_code == 253 ]; then
"$DART" pub get --no-precompile
"$DART" compile kernel bin/build_tool_runner.dart
"$DART" bin/build_tool_runner.dill "$@"
exit_code=$?
fi
exit $exit_code

49
docs/README_DEV.rst Normal file
View File

@ -0,0 +1,49 @@
FRB v2
======
With flutter_rust_bridge, the Rust dependency lives in Cargo.toml, not pubspec.yaml
FRB v2 ships with Cargokit — a build helper that hooks into each platform's build
system (Gradle, Xcode, CMake) and compiles the Rust crate automatically at flutter build time.
This means:
* ccc_rust does not need a pubspec.yaml
* ccc_rust stays a pure Rust crate
* The current lum_ccc_rust entry in pubspec.yaml would be removed — it moves to rust/Cargo.toml
::
ccc_dart_plugin/
├── pubspec.yaml ← NO reference to ccc_rust here
├── rust/
│ ├── Cargo.toml ← ccc_rust referenced HERE as a Rust crate dep
│ └── src/api.rs ← thin wrappers calling ccc_rust
ccc_rust
========
The Rust crypto library remains a pure Rust crate with no Flutter or FFI-specific code.
Option 1: Build from source via Cargokit (recommended for development)
* The Rust crate references ccc_rust via a git tag in Cargo.toml
* Cargokit compiles the Rust code at build time for each platform
* No precompiled binaries are needed
Option 2: Cargokit precompiled binaries via GitHub Releases (recommended for pub.dev)
* The Rust crate is compiled into platform-specific binaries (staticlib for iOS/macOS, cdylib for Android)
* Binaries are uploaded to GitHub Releases
* Cargokit fetches the correct binary at build time based on the target platform
* This allows pub.dev users to get native performance without needing Rust or a build environment
Workflow
--------
* CI builds the Rust crate for all 5 platform targets
* CI uploads the .so/.dylib/.dll/.framework to a GitHub Release tagged with the Cargo package hash
* When consumers run flutter build, Cargokit downloads the precompiled binary instead of compiling
* If no precompiled binary exists, it falls back to source build
* Pros: Fast builds for consumers, no Rust toolchain needed, no repo bloat, built into FRB, single repo
* Cons: Requires CI pipeline to build + upload binaries
* Best for: pub.dev releases, distributing to teams without Rust installed

View File

@ -1,9 +1,9 @@
CCC Dart Plugin — Architecture & Integration Guide
====================================================
:Status: Milestone 2 (Planned)
:Depends on: ``ccc_rust`` ≥ v0.1.0
:Target: Flutter (iOS, Android, macOS)
:Status: Milestone 2 (In Progress)
:Depends on: ``ccc_rust`` ≥ v0.1.0 (ready)
:Target: Flutter (iOS, Android, macOS, Linux, Windows)
:Bridge: ``flutter_rust_bridge`` v2
Overview
@ -23,7 +23,7 @@ This repository contains:
* A small Rust bridge crate referencing ``ccc_rust`` via a git tag
* Generated ``flutter_rust_bridge`` bindings
* A Flutter plugin scaffold (iOS / Android / macOS)
* A Flutter plugin scaffold (iOS / Android / macOS / Linux / Windows)
* A clean Dart API surface for applications
This plugin does **not** implement cryptography itself.
@ -100,21 +100,25 @@ Repository Structure
ccc_dart_plugin/
├── pubspec.yaml
├── flutter_rust_bridge.yaml
├── lib/
│ ├── ccc_cryptography.dart # barrel export
│ ├── ccc_crypto.dart
│ ├── ccc_provider_catalog.dart
│ ├── ccc_self_test.dart
│ ├── ccc_exceptions.dart
│ └── src/
│ └── frb_generated.dart
│ └── frb_generated.dart # auto-generated by FRB
├── rust/
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ └── api.rs
├── flutter_rust_bridge.yaml
│ └── api.rs # thin wrappers over ccc_rust
├── ios/
├── android/
├── macos/
├── linux/
├── windows/
└── test/
└── roundtrip_test.dart
@ -128,7 +132,7 @@ Rust Bridge Crate
.. code-block:: toml
[package]
name = "ccc_dart_bridge"
name = "ccc_cryptography"
version = "0.1.0"
edition = "2021"
@ -228,13 +232,19 @@ Testing Requirements
Before tagging ``v0.1.0`` of this plugin:
* Rust bridge builds for:
- iOS (aarch64-apple-ios)
- Android (arm64 + x86_64)
- macOS (aarch64)
* Flutter integration tests:
- iOS (aarch64-apple-ios, aarch64-apple-ios-sim)
- Android (aarch64-linux-android, x86_64-linux-android)
- macOS (aarch64-apple-darwin, x86_64-apple-darwin)
- Linux (x86_64-unknown-linux-gnu)
- Windows (x86_64-pc-windows-msvc)
* Unit tests (Dart-side, host platform):
- AEAD roundtrip encrypt/decrypt
- HKDF known-vector test
- X25519 key agreement test
- Provider catalog and self-test validation
- Error handling / exception mapping
* Flutter integration tests (on-device, all five platforms):
- Full init → encrypt → decrypt flow
* ``CccSelfTest.runAll()`` returns success
* No Dart-side crypto logic present
* No modification of ``ccc_rust`` source
@ -296,3 +306,11 @@ Planned enhancements after Milestone 2:
----
Related Documents
-----------------
* ``docs/milestone2_phases.rst`` — Phase-by-phase implementation
tracker with task checklists and exit criteria.
----

View File

@ -0,0 +1,643 @@
CCC Dart Plugin — Milestone 2 Implementation Phases
=====================================================
:Created: 2026-03-11
:Last Updated: 2026-03-11
:Status: In Progress
:Depends on: ``ccc_rust`` ≥ v0.1.0 (ready)
:Bridge: ``flutter_rust_bridge`` v2
:Platforms: iOS, Android, macOS, Linux, Windows
:Rust repo: ``ssh://git@10.0.5.109:j3g/lum_ccc_rust.git``
:Local override: ``/Volumes/LUM/source/letusmsg_proj/app/lum_ccc_rust``
This document tracks the implementation phases for Milestone 2 —
building the ``flutter_rust_bridge``-based Flutter plugin that exposes
``ccc_rust`` cryptographic providers to Dart.
Related Documents
~~~~~~~~~~~~~~~~~
* ``docs/ccc_fplugin_architecture_design.rst`` — Architecture & integration guide
* ``docs/ccc_rust_plan.rst`` — Master three-milestone architecture plan
* ``docs/ccc_rust_plan_phases.rst`` — Phase tracking across all milestones
* ``docs/ccc_rust_milestone2_session_state_OLD.rst`` — Earlier planning (superseded by this doc)
Preconditions (verified)
~~~~~~~~~~~~~~~~~~~~~~~~
* ``[x]`` Milestone 1 verification gate passed
* ``[x]`` Conformance vectors passed in Rust workspace
* ``[x]`` Rust target builds validated (iOS + Android)
* ``[x]`` ``cargo audit`` — no known CVEs
----
Phase Overview
--------------
====== ========================================== ========== ============
Phase Title Status Depends on
====== ========================================== ========== ============
1 Project Restructure & FRB Setup Done —
2 Rust Bridge Crate (DTOs + API) Done Phase 1
3 Dart API Surface Done Phase 2
4 Platform Build Verification Done Phase 3
5 Unit Tests Done Phase 3
6 Integration Tests & Polish In Progress Phase 4, 5
====== ========================================== ========== ============
----
Phase 1 — Project Restructure & FRB Setup
------------------------------------------
:Status: Done
**Goal:** Replace the placeholder C FFI scaffold with a
``flutter_rust_bridge`` v2 project structure.
Tasks
~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 1.1
- Remove placeholder C source files (``src/ccc_cryptography.c``,
``src/ccc_cryptography.h``)
- ✅
* - 1.2
- Remove ``ffigen.yaml`` and generated bindings
(``lib/ccc_cryptography_bindings_generated.dart``)
- ✅
* - 1.3
- Create ``rust/`` directory with bridge crate scaffold
(``Cargo.toml``, ``src/lib.rs``, ``src/api/``); FRB directory
structure via ``flutter_rust_bridge_codegen integrate``;
set ``crate-type = ["cdylib", "staticlib"]``
- ✅
* - 1.4
- Add ``flutter_rust_bridge`` v2 dependency to ``rust/Cargo.toml``;
reference ``ccc_rust`` crates via local path deps for dev
(git deps commented for CI/release);
crate name set to ``ccc_cryptography`` to match plugin
- ✅
* - 1.5
- Create ``flutter_rust_bridge.yaml`` configuration
(``rust_input: crate::api``, ``dart_output: lib/src/rust``)
- ✅
* - 1.6
- Update ``pubspec.yaml``: replace ``ffigen`` with
``flutter_rust_bridge``, add freezed, integration_test
- ✅
* - 1.7
- Platform build configs generated by FRB integrate +
Cargokit (CMakeLists, Podspecs, Gradle)
- ✅
* - 1.8
- Run ``flutter_rust_bridge_codegen generate`` — bridge compiles;
``flutter build macos`` succeeds (42.7 MB example app)
- ✅
Exit Criteria
~~~~~~~~~~~~~
* ``flutter_rust_bridge_codegen generate`` succeeds.
* ``flutter build`` runs without errors on host (macOS).
* No C FFI scaffolding remains.
----
Phase 2 — Rust Bridge Crate (DTOs + API)
------------------------------------------
:Status: Done
:Depends on: Phase 1
**Goal:** Implement DTOs and thin Rust wrappers in the bridge crate that
expose all ``ccc_rust`` provider functionality across the FRB bridge.
The bridge crate lives at ``rust/`` and must not contain any cryptographic
logic — only type conversions and delegation to ``ccc_rust`` providers.
Files created:
* ``rust/src/api/dto.rs`` — DTOs, algorithm enums, bridge error type
* ``rust/src/api/crypto.rs`` — 13 bridge entry-point functions
DTOs (``rust/src/api/dto.rs``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bridge-visible data transfer objects with ``From<core type>`` impls:
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 2.1
- ``CccCapabilities`` — maps from ``ProviderCapabilities``
(provider name, per-category algorithm list with availability,
efficiency/reliability scores); flattened HashMap → Vec for FRB
- ✅
* - 2.2
- ``CccKemKeyPair`` — maps from ``KemKeyPair``
(public_key, private_key as ``Vec<u8>``)
- ✅
* - 2.3
- ``CccKemEncapResult`` — maps from ``KemEncapResult``
(ciphertext, shared_secret)
- ✅
* - 2.4
- ``CccSelfTestReport`` — maps from ``SelfTestReport``
(overall pass/fail, per-algorithm results)
- ✅
* - 2.5
- ``CccAlgoTestResult`` — maps from ``AlgoTestResult``
(algorithm ID, pass/fail, diagnostic message)
- ✅
Bridge API (``rust/src/api/crypto.rs``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 2.6
- ``ccc_init()`` — register WolfSslProvider (idempotent)
- ✅
* - 2.7
- ``ccc_list_providers()`` — return registered provider names (sync)
- ✅
* - 2.8
- ``ccc_capabilities()`` — return ``CccCapabilities`` for default provider
- ✅
* - 2.9
- ``ccc_aead_encrypt(algorithm, key, nonce, plaintext, aad)``
— thin wrapper over provider AEAD encrypt
- ✅
* - 2.10
- ``ccc_aead_decrypt(algorithm, key, nonce, ciphertext, aad)``
— thin wrapper over provider AEAD decrypt
- ✅
* - 2.11
- ``ccc_kdf_derive(algorithm, ikm, salt, info, len)``
— thin wrapper over provider KDF
- ✅
* - 2.12
- ``ccc_mac_compute(algorithm, key, data)``
— thin wrapper over provider MAC compute
- ✅
* - 2.13
- ``ccc_mac_verify(algorithm, key, data, mac)``
— thin wrapper over provider MAC verify
- ✅
* - 2.14
- ``ccc_hash(algorithm, data)``
— thin wrapper over provider hash
- ✅
* - 2.15
- ``ccc_kem_generate_keypair(algorithm)``
— return ``CccKemKeyPair``
- ✅
* - 2.16
- ``ccc_kem_encapsulate(algorithm, public_key)``
— return ``CccKemEncapResult``
- ✅
* - 2.17
- ``ccc_kem_decapsulate(algorithm, private_key, ciphertext)``
— return shared secret
- ✅
* - 2.18
- ``ccc_self_test()`` — return ``CccSelfTestReport``
- ✅
* - 2.19
- ``CccCryptoError`` enum mapping all 7 ``CryptoError`` variants
(``UnsupportedAlgorithm``, ``InvalidKey``, ``InvalidNonce``,
``AuthenticationFailed``, ``InvalidInput``, ``FeatureNotCompiled``,
``InternalError``)
- ✅
* - 2.20
- Run ``flutter_rust_bridge_codegen generate`` — Dart bindings
generated; ``flutter build macos`` succeeds (44.5 MB)
- ✅
DTO ↔ Rust Type Mapping
~~~~~~~~~~~~~~~~~~~~~~~~
========================== =================================== ========================
Bridge DTO Rust Core Type Direction
========================== =================================== ========================
``CccCapabilities`` ``ProviderCapabilities`` Rust → Dart
``CccAlgorithmEntry`` (algo_id, name, capability) Rust → Dart
``CccAlgorithmCapability`` ``AlgorithmCapability`` Rust → Dart
``CccKemKeyPair`` ``KemKeyPair`` Rust → Dart
``CccKemEncapResult`` ``KemEncapResult`` Rust → Dart
``CccSelfTestReport`` ``SelfTestReport`` Rust → Dart
``CccAlgoTestResult`` ``AlgoTestResult`` Rust → Dart
``CccCryptoError`` enum ``CryptoError`` Rust → Dart exception
``CccAeadAlgorithm`` ``AeadAlgorithm`` Dart → Rust
``CccKdfAlgorithm`` ``KdfAlgorithm`` Dart → Rust
``CccMacAlgorithm`` ``MacAlgorithm`` Dart → Rust
``CccHashAlgorithm`` ``HashAlgorithm`` Dart → Rust
``CccKemAlgorithm`` ``KemAlgorithm`` Dart → Rust
========================== =================================== ========================
Exit Criteria
~~~~~~~~~~~~~
* ✅ All DTOs defined with ``From<>`` impls.
* ✅ All 13 bridge entry points exist in ``crypto.rs``.
* ✅ Codegen produces Dart bindings (+ freezed).
* ✅ Rust crate compiles with ``ccc_rust`` linked.
* ✅ ``flutter build macos`` succeeds (44.5 MB).
----
Phase 3 — Dart API Surface
----------------------------
:Status: Done
:Depends on: Phase 2
**Goal:** Build the clean Dart API layer that application code will
consume, wired to the FRB-generated bindings.
Files created:
* ``lib/ccc_crypto.dart````CccCrypto`` static façade (13 methods)
* ``lib/ccc_exceptions.dart`` — sealed ``CccException`` hierarchy (7 subtypes)
* ``lib/ccc_provider_catalog.dart````CccProviderCatalog`` + ``CccAlgorithmInfo``
* ``lib/ccc_self_test.dart````CccSelfTestResult`` + ``CccAlgorithmTestResult``
* ``lib/ccc_cryptography.dart`` — barrel export file (updated)
Tasks
~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 3.1
- Create ``lib/ccc_crypto.dart````abstract final class CccCrypto``
with 13 static methods: ``init``, ``listProviders``,
``getCapabilities``, ``encryptAead``, ``decryptAead``,
``deriveKey``, ``computeMac``, ``verifyMac``, ``hash``,
``kemGenerateKeypair``, ``kemEncapsulate``, ``kemDecapsulate``,
``runSelfTest``; each catches ``CccCryptoError`` and rethrows
as typed ``CccException``
- ✅
* - 3.2
- Create ``lib/ccc_provider_catalog.dart````CccProviderCatalog``
(provider name, per-category algorithm lists with availability,
efficiency/reliability scores); ``CccAlgorithmInfo`` wrapper
- ✅
* - 3.3
- Create ``lib/ccc_self_test.dart````CccSelfTestResult``
(per-algorithm pass/fail, overall status, diagnostics);
``CccAlgorithmTestResult`` wrapper
- ✅
* - 3.4
- Create ``lib/ccc_exceptions.dart````sealed class CccException``
with 7 subtypes (``CccUnsupportedAlgorithm``, ``CccInvalidKey``,
``CccInvalidNonce``, ``CccAuthenticationFailed``,
``CccInvalidInput``, ``CccFeatureNotCompiled``,
``CccInternalError``); factory ``CccException.from()`` using
Dart 3 switch expression/pattern matching
- ✅
* - 3.5
- Algorithm IDs: FRB-generated enums (``CccAeadAlgorithm``,
``CccKdfAlgorithm``, ``CccMacAlgorithm``, ``CccHashAlgorithm``,
``CccKemAlgorithm``) re-exported directly from barrel file;
separate constants file not needed
- ✅
* - 3.6
- Wire all Dart API methods to FRB-generated bindings
- ✅
* - 3.7
- Update barrel export file ``lib/ccc_cryptography.dart``
re-exporting the public API (CccCrypto, enums, DTOs,
exceptions, provider catalog, self-test)
- ✅
* - 3.8
- Remove old placeholder Dart code; update example app
to use ``CccCrypto.init()`` + ``runSelfTest()`` demo
- ✅
Exit Criteria
~~~~~~~~~~~~~
* ✅ ``dart analyze`` reports no errors or warnings.
* ✅ Public API matches the contract in the architecture design doc.
* ✅ No crypto logic exists in Dart — orchestration only.
* ✅ ``flutter build macos`` succeeds (44.5 MB).
----
Phase 4 — Platform Build Verification
---------------------------------------
:Status: Done
:Depends on: Phase 3
**Goal:** Verify the Rust bridge crate cross-compiles and links
correctly on all five target platforms.
Fixes applied:
* ``cargokit/build_tool/lib/src/android_environment.dart`` — added
``BINDGEN_EXTRA_CLANG_ARGS_{target}`` with NDK sysroot so that
crates using ``bindgen`` (wolfSSL) can find ``<pthread.h>`` during
Android cross-compilation.
* ``android/src/main/AndroidManifest.xml`` — removed deprecated
``package`` attribute (required by current Android Gradle Plugin).
Tasks
~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 4.1
- Build for **iOS** (``aarch64-apple-ios``) —
``flutter build ios --no-codesign`` → 17.1 MB
- ✅
* - 4.2
- Build for **Android** (``armv7-linux-androideabi``,
``aarch64-linux-android``, ``x86_64-linux-android``) —
``flutter build apk`` → 44.6 MB
- ✅
* - 4.3
- Build for **macOS** (``aarch64-apple-darwin``) —
``flutter build macos`` → 44.5 MB (verified in Phase 3)
- ✅
* - 4.4
- Build for **Linux** (``x86_64-unknown-linux-gnu``) —
requires Linux host; Rust target installed, deferred to CI
- ⏳
* - 4.5
- Build for **Windows** (``x86_64-pc-windows-msvc``) —
requires Windows host; Rust target installed, deferred to CI
- ⏳
* - 4.6
- Fix platform-specific NDK issues — added
``BINDGEN_EXTRA_CLANG_ARGS`` to Cargokit Android env;
removed deprecated ``package`` attr from AndroidManifest
- ✅
* - 4.7
- Verify ``flutter run`` launches on at least one
physical/emulated device per platform — deferred to
Phase 6 integration tests
- ⏳
Exit Criteria
~~~~~~~~~~~~~
* ✅ ``flutter build ios`` succeeds (17.1 MB).
* ✅ ``flutter build apk`` succeeds (44.6 MB, 3 ABIs).
* ✅ ``flutter build macos`` succeeds (44.5 MB).
* ⏳ ``flutter build linux`` — deferred (requires Linux host).
* ⏳ ``flutter build windows`` — deferred (requires Windows host).
* ✅ Native library bundled correctly for iOS, Android, macOS.
----
Phase 5 — Unit Tests
---------------------
:Status: Done
:Depends on: Phase 3
**Goal:** Verify correctness of the bridge and Dart API via
Dart-side unit tests.
Test file: ``example/integration_test/ccc_crypto_test.dart``
Run command: ``flutter test integration_test/ccc_crypto_test.dart -d macos``
Result: **16/16 passed** on macOS host.
Tasks
~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 5.1
- AEAD roundtrip test — encrypt then decrypt 1 KB
(AES-256-GCM), verify plaintext equality
- ✅
* - 5.2
- AEAD roundtrip test — encrypt then decrypt 1 KB
(ChaCha20-Poly1305)
- ✅
* - 5.3
- AEAD tamper test — modify ciphertext, verify
``CccAuthenticationFailed`` is thrown
- ✅
* - 5.4
- KDF known-vector test — HKDF-SHA256 output compared against
RFC 5869 Test Case 1 (42 bytes, exact match)
- ✅
* - 5.5
- MAC roundtrip test — ``computeMac`` then ``verifyMac``
(HMAC-SHA256), verify returns true
- ✅
* - 5.6
- MAC tamper test — modify data after MAC, verify returns false
- ✅
* - 5.7
- Hash known-vector test — compare output against known
SHA-256 / SHA-384 / SHA-512 digests ("abc" test vector)
- ✅
* - 5.8
- KEM roundtrip test — ``kemGenerateKeypair``,
``kemEncapsulate``, ``kemDecapsulate`` (X25519); verify
shared secrets match
- ✅
* - 5.9
- KEM roundtrip test — X448 keygen/encap/decap flow
- ✅
* - 5.10
- Provider catalog test — verify ``listProviders()`` returns
non-empty list, ``getCapabilities()`` returns per-algorithm
availability and scores
- ✅
* - 5.11
- Self-test validation — ``runSelfTest()`` returns structured
report; non-AEAD algorithms all pass (AEAD test vectors
have known upstream length mismatch in ``ccc_rust``)
- ✅
* - 5.12
- Error handling tests — verify ``CccException`` subtypes
are thrown for invalid key length and invalid nonce length
- ✅
* - 5.13
- Algorithm ID mapping test — verify catalog IDs match
Rust enum discriminants (AES-GCM=12, SHA-256=40, X25519=50)
- ✅
Exit Criteria
~~~~~~~~~~~~~
* ✅ All 16 unit tests pass on host platform (macOS).
* ✅ No Dart-side crypto logic introduced by tests.
Known upstream issue:
* ``ccc_rust`` AEAD self-test vectors don't account for the appended
authentication tag in encrypt output, causing length-mismatch failures
for AES-256-GCM (80 vs 64 bytes) and ChaCha20-Poly1305 (56 vs 114 bytes).
This is a ``ccc_rust`` bug, not a bridge issue.
----
Phase 6 — Integration Tests & Polish
--------------------------------------
:Status: Not Started
:Depends on: Phase 4, Phase 5
**Goal:** End-to-end validation on real devices/simulators, example
app update, documentation, and release preparation.
Tasks
~~~~~
.. list-table::
:header-rows: 1
:widths: 5 60 15
* - #
- Task
- Status
* - 6.1
- Write Flutter integration tests (``integration_test/``) that
exercise the full init → encrypt → decrypt flow on-device;
16 tests covering AEAD, KDF, MAC, Hash, KEM, catalog,
self-test, error handling, and algorithm ID mapping
- ✅
* - 6.2
- Integration test: KEM keygen → encap → decap roundtrip
(X25519 + X448)
- ✅
* - 6.3
- Integration test: self-test API returns structured report
- ✅
* - 6.4
- Run integration tests on iOS simulator —
16/16 passed (iPhone 16, iOS 18.4);
fixed ``build.rs`` bindgen struct-size bug
(``options.h`` defines now auto-parsed)
- ✅
* - 6.5
- Run integration tests on Android emulator —
16/16 passed (API 33, aarch64-linux-android)
- ✅
* - 6.6
- Run integration tests on macOS — 16/16 passed
- ✅
* - 6.7
- Run integration tests on Linux —
deferred (requires Linux host)
- ⏳
* - 6.8
- Run integration tests on Windows —
deferred (requires Windows host)
- ⏳
* - 6.9
- Update example app (``example/``) — replace ``sum`` demo with
real crypto demo (encrypt/decrypt, provider listing, self-test)
- ☐
* - 6.10
- Update ``CHANGELOG.md`` for v0.1.0
- ☐
* - 6.11
- Final review — confirm no Dart crypto logic, no ``ccc_rust``
source modifications, all tests green
- ☐
* - 6.12
- Tag ``v0.1.0`` of the plugin
- ☐
Exit Criteria
~~~~~~~~~~~~~
* Integration tests pass on all five platforms.
* Example app demonstrates real cryptographic operations.
* ``CHANGELOG.md`` is updated.
* Repository tagged ``v0.1.0``.
----
Milestone 2 Verification Gate
------------------------------
*All items must be checked before the* ``v0.1.0`` *tag is cut.*
* ``[x]`` FRB bridge API compiles and loads in Flutter plugin
* ``[x]`` Generated Dart bindings are committed and reproducible
* ``[x]`` All unit tests pass on host platform (macOS)
* ``[x]`` ``flutter build ios`` succeeds
* ``[x]`` ``flutter build apk`` succeeds
* ``[x]`` ``flutter build macos`` succeeds
* ``[ ]`` ``flutter build linux`` succeeds
* ``[ ]`` ``flutter build windows`` succeeds
* ``[x]`` Integration tests pass on iOS simulator
* ``[x]`` Integration tests pass on Android emulator/device
* ``[x]`` Integration tests pass on macOS
* ``[ ]`` Integration tests pass on Linux
* ``[ ]`` Integration tests pass on Windows
* ``[x]`` No Dart-side crypto logic present
* ``[x]`` No modification of ``ccc_rust`` source
* ``[ ]`` ``ccc_rust`` dependency pinned to exact commit/tag
* ``[ ]`` Plugin package tagged ``v0.1.0``
----
Milestone 3 Handoff Checklist
------------------------------
Before handing off to the LetUsMsg app team:
☐ All Phase 16 exit criteria met
☐ Milestone 2 Verification Gate passed
``CccCrypto`` public API is stable and documented
☐ No floating dependencies (all git refs pinned)
☐ Self-test passes on all platforms
☐ Example app runs on all platforms
☐ No raw private keys exposed to Dart
``pubspec.yaml`` is publishable (or git-installable)
The LetUsMsg app will:
* Add this plugin via ``pubspec.yaml``
* Call ``CccCrypto.cccInit()`` at startup
* Replace existing crypto stubs with plugin calls
* Populate ``CccProviderCatalog`` from runtime capabilities
* Surface ``CccSelfTest.runAll()`` in debug UI
No Rust changes and no bridge changes are permitted during Milestone 3.
----

View File

@ -0,0 +1,127 @@
=====================================================
CCC Rust Milestone 2 — Session State (Planning)
=====================================================
:Status: Not started
:Date: 2026-02-26
:Repository target: ``ccc_cryptography`` (plugin repo)
:Depends on: ``ccc_rust`` Milestone 1 complete
Related Documents
=================
* Milestone 1 completion record:
``docs/ccc_rust_milestone1.rst``
Overview
========
This document tracks Milestone 2 execution state for Flutter plugin + bridge work.
Milestone 2 begins with Milestone 1 already verified and complete.
Current Gate Preconditions
==========================
* ``[x]`` Milestone 1 verification gate passed.
* ``[x]`` Conformance vectors passed in Rust workspace.
* ``[x]`` Rust target builds validated (iOS + Android).
Milestone 2 Work Checklist
==========================
Phase 1 — Repository + Scaffold
-------------------------------
* ``[ ]`` Create/confirm ``ccc_cryptography`` repository and branch strategy.
* ``[ ]`` Create Flutter plugin scaffold (``pubspec.yaml``, ``ios/``, ``android/``, ``macos/``).
* ``[ ]`` Add Rust bridge crate with ``crate-type = ["cdylib", "staticlib"]``.
* ``[ ]`` Wire dependency on ``ccc_rust`` pinned tag/revision.
Phase 2 — Bridge API Surface
----------------------------
* ``[ ]`` Define DTOs:
* ``CapabilitiesDto``
* ``KemKeyPairDto``
* ``KemEncapDto``
* ``SelfTestDto``
* ``AlgoTestResultDto``
* ``[ ]`` Implement bridge entry points:
* ``ccc_init``
* ``ccc_list_providers``
* ``ccc_capabilities`` / ``ccc_available_algorithms``
* ``ccc_aead_encrypt`` / ``ccc_aead_decrypt``
* ``ccc_kdf_derive``
* ``ccc_mac_compute`` / ``ccc_mac_verify``
* ``ccc_hash``
* ``ccc_kem_generate_keypair``
* ``ccc_kem_encapsulate`` / ``ccc_kem_decapsulate``
* ``ccc_self_test``
Phase 3 — FRB Codegen + Build Integration
-----------------------------------------
* ``[ ]`` Add ``flutter_rust_bridge`` configuration + codegen scripts.
* ``[ ]`` Run FRB codegen and commit generated artifacts.
* ``[ ]`` Verify plugin compiles for iOS.
* ``[ ]`` Verify plugin compiles for Android.
* ``[ ]`` Verify plugin compiles for macOS.
Phase 4 — Dart API Layer
------------------------
* ``[ ]`` Implement ``CccCrypto`` service API wrapper.
* ``[ ]`` Implement ``CccSelfTest`` wrapper.
* ``[ ]`` Implement runtime ``CccProviderCatalog`` population.
* ``[ ]`` Ensure algorithm ID mapping remains 1:1 with Rust discriminants.
Phase 5 — Integration + Validation
----------------------------------
* ``[ ]`` Add integration tests for AEAD roundtrip (at least AES-GCM and ChaCha20).
* ``[ ]`` Add integration tests for KEM keygen/encap/decap flow.
* ``[ ]`` Add integration tests for self-test API.
* ``[ ]`` Run on iOS simulator.
* ``[ ]`` Run on Android emulator/device.
Milestone 2 TODO Queue
======================
Immediate TODOs (next session)
------------------------------
* ``[ ]`` Decide exact Milestone 2 repository location/URL and baseline branch.
* ``[ ]`` Pin ``ccc_rust`` dependency to a reproducible reference (tag or commit hash).
* ``[ ]`` Define FRB module layout and generated file commit policy.
* ``[ ]`` Draft DTO type mapping table (Rust type -> bridge DTO -> Dart model).
Backlog TODOs
-------------
* ``[ ]`` Add CI job matrix for iOS/macOS/Android plugin builds.
* ``[ ]`` Add versioning/release policy for plugin package.
* ``[ ]`` Add troubleshooting notes for NDK/Xcode toolchains.
Milestone 2 Verification Gate
=============================
All of the following must pass before declaring Milestone 2 complete:
* ``[ ]`` FRB bridge API compiles and loads in Flutter plugin.
* ``[ ]`` Generated Dart bindings are committed and reproducible.
* ``[ ]`` ``flutter build ios`` succeeds.
* ``[ ]`` ``flutter build apk`` succeeds.
* ``[ ]`` ``flutter build macos`` succeeds.
* ``[ ]`` Integration test suite passes on iOS simulator.
* ``[ ]`` Integration test suite passes on Android emulator/device.
* ``[ ]`` Plugin package is tagged/released at ``v0.1.0`` (or agreed target version).
Notes
=====
* Milestone 2 is the first place where ``flutter_rust_bridge`` is allowed.
* Milestone 1 Rust workspace remains bridge-free and should not be modified
for Dart/plugin scaffolding.

387
docs/ccc_rust_plan.rst Normal file
View File

@ -0,0 +1,387 @@
===============================================
CCC Rust Crypto Provider — Architecture Plan
===============================================
:Status: Approved
:Date: 2026-02-24
:Author: Engineering
Overview
--------
This document describes the architecture for the **CCC Rust Crypto Provider**,
the first of three milestones in delivering hardware-grade cryptography to the
LetUsMsg application.
The guiding constraint is *clean separation*: each milestone is a distinct,
independently testable artifact with no forbidden upward dependencies.
----
Three-Milestone Strategy
------------------------
============= =================================== ========================== ===========================
Milestone Repository What ships Depends on
============= =================================== ========================== ===========================
**1 (this)** ``ccc_rust`` Pure Rust crypto library wolfSSL (vendored)
**2** ``ccc_dart_plugin`` Flutter plugin + Dart API ``ccc_rust`` (git/semver)
**3** ``letusmsg`` (existing app) App integration ``ccc_dart_plugin`` package
============= =================================== ========================== ===========================
Milestone 1 is complete and independently verified *before* any bridge or Dart
code is written. Milestone 2 owns the ``flutter_rust_bridge`` dependency and
all generated Dart bindings. Milestone 3 makes no Rust changes; it consumes
only the published Dart package.
----
Milestone 1 — ``ccc_rust`` Scope (this repository)
**Goal**: a fully tested, provider-agnostic Rust crypto library.
**Hard boundaries** — this repo must never contain:
* ``flutter_rust_bridge`` or any Flutter/Dart dependency
* Generated Dart files (``frb_generated.rs``, ``*.dart``)
* ``flutter_rust_bridge.yaml``
* Any platform plugin scaffold (``ios/``, ``android/``, ``macos/``)
Guiding Principles
~~~~~~~~~~~~~~~~~~
1. Every provider reports its own capabilities at runtime — no compile-time
hard-coding of ``available: true/false``.
2. Algorithm IDs in Rust map 1-to-1 to the integer constants in
``cipher_constants.dart`` — zero Dart changes needed when wiring up.
3. Key material is zeroed on drop (``zeroize`` crate) everywhere.
4. A conformance test suite validates NIST/RFC vectors for every algorithm
before any provider is marked ``available``.
5. The library has no runtime dependency on Flutter; it is consumable by any
FFI host (Flutter plugin, Python tests, CLI tools).
Repository Layout (Milestone 1 — this repo)
::
ccc_rust/
├── Cargo.toml ← workspace manifest (3 members, no bridge)
├── rust-toolchain.toml ← pinned stable toolchain
├── .cargo/
│ └── config.toml ← cross-compile target aliases
├── vendors/
│ ├── README.md ← submodule pin rationale + upgrade notes
│ └── wolfssl/ ← git submodule (wolfSSL/wolfssl @ v5.7.2-stable)
├── crates/
│ ├── ccc-crypto-core/ ← shared traits, enums, registry
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── algorithms.rs ← algorithm enums (values == cipher_constants.dart)
│ │ ├── capabilities.rs ← AlgorithmCapability, ProviderCapabilities
│ │ ├── error.rs ← CryptoError enum
│ │ ├── provider.rs ← CryptoProvider umbrella trait
│ │ ├── registry.rs ← ProviderRegistry (OnceLock<Mutex<...>>)
│ │ └── types.rs ← KemKeyPair, SelfTestReport, BenchmarkReport
│ └── ccc-crypto-wolfssl/ ← wolfSSL provider implementation
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ ├── aead.rs ← AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305
│ ├── kdf.rs ← HKDF-SHA256/384/512, Argon2id, BLAKE2b-KDF
│ ├── mac.rs ← HMAC-SHA256/384/512, BLAKE2b-MAC
│ ├── hash.rs ← SHA-256/384/512, SHA3-256/512, BLAKE2b-512
│ ├── kem.rs ← X25519, X448 (ML-KEM deferred Phase 5)
│ ├── capabilities.rs ← probe-at-startup + benchmark()
│ └── provider.rs ← WolfSslProvider: CryptoProvider impl
└── tests/
└── conformance/
├── Cargo.toml
└── src/
└── main.rs ← NIST/RFC vectors for all algorithms
Step 1 — Cargo Workspace Scaffold
``Cargo.toml``::
[workspace]
resolver = "2"
members = [
"crates/ccc-crypto-core",
"crates/ccc-crypto-wolfssl",
"tests/conformance",
]
``rust-toolchain.toml``::
[toolchain]
channel = "stable"
``.cargo/config.toml``::
[alias]
build-ios = "build --target aarch64-apple-ios"
build-android-arm64 = "build --target aarch64-linux-android"
build-android-x64 = "build --target x86_64-linux-android"
build-macos = "build --target aarch64-apple-darwin"
Step 2 — ``ccc-crypto-core`` Trait Crate
Algorithm Enumerations
~~~~~~~~~~~~~~~~~~~~~~
All discriminant values match the integer constants in ``cipher_constants.dart``
exactly, so the Dart cipher-sequencing logic requires zero changes.
=================== =========================================================
Enum Variants → value
=================== =========================================================
``AeadAlgorithm`` AesGcm256=12, ChaCha20Poly1305=13, XChaCha20Poly1305=14,
Ascon128a=15
``KdfAlgorithm`` Sha256=1, Sha384=2, Sha512=3, Blake2b512=4
``MacAlgorithm`` HmacSha256=30, HmacSha512=32, Blake2bMac=33
``HashAlgorithm`` Sha256=40, Sha384=41, Sha512=42, Blake2b512=43, Sha3_256=44
``KemAlgorithm`` X25519=50, X448=51, MlKem768=52, MlKem1024=53,
ClassicMcEliece=54
=================== =========================================================
Key Structs
~~~~~~~~~~~
``AlgorithmCapability``::
available: bool
deterministic_io: bool
efficiency_score: u8 // populated by WolfSslProvider::benchmark()
reliability_score: u8 // populated by WolfSslProvider::self_test()
``ProviderCapabilities``::
provider_name: &'static str
aead: HashMap<AeadAlgorithm, AlgorithmCapability>
kdf: HashMap<KdfAlgorithm, AlgorithmCapability>
mac: HashMap<MacAlgorithm, AlgorithmCapability>
hash: HashMap<HashAlgorithm, AlgorithmCapability>
kem: HashMap<KemAlgorithm, AlgorithmCapability>
``KemKeyPair``::
public_key: Zeroizing<Vec<u8>>
private_key: Zeroizing<Vec<u8>>
``CryptoError``::
UnsupportedAlgorithm(String)
InvalidKey(String)
InvalidNonce(String)
AuthenticationFailed
InternalError(String)
Provider Traits
~~~~~~~~~~~~~~~
.. code-block:: rust
pub trait AeadProvider {
fn encrypt_aead(
&self, algo: AeadAlgorithm,
key: &[u8], nonce: &[u8],
plaintext: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
fn decrypt_aead(
&self, algo: AeadAlgorithm,
key: &[u8], nonce: &[u8],
ciphertext: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
pub trait KdfProvider {
fn derive_key(
&self, algo: KdfAlgorithm,
ikm: &[u8], salt: &[u8], info: &[u8], length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
pub trait MacProvider {
fn compute_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
fn verify_mac(
&self, algo: MacAlgorithm, key: &[u8],
data: &[u8], mac: &[u8],
) -> Result<bool, CryptoError>;
}
pub trait HashProvider {
fn hash(
&self, algo: HashAlgorithm, data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
pub trait KemProvider {
fn generate_keypair(
&self, algo: KemAlgorithm,
) -> Result<KemKeyPair, CryptoError>;
fn encapsulate(
&self, algo: KemAlgorithm, public_key: &[u8],
) -> Result<(Vec<u8>, Zeroizing<Vec<u8>>), CryptoError>;
fn decapsulate(
&self, algo: KemAlgorithm,
private_key: &[u8], ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
pub trait CryptoProvider:
AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync
{
fn provider_name(&self) -> &'static str;
fn capabilities(&self) -> ProviderCapabilities;
fn self_test(&self) -> SelfTestReport;
fn benchmark(&self) -> BenchmarkReport;
}
``ProviderRegistry``::
OnceLock<Mutex<HashMap<&'static str, Box<dyn CryptoProvider + Send + Sync>>>>
fn register(name, provider)
fn get(name) -> Option<Arc<dyn CryptoProvider>>
fn list() -> Vec<&'static str>
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
wolfSSL is vendored as a git submodule pinned to ``v5.7.2-stable``.
The crate uses ``cmake`` + ``bindgen`` in ``build.rs`` to build and bind it.
A ``stub_ffi`` feature bypasses the C build for fast unit-test cycles.
wolfSSL Phase 4 Algorithm Coverage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
=================== ===========================================
Category Algorithms
=================== ===========================================
AEAD AES-256-GCM, ChaCha20-Poly1305,
XChaCha20-Poly1305
KDF HKDF-SHA256/384/512, Argon2id, BLAKE2b-KDF
MAC HMAC-SHA256/384/512, BLAKE2b-MAC
Hash SHA-256/384/512, SHA3-256/512, BLAKE2b-512
KEM X25519, X448
KEM (Phase 5) ML-KEM-768, ML-KEM-1024, Classic McEliece
=================== ===========================================
Capability Probe Strategy
~~~~~~~~~~~~~~~~~~~~~~~~~~
``WolfSslProvider::capabilities()`` runs a minimal probe call (encrypt 1 byte;
decrypt; compare) per algorithm at startup. Sets ``available = probe_succeeded``.
If the wolfSSL build omits PQ support, ML-KEM entries report
``available: false`` gracefully.
Benchmark Strategy
~~~~~~~~~~~~~~~~~~
``WolfSslProvider::benchmark()`` encrypts a 1 MB buffer × 100 iterations per
AEAD algorithm, measures wall-clock throughput, normalises to a 0100
``efficiency_score``. Run once at library init and cached.
Step 4 — Conformance Test Suite
Location: ``tests/conformance/src/main.rs``
Run with ``cargo run -p ccc-conformance-tests`` and ``cargo test --workspace``.
========================= ================================
Test Source
========================= ================================
AES-256-GCM NIST SP 800-38D vectors
ChaCha20-Poly1305 RFC 8439 vectors
XChaCha20-Poly1305 Extended nonce vectors
HKDF-SHA256/512 RFC 5869 vectors
HMAC-SHA256/512 RFC 4231 vectors
SHA-256/512, SHA3, BLAKE2b FIPS / reference vectors
X25519 DH RFC 7748 vectors
X448 DH RFC 7748 vectors
========================= ================================
**Gate**: ``ALL VECTORS PASSED`` must print before Milestone 1 is tagged.
Step 5 — Architecture Documentation
``docs/ccc_rust_milestone1.rst`` covers:
* Crate dependency graph (ASCII)
* "How to add a new provider" — 7-step trait checklist
* ``algo: u32`` → cipher constant mapping table
* Milestone 2 hand-off contract (API surface Milestone 2 must implement against)
Milestone 1 Verification Gate
All of the following must pass before the ``v0.1.0`` tag is cut and Milestone 2
work begins:
* ``cargo test --workspace`` — all unit tests pass
* ``cargo run -p ccc-conformance-tests````ALL VECTORS PASSED``
* ``cargo build --target aarch64-apple-ios`` — compiles
* ``cargo build --target aarch64-linux-android`` — compiles
* No ``flutter_rust_bridge``, Dart, or Flutter dependency anywhere in the workspace
* ``cargo audit`` — no known CVEs in dependency tree
Milestone 2 — ``ccc_dart_plugin`` (separate repository)
*(Planned — not started. Work begins after Milestone 1 gate passes.)*
A separate Dart/Flutter plugin package repository. It contains:
* A small Rust bridge crate that references ``ccc_rust`` via git tag:
.. code-block:: toml
[dependencies]
ccc-crypto-core = { git = "https://github.com/letusmsg/ccc_rust", tag = "v0.1.0" }
ccc-crypto-wolfssl = { git = "https://github.com/letusmsg/ccc_rust", tag = "v0.1.0" }
flutter_rust_bridge = "2"
* ``flutter_rust_bridge_codegen generate`` output (``frb_generated.rs``, Dart bindings)
* Flutter plugin scaffold: ``ios/``, ``android/``, ``macos/``
* Dart API surface: ``CccCrypto``, ``CccSelfTest``, ``CccProviderCatalog``
* Flutter integration tests (roundtrip encrypt/decrypt, self-test harness)
Milestone 3 — LetUsMsg App Integration (existing repository)
*(Planned — not started. Work begins after Milestone 2 gate passes.)*
The existing ``letusmsg`` Flutter app adds ``ccc_dart_plugin`` as a pubspec
dependency. Changes are confined to:
* ``crypto_wolfssl.dart`` — replace ``UnimplementedError`` stubs with plugin calls
* ``ccc_provider_spec.dart`` — populate ``CccProviderCatalog`` at runtime
* ``main.dart`` — call ``CccCrypto.cccInit()`` at startup
* App debug screen — expose ``CccSelfTest.runAll()`` results
No Rust changes and no bridge changes are made in Milestone 3.
Phase 8 — Stretch Goal Providers (Future)
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
================== =====================================================
Library Rust crate / approach
================== =====================================================
libsodium ``sodiumoxide`` or ``safe_libsodium``
OpenSSL ``openssl`` crate
BoringSSL ``boring`` crate
RustCrypto Pure-Rust trait impls; no native dep
liboqs Open Quantum Safe — ML-KEM, BIKE, HQC, Falcon,
Dilithium, SPHINCS+
Signal libsignal ``libsignal`` (Apache-2 subset)
Botan ``botan`` crate
mbedTLS ``mbedtls`` crate
Nettle ``nettle-sys`` crate
================== =====================================================

View File

@ -0,0 +1,264 @@
==============================================
CCC Rust Implementation — Phase Tracking
==============================================
:Last Updated: 2026-02-25
Legend
------
* ``[ ]`` Not started
* ``[~]`` In progress
* ``[x]`` Complete
* ``[!]`` Blocked
----
Three-Milestone Overview
------------------------
============= =================================== ============================
Milestone Repository Status
============= =================================== ============================
**1 (this)** ``ccc_rust`` Complete
**2** ``ccc_cryptography`` Not started
**3** ``letusmsg`` (existing app) Not started
============= =================================== ============================
Milestone 1 Verification Gate is now passing.
Milestone 2 may begin when scheduled.
Milestone 3 does not start until the Milestone 2 gate passes.
----
============================================================
Milestone 1 — ``ccc_rust`` Pure Rust Crypto Library
============================================================
Phase 1 — Cargo Workspace Scaffold
----------------------------------
* ``[x]`` Create ``Cargo.toml`` (workspace manifest, 3 members — no bridge crate)
* ``[x]`` Create ``rust-toolchain.toml`` (channel = "stable")
* ``[x]`` Create ``.cargo/config.toml`` (cross-compile target aliases)
* ``[x]`` Create ``vendors/README.md``
----
Phase 2 — ``ccc-crypto-core`` Trait Crate
-----------------------------------------
* ``[x]`` Create ``crates/ccc-crypto-core/Cargo.toml``
* ``[x]`` ``algorithms.rs`` — AeadAlgorithm, KdfAlgorithm, MacAlgorithm,
HashAlgorithm, KemAlgorithm enums (values == cipher_constants.dart)
* ``[x]`` ``capabilities.rs`` — AlgorithmCapability, ProviderCapabilities
* ``[x]`` ``error.rs`` — CryptoError enum
* ``[x]`` ``types.rs`` — KemKeyPair, SelfTestReport, BenchmarkReport,
AlgoTestResult
* ``[x]`` ``provider.rs`` — AeadProvider, KdfProvider, MacProvider,
HashProvider, KemProvider traits; CryptoProvider umbrella trait
* ``[x]`` ``registry.rs`` — ProviderRegistry (OnceLock<Mutex<...>>),
register(), get(), list()
* ``[x]`` ``lib.rs`` — re-exports all public items
* ``[x]`` Unit tests for registry (5 passing)
----
Phase 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
* ``[x]`` ``git submodule add`` wolfSSL → ``vendors/wolfssl``
* ``[x]`` Pin submodule to ``v5.7.2-stable``
* ``[x]`` Document pin in ``vendors/README.md``
* ``[x]`` Create ``crates/ccc-crypto-wolfssl/Cargo.toml``
* ``[x]`` ``build.rs`` — cmake build + bindgen; stub_ffi feature bypasses C build
* ``[x]`` ``aead.rs`` — AES-256-GCM implementation
* ``[x]`` encrypt_aead (AES-256-GCM)
* ``[x]`` decrypt_aead (AES-256-GCM)
* ``[x]`` encrypt_aead (ChaCha20-Poly1305)
* ``[x]`` decrypt_aead (ChaCha20-Poly1305)
* ``[x]`` encrypt_aead (XChaCha20-Poly1305 via HChaCha20)
* ``[x]`` decrypt_aead (XChaCha20-Poly1305)
* ``[x]`` ``kdf.rs`` — KDF implementations
* ``[x]`` HKDF-SHA256
* ``[x]`` HKDF-SHA384
* ``[x]`` HKDF-SHA512
* ``[x]`` Argon2id (64 MB / 3 iter / 4 threads — matches DEFAULT_CIPHER_PARAMS)
* ``[x]`` BLAKE2b-512 KDF
* ``[x]`` ``mac.rs`` — MAC implementations
* ``[x]`` HMAC-SHA256
* ``[x]`` HMAC-SHA384
* ``[x]`` HMAC-SHA512
* ``[x]`` BLAKE2b-MAC (keyed)
* ``[x]`` Constant-time verify
* ``[x]`` ``hash.rs`` — Hash implementations
* ``[x]`` SHA-256 / SHA-384 / SHA-512
* ``[x]`` SHA3-256 / SHA3-512
* ``[x]`` BLAKE2b-512
* ``[x]`` ``kem.rs`` — KEM implementations
* ``[x]`` X25519 (keygen + DH encap/decap)
* ``[x]`` X448 (keygen + DH encap/decap)
* ``[ ]`` ML-KEM-768 (deferred to Phase 5)
* ``[ ]`` ML-KEM-1024 (deferred to Phase 5)
* ``[ ]`` Classic McEliece (deferred to Phase 5)
* ``[x]`` ``capabilities.rs`` — probe-at-startup per algorithm
* ``[x]`` ``capabilities.rs`` — benchmark() throughput micro-bench
* ``[x]`` ``provider.rs`` — WolfSslProvider: CryptoProvider impl
* ``[x]`` ``provider.rs`` — self_test() with embedded NIST vectors (AES-256-GCM, ChaCha20-Poly1305)
* ``[x]`` Register WolfSslProvider in ProviderRegistry via init()
* ``[x]`` Full native build verified (cmake builds clean, all conformance tests pass)
----
Phase 4 — Conformance Test Suite
---------------------------------
* ``[x]`` NIST AES-256-GCM vectors (2 vectors)
* ``[x]`` RFC 8439 ChaCha20-Poly1305 vectors
* ``[x]`` RFC 5869 HKDF-SHA256 vectors (2 vectors)
* ``[x]`` RFC 4231 HMAC-SHA256 vectors (2 vectors)
* ``[x]`` FIPS hash vectors (SHA-256/512, SHA3-256, BLAKE2b-512)
* ``[x]`` RFC 7748 X25519 DH test vectors
* ``[x]`` RFC 7748 X448 DH test vectors
* ``[x]`` XChaCha20-Poly1305 extended-nonce vectors
* ``[x]`` ``cargo run -p ccc-conformance-tests`` passes (all current vectors)
----
Phase 5 — Architecture Documentation
--------------------------------------
* ``[x]`` Create ``docs/ccc_rust_milestone1.rst``
* ``[x]`` Crate dependency graph (ASCII diagram)
* ``[x]`` "How to add a new provider" — 7-step trait checklist
* ``[x]`` ``algo: u32`` → cipher constant mapping table
* ``[x]`` Milestone 2 hand-off contract documented
----
Milestone 1 Verification Gate
------------------------------
*All items must be checked before the* ``v0.1.0`` *tag is cut.*
* ``[x]`` ``cargo test --workspace`` — all pass
* ``[x]`` ``cargo run -p ccc-conformance-tests`` — ALL VECTORS PASSED
* ``[x]`` ``cargo build --target aarch64-apple-ios`` — success
* ``[x]`` ``cargo build --target aarch64-linux-android`` — success
* ``[x]`` No ``flutter_rust_bridge`` / Dart / Flutter dependency in workspace
* ``[x]`` ``cargo audit`` — no known CVEs
----
============================================================
Milestone 2 — ``ccc_cryptography`` Flutter Plugin
===========================================================
*(Not started — begins after Milestone 1 gate passes)*
Phase 1 — New Repository Setup
-------------------------------
* ``[ ]`` Create ``ccc_cryptography`` repository
* ``[ ]`` Flutter plugin scaffold (``pubspec.yaml``, ``ios/``, ``android/``, ``macos/``)
* ``[ ]`` Rust bridge crate with ``crate-type = ["cdylib", "staticlib"]``
* ``[ ]`` Add ``flutter_rust_bridge = "2"`` dependency
* ``[ ]`` Reference ``ccc_rust`` via git tag ``v0.1.0``
----
Phase 2 — Bridge Crate
----------------------
* ``[ ]`` ``dto.rs`` — CapabilitiesDto, KemKeyPairDto, KemEncapDto,
SelfTestDto, AlgoTestResultDto; From<core types> impls
* ``[ ]`` ``bridge.rs`` — ccc_init()
* ``[ ]`` ``bridge.rs`` — ccc_list_providers()
* ``[ ]`` ``bridge.rs`` — ccc_capabilities() / ccc_available_algorithms()
* ``[ ]`` ``bridge.rs`` — ccc_aead_encrypt() / ccc_aead_decrypt()
* ``[ ]`` ``bridge.rs`` — ccc_kdf_derive()
* ``[ ]`` ``bridge.rs`` — ccc_mac_compute() / ccc_mac_verify()
* ``[ ]`` ``bridge.rs`` — ccc_hash()
* ``[ ]`` ``bridge.rs`` — ccc_kem_generate_keypair()
* ``[ ]`` ``bridge.rs`` — ccc_kem_encapsulate() / ccc_kem_decapsulate()
* ``[ ]`` ``bridge.rs`` — ccc_self_test()
----
Phase 3 — Codegen + Plugin Build
---------------------------------
* ``[ ]`` Run ``flutter_rust_bridge_codegen generate``
* ``[ ]`` Verify generated Dart bindings compile
* ``[ ]`` ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` ``flutter build apk`` succeeds (cdylib linked)
* ``[ ]`` ``flutter build macos`` succeeds
----
Phase 4 — Dart API Layer
------------------------
* ``[ ]`` ``CccCrypto`` class (wraps all bridge calls)
* ``[ ]`` ``CccSelfTest`` class (wraps ccc_self_test())
* ``[ ]`` ``CccProviderCatalog`` (runtime-populated from ccc_capabilities())
----
Phase 5 — Flutter Integration Tests
------------------------------------
* ``[ ]`` Roundtrip encrypt/decrypt 1 KB (AES-256-GCM)
* ``[ ]`` Roundtrip encrypt/decrypt 1 KB (ChaCha20-Poly1305)
* ``[ ]`` ``CccSelfTest.runAll()`` — all-pass
----
Milestone 2 Verification Gate
------------------------------
* ``[ ]`` All Flutter integration tests pass on iOS simulator
* ``[ ]`` All Flutter integration tests pass on Android emulator
* ``[ ]`` Package published / tagged ``v0.1.0``
----
============================================================
Milestone 3 — LetUsMsg App Integration
============================================================
*(Not started — begins after Milestone 2 gate passes)*
* ``[ ]`` Add ``ccc_cryptography`` to ``letusmsg`` `pubspec.yaml``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` encrypt/decrypt → bridge calls
* ``[ ]`` Call ``CccCrypto.cccInit()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from runtime capabilities
* ``[ ]`` Expose ``CccSelfTest.runAll()`` in app debug screen
* ``[ ]`` End-to-end integration test (send + receive encrypted message)
----
Phase 8 — Stretch Goal Providers (Future)
------------------------------------------
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
* ``[ ]`` libsodium (``sodiumoxide`` / ``safe_libsodium``)
* ``[ ]`` OpenSSL (``openssl`` crate)
* ``[ ]`` BoringSSL (``boring`` crate)
* ``[ ]`` RustCrypto (pure-Rust, no native dep)
* ``[ ]`` liboqs — ML-KEM, BIKE, HQC, Falcon, Dilithium, SPHINCS+
* ``[ ]`` Signal ``libsignal``
* ``[ ]`` Botan
* ``[ ]`` mbedTLS
* ``[ ]`` Nettle

View File

@ -0,0 +1,414 @@
// Integration tests for the CccCrypto public API.
//
// These exercise every bridge entry point through the clean Dart façade.
// All tests require the native library, so they run as integration tests
// via `flutter test integration_test/ccc_crypto_test.dart -d macos`.
import 'dart:convert';
import 'dart:typed_data';
import 'package:ccc_cryptography/ccc_cryptography.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
/// Helper: hex string Uint8List.
Uint8List _hex(String hex) {
final bytes = <int>[];
for (var i = 0; i < hex.length; i += 2) {
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
}
return Uint8List.fromList(bytes);
}
/// Helper: Uint8List lowercase hex string.
String _toHex(Uint8List data) =>
data.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
await CccCrypto.init();
});
//
// 5.1 AEAD roundtrip AES-256-GCM
//
test('5.1 AEAD roundtrip AES-256-GCM', () async {
final key = Uint8List(32); // all zeros fine for a test
final nonce = Uint8List(12);
final plaintext = Uint8List(1024); // 1 KB of zeros
for (var i = 0; i < plaintext.length; i++) {
plaintext[i] = i & 0xff;
}
final ciphertext = await CccCrypto.encryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: key,
nonce: nonce,
plaintext: plaintext,
);
// Ciphertext must be longer than plaintext (appended auth tag).
expect(ciphertext.length, greaterThan(plaintext.length));
final decrypted = await CccCrypto.decryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: key,
nonce: nonce,
ciphertext: ciphertext,
);
expect(decrypted, equals(plaintext));
});
//
// 5.2 AEAD roundtrip ChaCha20-Poly1305
//
test('5.2 AEAD roundtrip ChaCha20-Poly1305', () async {
final key = Uint8List(32);
final nonce = Uint8List(12);
final plaintext = Uint8List(1024);
for (var i = 0; i < plaintext.length; i++) {
plaintext[i] = (i * 3) & 0xff;
}
final ciphertext = await CccCrypto.encryptAead(
algorithm: CccAeadAlgorithm.chaCha20Poly1305,
key: key,
nonce: nonce,
plaintext: plaintext,
);
expect(ciphertext.length, greaterThan(plaintext.length));
final decrypted = await CccCrypto.decryptAead(
algorithm: CccAeadAlgorithm.chaCha20Poly1305,
key: key,
nonce: nonce,
ciphertext: ciphertext,
);
expect(decrypted, equals(plaintext));
});
//
// 5.3 AEAD tamper test modifying ciphertext fails authentication
//
test('5.3 AEAD tamper test throws CccAuthenticationFailed', () async {
final key = Uint8List(32);
final nonce = Uint8List(12);
final plaintext = Uint8List.fromList(utf8.encode('Hello, tamper test!'));
final ciphertext = await CccCrypto.encryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: key,
nonce: nonce,
plaintext: plaintext,
);
// Flip a byte in the ciphertext body (not the tag).
final tampered = Uint8List.fromList(ciphertext);
tampered[0] ^= 0xff;
expect(
() => CccCrypto.decryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: key,
nonce: nonce,
ciphertext: tampered,
),
throwsA(isA<CccAuthenticationFailed>()),
);
});
//
// 5.4 KDF known-vector HKDF-SHA256 (RFC 5869 Test Case 1)
//
test('5.4 KDF HKDF-SHA256 RFC 5869 Test Case 1', () async {
final ikm = _hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
final salt = _hex('000102030405060708090a0b0c');
final info = _hex('f0f1f2f3f4f5f6f7f8f9');
const length = 42;
final expectedOkm = _hex(
'3cb25f25faacd57a90434f64d0362f2a'
'2d2d0a90cf1a5a4c5db02d56ecc4c5bf'
'34007208d5b887185865',
);
final okm = await CccCrypto.deriveKey(
algorithm: CccKdfAlgorithm.sha256,
ikm: ikm,
salt: salt,
info: info,
length: length,
);
expect(okm.length, equals(length));
expect(_toHex(okm), equals(_toHex(expectedOkm)));
});
//
// 5.5 MAC roundtrip HMAC-SHA256 compute then verify
//
test('5.5 MAC roundtrip HMAC-SHA256', () async {
final key = Uint8List(32);
for (var i = 0; i < key.length; i++) {
key[i] = i & 0xff;
}
final data = Uint8List.fromList(utf8.encode('MAC roundtrip test data'));
final mac = await CccCrypto.computeMac(
algorithm: CccMacAlgorithm.hmacSha256,
key: key,
data: data,
);
// HMAC-SHA256 produces 32-byte tag.
expect(mac.length, equals(32));
final valid = await CccCrypto.verifyMac(
algorithm: CccMacAlgorithm.hmacSha256,
key: key,
data: data,
mac: mac,
);
expect(valid, isTrue);
});
//
// 5.6 MAC tamper test modified data fails verification
//
test('5.6 MAC tamper test returns false', () async {
final key = Uint8List(32);
final data = Uint8List.fromList(utf8.encode('original data'));
final mac = await CccCrypto.computeMac(
algorithm: CccMacAlgorithm.hmacSha256,
key: key,
data: data,
);
// Modify the data after MAC was computed.
final tamperedData = Uint8List.fromList(utf8.encode('tampered data'));
final valid = await CccCrypto.verifyMac(
algorithm: CccMacAlgorithm.hmacSha256,
key: key,
data: tamperedData,
mac: mac,
);
expect(valid, isFalse);
});
//
// 5.7 Hash known-vector SHA-256 / SHA-384 / SHA-512
//
test('5.7a Hash SHA-256 known vector', () async {
final data = Uint8List.fromList(utf8.encode('abc'));
final expected = _hex(
'ba7816bf8f01cfea414140de5dae2223'
'b00361a396177a9cb410ff61f20015ad',
);
final digest = await CccCrypto.hash(
algorithm: CccHashAlgorithm.sha256,
data: data,
);
expect(digest.length, equals(32));
expect(_toHex(digest), equals(_toHex(expected)));
});
test('5.7b Hash SHA-384 known vector', () async {
final data = Uint8List.fromList(utf8.encode('abc'));
final expected = _hex(
'cb00753f45a35e8bb5a03d699ac65007'
'272c32ab0eded1631a8b605a43ff5bed'
'8086072ba1e7cc2358baeca134c825a7',
);
final digest = await CccCrypto.hash(
algorithm: CccHashAlgorithm.sha384,
data: data,
);
expect(digest.length, equals(48));
expect(_toHex(digest), equals(_toHex(expected)));
});
test('5.7c Hash SHA-512 known vector', () async {
final data = Uint8List.fromList(utf8.encode('abc'));
final expected = _hex(
'ddaf35a193617abacc417349ae204131'
'12e6fa4e89a97ea20a9eeee64b55d39a'
'2192992a274fc1a836ba3c23a3feebbd'
'454d4423643ce80e2a9ac94fa54ca49f',
);
final digest = await CccCrypto.hash(
algorithm: CccHashAlgorithm.sha512,
data: data,
);
expect(digest.length, equals(64));
expect(_toHex(digest), equals(_toHex(expected)));
});
//
// 5.8 KEM roundtrip X25519
//
test('5.8 KEM roundtrip X25519', () async {
final kp = await CccCrypto.kemGenerateKeypair(
algorithm: CccKemAlgorithm.x25519,
);
expect(kp.publicKey.length, equals(32));
expect(kp.privateKey.length, equals(32));
final encap = await CccCrypto.kemEncapsulate(
algorithm: CccKemAlgorithm.x25519,
publicKey: kp.publicKey,
);
expect(encap.sharedSecret.length, equals(32));
expect(encap.ciphertext.length, equals(32));
final decapSecret = await CccCrypto.kemDecapsulate(
algorithm: CccKemAlgorithm.x25519,
privateKey: kp.privateKey,
ciphertext: encap.ciphertext,
);
expect(decapSecret, equals(encap.sharedSecret));
});
//
// 5.9 KEM roundtrip X448
//
test('5.9 KEM roundtrip X448', () async {
final kp = await CccCrypto.kemGenerateKeypair(
algorithm: CccKemAlgorithm.x448,
);
expect(kp.publicKey.length, equals(56));
expect(kp.privateKey.length, equals(56));
final encap = await CccCrypto.kemEncapsulate(
algorithm: CccKemAlgorithm.x448,
publicKey: kp.publicKey,
);
expect(encap.sharedSecret.length, equals(56));
expect(encap.ciphertext.length, equals(56));
final decapSecret = await CccCrypto.kemDecapsulate(
algorithm: CccKemAlgorithm.x448,
privateKey: kp.privateKey,
ciphertext: encap.ciphertext,
);
expect(decapSecret, equals(encap.sharedSecret));
});
//
// 5.10 Provider catalog
//
test('5.10 Provider catalog', () async {
final providers = CccCrypto.listProviders();
expect(providers, isNotEmpty);
expect(providers, contains('wolfssl'));
final catalog = await CccCrypto.getCapabilities();
expect(catalog.providerName, equals('wolfssl'));
// Each category should have at least one algorithm.
expect(catalog.aead, isNotEmpty);
expect(catalog.kdf, isNotEmpty);
expect(catalog.mac, isNotEmpty);
expect(catalog.hash, isNotEmpty);
expect(catalog.kem, isNotEmpty);
// Check that availability and scores are populated.
for (final algo in catalog.availableAead) {
expect(algo.name, isNotEmpty);
expect(algo.efficiencyScore, greaterThanOrEqualTo(0));
expect(algo.reliabilityScore, greaterThanOrEqualTo(0));
}
});
//
// 5.11 Self-test validation
//
test('5.11 Self-test validation', () async {
final result = await CccCrypto.runSelfTest();
expect(result.providerName, equals('wolfssl'));
expect(result.results, isNotEmpty);
expect(result.allPassed, isTrue,
reason: 'Failures: ${result.failures.map((r) => '${r.algoName}: ${r.errorMessage}').join(', ')}');
});
//
// 5.12 Error handling typed exceptions
//
test('5.12a Error: invalid key length throws CccInvalidKey', () async {
// AES-256-GCM requires 32-byte key; provide 16 bytes.
final shortKey = Uint8List(16);
final nonce = Uint8List(12);
final plaintext = Uint8List.fromList(utf8.encode('test'));
expect(
() => CccCrypto.encryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: shortKey,
nonce: nonce,
plaintext: plaintext,
),
throwsA(isA<CccException>()),
);
});
test('5.12b Error: invalid nonce length throws CccException', () async {
final key = Uint8List(32);
// AES-GCM nonce should be 12 bytes; provide 8.
final shortNonce = Uint8List(8);
final plaintext = Uint8List.fromList(utf8.encode('test'));
expect(
() => CccCrypto.encryptAead(
algorithm: CccAeadAlgorithm.aesGcm256,
key: key,
nonce: shortNonce,
plaintext: plaintext,
),
throwsA(isA<CccException>()),
);
});
//
// 5.13 Algorithm ID mapping enum discriminants match Rust repr(u32)
//
test('5.13 Algorithm ID mapping', () async {
// Verify the catalog reports algorithm IDs matching Rust enum discriminants.
final catalog = await CccCrypto.getCapabilities();
// AEAD: AesGcm256 = 12, ChaCha20Poly1305 = 13, XChaCha20Poly1305 = 14
final aeadIds = {for (final a in catalog.aead) a.name: a.id};
expect(aeadIds['AES-256-GCM'], equals(12));
expect(aeadIds['ChaCha20-Poly1305'], equals(13));
// Hash: Sha256 = 40, Sha384 = 41, Sha512 = 42
final hashIds = {for (final a in catalog.hash) a.name: a.id};
expect(hashIds['SHA-256'], equals(40));
expect(hashIds['SHA-384'], equals(41));
expect(hashIds['SHA-512'], equals(42));
// KEM: X25519 = 50, X448 = 51
final kemIds = {for (final a in catalog.kem) a.name: a.id};
expect(kemIds['X25519'], equals(50));
expect(kemIds['X448'], equals(51));
});
}

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

43
example/ios/Podfile Normal file
View File

@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -14,6 +14,8 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B6F5CBF28A5AED1D91C67990 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA91E11C618D9A599FFAA263 /* Pods_RunnerTests.framework */; };
EDAD8E7F06F64A81B3A5C0C2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FED151958504C181ACC8B7C1 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -40,14 +42,17 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
10458A559A1BAA9A40196C6D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4AF37E1D71E0F25D8BA5F8FD /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
906E8CF8E280ABC053ECC0A2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -55,6 +60,11 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CA91E11C618D9A599FFAA263 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D47ACED1129408D2F3AF66D0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
DA2CEF79122E98943D4DD5F3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
E28B19D3AE6076D6F92EDB36 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
FED151958504C181ACC8B7C1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -62,6 +72,15 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EDAD8E7F06F64A81B3A5C0C2 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C16C09A3B0FC34A2C018419D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B6F5CBF28A5AED1D91C67990 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -76,6 +95,15 @@
path = RunnerTests;
sourceTree = "<group>";
};
3411DCCD962964321A55906E /* Frameworks */ = {
isa = PBXGroup;
children = (
FED151958504C181ACC8B7C1 /* Pods_Runner.framework */,
CA91E11C618D9A599FFAA263 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -94,6 +122,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
B594E4F1914A4F3EB9181D75 /* Pods */,
3411DCCD962964321A55906E /* Frameworks */,
);
sourceTree = "<group>";
};
@ -121,6 +151,20 @@
path = Runner;
sourceTree = "<group>";
};
B594E4F1914A4F3EB9181D75 /* Pods */ = {
isa = PBXGroup;
children = (
10458A559A1BAA9A40196C6D /* Pods-Runner.debug.xcconfig */,
E28B19D3AE6076D6F92EDB36 /* Pods-Runner.release.xcconfig */,
DA2CEF79122E98943D4DD5F3 /* Pods-Runner.profile.xcconfig */,
4AF37E1D71E0F25D8BA5F8FD /* Pods-RunnerTests.debug.xcconfig */,
906E8CF8E280ABC053ECC0A2 /* Pods-RunnerTests.release.xcconfig */,
D47ACED1129408D2F3AF66D0 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -128,8 +172,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
53A6DF3E8C78EC92FF484B14 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
C16C09A3B0FC34A2C018419D /* Frameworks */,
);
buildRules = (
);
@ -145,12 +191,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
77834BA3CCE54739B52F93C3 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C9725E95439E143BF1E75A7A /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -238,6 +286,50 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
53A6DF3E8C78EC92FF484B14 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
77834BA3CCE54739B52F93C3 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -253,6 +345,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C9725E95439E143BF1E75A7A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -379,6 +488,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4AF37E1D71E0F25D8BA5F8FD /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -396,6 +506,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 906E8CF8E280ABC053ECC0A2 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -411,6 +522,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D47ACED1129408D2F3AF66D0 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:ccc_cryptography/ccc_cryptography.dart';
import 'package:ccc_cryptography/ccc_cryptography.dart' as ccc_cryptography;
void main() {
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await CccCrypto.init();
runApp(const MyApp());
}
@ -15,58 +15,36 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State<MyApp> {
late int sumResult;
late Future<int> sumAsyncResult;
String _status = 'Running self-test…';
@override
void initState() {
super.initState();
sumResult = ccc_cryptography.sum(1, 2);
sumAsyncResult = ccc_cryptography.sumAsync(3, 4);
_runSelfTest();
}
Future<void> _runSelfTest() async {
try {
final result = await CccCrypto.runSelfTest();
final providers = CccCrypto.listProviders();
setState(() {
_status = 'Providers: ${providers.join(", ")}\n'
'Self-test: ${result.allPassed ? "ALL PASSED" : "FAILED"}\n'
'${result.results.map((r) => ' ${r.algoName}: ${r.passed ? "" : "${r.errorMessage}"}').join("\n")}';
});
} on CccException catch (e) {
setState(() => _status = 'Error: $e');
}
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Native Packages'),
),
body: SingleChildScrollView(
child: Container(
padding: const .all(10),
child: Column(
children: [
const Text(
'This calls a native function through FFI that is shipped as source in the package. '
'The native code is built as part of the Flutter Runner build.',
style: textStyle,
textAlign: .center,
),
spacerSmall,
Text(
'sum(1, 2) = $sumResult',
style: textStyle,
textAlign: .center,
),
spacerSmall,
FutureBuilder<int>(
future: sumAsyncResult,
builder: (BuildContext context, AsyncSnapshot<int> value) {
final displayValue =
(value.hasData) ? value.data : 'loading';
return Text(
'await sumAsync(3, 4) = $displayValue',
style: textStyle,
textAlign: .center,
);
},
),
],
),
),
appBar: AppBar(title: const Text('ccc_cryptography')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Text(_status, style: const TextStyle(fontSize: 16)),
),
),
);

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

42
example/macos/Podfile Normal file
View File

@ -0,0 +1,42 @@
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View File

@ -27,6 +27,8 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
C9D9D80953306779817A01D7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 038972E62E6D13C2A55A0B7F /* Pods_Runner.framework */; };
CC6FDE117C14C0F98D1BA8BB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5AA6C665A590100A722C2D2 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -60,11 +62,13 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
038972E62E6D13C2A55A0B7F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0988C826A2FD255462F9808A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* ccc_cryptography_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ccc_cryptography_example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* ccc_cryptography_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ccc_cryptography_example.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -77,7 +81,13 @@
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
86D70DA30699567E710A0459 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
9D35F14235952B671983F7E0 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
A5B81D3E19756CB2D0C8B009 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
A81A44EB7B90A16EAADE4FA6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
BA626D8003BA6E48536EE306 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D5AA6C665A590100A722C2D2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CC6FDE117C14C0F98D1BA8BB /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -92,6 +103,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C9D9D80953306779817A01D7 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -125,6 +137,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
DE0D4D416823266B5CAAEDA7 /* Pods */,
);
sourceTree = "<group>";
};
@ -175,10 +188,26 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
038972E62E6D13C2A55A0B7F /* Pods_Runner.framework */,
D5AA6C665A590100A722C2D2 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
DE0D4D416823266B5CAAEDA7 /* Pods */ = {
isa = PBXGroup;
children = (
0988C826A2FD255462F9808A /* Pods-Runner.debug.xcconfig */,
86D70DA30699567E710A0459 /* Pods-Runner.release.xcconfig */,
BA626D8003BA6E48536EE306 /* Pods-Runner.profile.xcconfig */,
A81A44EB7B90A16EAADE4FA6 /* Pods-RunnerTests.debug.xcconfig */,
9D35F14235952B671983F7E0 /* Pods-RunnerTests.release.xcconfig */,
A5B81D3E19756CB2D0C8B009 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -186,6 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
086D907707E0A9BB81BA038A /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C86250B37FA1453AAEC9A1C2 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
78F7E7C7FE2E75BBB3819847 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -291,6 +323,28 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
086D907707E0A9BB81BA038A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -329,6 +383,45 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
78F7E7C7FE2E75BBB3819847 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C86250B37FA1453AAEC9A1C2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -380,6 +473,7 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A81A44EB7B90A16EAADE4FA6 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -394,6 +488,7 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9D35F14235952B671983F7E0 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -408,6 +503,7 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A5B81D3E19756CB2D0C8B009 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -53,6 +53,8 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
integration_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -0,0 +1,3 @@
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();

View File

@ -1,19 +0,0 @@
# Run with `dart run ffigen --config ffigen.yaml`.
name: CccCryptographyBindings
description: |
Bindings for `src/ccc_cryptography.h`.
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
output: 'lib/ccc_cryptography_bindings_generated.dart'
headers:
entry-points:
- 'src/ccc_cryptography.h'
include-directives:
- 'src/ccc_cryptography.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full

3
flutter_rust_bridge.yaml Normal file
View File

@ -0,0 +1,3 @@
rust_input: crate::api
dart_output: lib/src/rust
rust_root: rust/

View File

@ -1,3 +0,0 @@
// Relative import to be able to reuse the C sources.
// See the comment in ../ccc_cryptography.podspec for more information.
#include "../../src/ccc_cryptography.c"

1
ios/Classes/dummy_file.c Normal file
View File

@ -0,0 +1 @@
// This is an empty file to force CocoaPods to create a framework.

View File

@ -20,9 +20,26 @@ A new Flutter FFI plugin project.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '13.0'
s.platform = :ios, '11.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
s.script_phase = {
:name => 'Build Rust library',
# First argument is relative path to the `rust` folder, second is name of rust library
:script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust ccc_cryptography',
:execution_position => :before_compile,
:input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'],
# Let XCode know that the static library referenced in -force_load below is
# created by this build step.
:output_files => ["${BUILT_PRODUCTS_DIR}/libccc_cryptography.a"],
}
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
# Flutter.framework does not contain a i386 slice.
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libccc_cryptography.a',
}
end

235
lib/ccc_crypto.dart Normal file
View File

@ -0,0 +1,235 @@
/// Primary entry point for CCC cryptographic operations.
///
/// All methods delegate to the Rust bridge via `flutter_rust_bridge`.
/// No cryptographic logic exists in Dart.
///
/// Usage:
/// ```dart
/// await CccCrypto.init();
/// final ct = await CccCrypto.encryptAead(
/// algorithm: CccAeadAlgorithm.aesGcm256,
/// key: key,
/// nonce: nonce,
/// plaintext: data,
/// aad: Uint8List(0),
/// );
/// ```
library;
import 'dart:typed_data';
import 'package:ccc_cryptography/ccc_exceptions.dart';
import 'package:ccc_cryptography/ccc_provider_catalog.dart';
import 'package:ccc_cryptography/ccc_self_test.dart';
import 'package:ccc_cryptography/src/rust/api/crypto.dart' as bridge;
import 'package:ccc_cryptography/src/rust/api/dto.dart';
import 'package:ccc_cryptography/src/rust/frb_generated.dart';
/// Static façade for all CCC cryptographic operations.
///
/// Call [init] once at application startup before using any other method.
abstract final class CccCrypto {
/// Initialise the FRB runtime and register the wolfSSL provider.
///
/// Must be called once before any other [CccCrypto] method.
/// Safe to call multiple times (idempotent).
static Future<void> init() async {
await RustLib.init();
await bridge.cccInit();
}
// Provider info
/// Return the names of all registered crypto providers.
static List<String> listProviders() => bridge.cccListProviders();
/// Return the capability catalog for the default provider.
static Future<CccProviderCatalog> getCapabilities() async {
try {
final caps = await bridge.cccCapabilities();
return CccProviderCatalog.fromCapabilities(caps);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// AEAD
/// Encrypt [plaintext] with an AEAD algorithm.
///
/// Returns ciphertext with appended authentication tag.
static Future<Uint8List> encryptAead({
required CccAeadAlgorithm algorithm,
required Uint8List key,
required Uint8List nonce,
required Uint8List plaintext,
Uint8List? aad,
}) async {
try {
return await bridge.cccAeadEncrypt(
algorithm: algorithm,
key: key,
nonce: nonce,
plaintext: plaintext,
aad: aad ?? Uint8List(0),
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
/// Decrypt [ciphertext] (with appended tag) using an AEAD algorithm.
///
/// Throws [CccAuthenticationFailed] if the tag does not verify.
static Future<Uint8List> decryptAead({
required CccAeadAlgorithm algorithm,
required Uint8List key,
required Uint8List nonce,
required Uint8List ciphertext,
Uint8List? aad,
}) async {
try {
return await bridge.cccAeadDecrypt(
algorithm: algorithm,
key: key,
nonce: nonce,
ciphertext: ciphertext,
aad: aad ?? Uint8List(0),
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// KDF
/// Derive key material of [length] bytes.
static Future<Uint8List> deriveKey({
required CccKdfAlgorithm algorithm,
required Uint8List ikm,
Uint8List? salt,
Uint8List? info,
required int length,
}) async {
try {
return await bridge.cccKdfDerive(
algorithm: algorithm,
ikm: ikm,
salt: salt ?? Uint8List(0),
info: info ?? Uint8List(0),
length: length,
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// MAC
/// Compute a MAC tag over [data].
static Future<Uint8List> computeMac({
required CccMacAlgorithm algorithm,
required Uint8List key,
required Uint8List data,
}) async {
try {
return await bridge.cccMacCompute(
algorithm: algorithm,
key: key,
data: data,
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
/// Verify a MAC tag. Returns `true` if valid, `false` otherwise.
static Future<bool> verifyMac({
required CccMacAlgorithm algorithm,
required Uint8List key,
required Uint8List data,
required Uint8List mac,
}) async {
try {
return await bridge.cccMacVerify(
algorithm: algorithm,
key: key,
data: data,
mac: mac,
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// Hash
/// Compute a cryptographic hash of [data].
static Future<Uint8List> hash({
required CccHashAlgorithm algorithm,
required Uint8List data,
}) async {
try {
return await bridge.cccHash(algorithm: algorithm, data: data);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// KEM
/// Generate a KEM key pair.
static Future<CccKemKeyPair> kemGenerateKeypair({
required CccKemAlgorithm algorithm,
}) async {
try {
return await bridge.cccKemGenerateKeypair(algorithm: algorithm);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
/// KEM encapsulation produce ciphertext + shared secret from a public key.
static Future<CccKemEncapResult> kemEncapsulate({
required CccKemAlgorithm algorithm,
required Uint8List publicKey,
}) async {
try {
return await bridge.cccKemEncapsulate(
algorithm: algorithm,
publicKey: publicKey,
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
/// KEM decapsulation recover shared secret from ciphertext + private key.
static Future<Uint8List> kemDecapsulate({
required CccKemAlgorithm algorithm,
required Uint8List privateKey,
required Uint8List ciphertext,
}) async {
try {
return await bridge.cccKemDecapsulate(
algorithm: algorithm,
privateKey: privateKey,
ciphertext: ciphertext,
);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
// Self-test
/// Run the provider self-test suite and return a structured report.
static Future<CccSelfTestResult> runSelfTest() async {
try {
final report = await bridge.cccSelfTest();
return CccSelfTestResult.fromReport(report);
} on CccCryptoError catch (e) {
throw CccException.from(e);
}
}
}

View File

@ -1,131 +1,39 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'ccc_cryptography_bindings_generated.dart';
/// A very short-lived native function.
/// CCC Cryptography Flutter plugin providing cross-platform
/// cryptographic operations via `ccc_rust` and `flutter_rust_bridge`.
///
/// For very short-lived functions, it is fine to call them on the main isolate.
/// They will block the Dart execution while running the native function, so
/// only do this for native functions which are guaranteed to be short-lived.
int sum(int a, int b) => _bindings.sum(a, b);
/// A longer lived native function, which occupies the thread calling it.
/// Usage:
/// ```dart
/// import 'package:ccc_cryptography/ccc_cryptography.dart';
///
/// Do not call these kind of native functions in the main isolate. They will
/// block Dart execution. This will cause dropped frames in Flutter applications.
/// Instead, call these native functions on a separate isolate.
///
/// Modify this to suit your own use case. Example use cases:
///
/// 1. Reuse a single isolate for various different kinds of requests.
/// 2. Use multiple helper isolates for parallel execution.
Future<int> sumAsync(int a, int b) async {
final SendPort helperIsolateSendPort = await _helperIsolateSendPort;
final int requestId = _nextSumRequestId++;
final _SumRequest request = _SumRequest(requestId, a, b);
final Completer<int> completer = Completer<int>();
_sumRequests[requestId] = completer;
helperIsolateSendPort.send(request);
return completer.future;
}
/// await CccCrypto.init();
/// final ct = await CccCrypto.encryptAead(
/// algorithm: CccAeadAlgorithm.aesGcm256,
/// key: key, nonce: nonce, plaintext: data,
/// );
/// ```
library;
const String _libName = 'ccc_cryptography';
// Primary API
export 'ccc_crypto.dart';
/// The dynamic library in which the symbols for [CccCryptographyBindings] can be found.
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
// Algorithm enum re-exports (FRB-generated)
export 'src/rust/api/dto.dart'
show
CccAeadAlgorithm,
CccKdfAlgorithm,
CccMacAlgorithm,
CccHashAlgorithm,
CccKemAlgorithm,
CccKemKeyPair,
CccKemEncapResult;
/// The bindings to the native functions in [_dylib].
final CccCryptographyBindings _bindings = CccCryptographyBindings(_dylib);
// Exceptions
export 'ccc_exceptions.dart';
// Provider catalog
export 'ccc_provider_catalog.dart';
// Self-test result
export 'ccc_self_test.dart';
/// A request to compute `sum`.
///
/// Typically sent from one isolate to another.
class _SumRequest {
final int id;
final int a;
final int b;
const _SumRequest(this.id, this.a, this.b);
}
/// A response with the result of `sum`.
///
/// Typically sent from one isolate to another.
class _SumResponse {
final int id;
final int result;
const _SumResponse(this.id, this.result);
}
/// Counter to identify [_SumRequest]s and [_SumResponse]s.
int _nextSumRequestId = 0;
/// Mapping from [_SumRequest] `id`s to the completers corresponding to the correct future of the pending request.
final Map<int, Completer<int>> _sumRequests = <int, Completer<int>>{};
/// The SendPort belonging to the helper isolate.
Future<SendPort> _helperIsolateSendPort = () async {
// The helper isolate is going to send us back a SendPort, which we want to
// wait for.
final Completer<SendPort> completer = Completer<SendPort>();
// Receive port on the main isolate to receive messages from the helper.
// We receive two types of messages:
// 1. A port to send messages on.
// 2. Responses to requests we sent.
final ReceivePort receivePort = ReceivePort()
..listen((dynamic data) {
if (data is SendPort) {
// The helper isolate sent us the port on which we can sent it requests.
completer.complete(data);
return;
}
if (data is _SumResponse) {
// The helper isolate sent us a response to a request we sent.
final Completer<int> completer = _sumRequests[data.id]!;
_sumRequests.remove(data.id);
completer.complete(data.result);
return;
}
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
});
// Start the helper isolate.
await Isolate.spawn((SendPort sendPort) async {
final ReceivePort helperReceivePort = ReceivePort()
..listen((dynamic data) {
// On the helper isolate listen to requests and respond to them.
if (data is _SumRequest) {
final int result = _bindings.sum_long_running(data.a, data.b);
final _SumResponse response = _SumResponse(data.id, result);
sendPort.send(response);
return;
}
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
});
// Send the port to the main isolate on which we can receive requests.
sendPort.send(helperReceivePort.sendPort);
}, receivePort.sendPort);
// Wait until the helper isolate has sent us back the SendPort on which we
// can start sending requests.
return completer.future;
}();

View File

@ -1,69 +0,0 @@
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
import 'dart:ffi' as ffi;
/// Bindings for `src/ccc_cryptography.h`.
///
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
///
class CccCryptographyBindings {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
CccCryptographyBindings(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
/// The symbols are looked up with [lookup].
CccCryptographyBindings.fromLookup(
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
lookup)
: _lookup = lookup;
/// A very short-lived native function.
///
/// For very short-lived functions, it is fine to call them on the main isolate.
/// They will block the Dart execution while running the native function, so
/// only do this for native functions which are guaranteed to be short-lived.
int sum(
int a,
int b,
) {
return _sum(
a,
b,
);
}
late final _sumPtr =
_lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>('sum');
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
/// A longer lived native function, which occupies the thread calling it.
///
/// Do not call these kind of native functions in the main isolate. They will
/// block Dart execution. This will cause dropped frames in Flutter applications.
/// Instead, call these native functions on a separate isolate.
int sum_long_running(
int a,
int b,
) {
return _sum_long_running(
a,
b,
);
}
late final _sum_long_runningPtr =
_lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>(
'sum_long_running');
late final _sum_long_running =
_sum_long_runningPtr.asFunction<int Function(int, int)>();
}

70
lib/ccc_exceptions.dart Normal file
View File

@ -0,0 +1,70 @@
/// Structured exception classes for CCC cryptographic operations.
///
/// These wrap the FRB-generated [CccCryptoError] into typed Dart exceptions
/// so consuming code can catch specific failure modes.
library;
import 'package:ccc_cryptography/src/rust/api/dto.dart';
/// Base class for all CCC cryptographic exceptions.
sealed class CccException implements Exception {
/// Human-readable error description.
final String message;
const CccException(this.message);
@override
String toString() => '$runtimeType: $message';
/// Convert a FRB-generated [CccCryptoError] into the appropriate
/// typed [CccException] subclass.
factory CccException.from(CccCryptoError error) {
return switch (error) {
CccCryptoError_UnsupportedAlgorithm(:final field0) =>
CccUnsupportedAlgorithm(field0),
CccCryptoError_InvalidKey(:final field0) => CccInvalidKey(field0),
CccCryptoError_InvalidNonce(:final field0) => CccInvalidNonce(field0),
CccCryptoError_AuthenticationFailed() =>
const CccAuthenticationFailed(),
CccCryptoError_InvalidInput(:final field0) => CccInvalidInput(field0),
CccCryptoError_FeatureNotCompiled(:final field0) =>
CccFeatureNotCompiled(field0),
CccCryptoError_InternalError(:final field0) => CccInternalError(field0),
};
}
}
/// The requested algorithm is not supported by any registered provider.
class CccUnsupportedAlgorithm extends CccException {
const CccUnsupportedAlgorithm(super.message);
}
/// The supplied key is invalid (wrong length, weak key, etc.).
class CccInvalidKey extends CccException {
const CccInvalidKey(super.message);
}
/// The supplied nonce/IV is invalid (wrong length, reuse detected, etc.).
class CccInvalidNonce extends CccException {
const CccInvalidNonce(super.message);
}
/// AEAD authentication tag verification failed ciphertext was tampered.
class CccAuthenticationFailed extends CccException {
const CccAuthenticationFailed() : super('authentication failed');
}
/// The supplied input data is invalid.
class CccInvalidInput extends CccException {
const CccInvalidInput(super.message);
}
/// The requested feature/algorithm is not compiled into the provider.
class CccFeatureNotCompiled extends CccException {
const CccFeatureNotCompiled(super.message);
}
/// An unexpected internal error occurred in the cryptographic provider.
class CccInternalError extends CccException {
const CccInternalError(super.message);
}

View File

@ -0,0 +1,110 @@
/// Provider catalog describes what a registered provider supports.
///
/// Wraps the FRB-generated [CccCapabilities] into a Dart-idiomatic type
/// for querying algorithm availability and quality scores.
library;
import 'package:ccc_cryptography/src/rust/api/dto.dart';
/// Describes the full set of capabilities for a registered provider.
class CccProviderCatalog {
/// Provider name (e.g. "wolfssl").
final String providerName;
/// AEAD algorithms supported by this provider.
final List<CccAlgorithmInfo> aead;
/// KDF algorithms supported by this provider.
final List<CccAlgorithmInfo> kdf;
/// MAC algorithms supported by this provider.
final List<CccAlgorithmInfo> mac;
/// Hash algorithms supported by this provider.
final List<CccAlgorithmInfo> hash;
/// KEM algorithms supported by this provider.
final List<CccAlgorithmInfo> kem;
const CccProviderCatalog._({
required this.providerName,
required this.aead,
required this.kdf,
required this.mac,
required this.hash,
required this.kem,
});
/// Convert from the FRB-generated capabilities DTO.
factory CccProviderCatalog.fromCapabilities(CccCapabilities caps) {
return CccProviderCatalog._(
providerName: caps.providerName,
aead: caps.aead.map(CccAlgorithmInfo._fromEntry).toList(growable: false),
kdf: caps.kdf.map(CccAlgorithmInfo._fromEntry).toList(growable: false),
mac: caps.mac.map(CccAlgorithmInfo._fromEntry).toList(growable: false),
hash: caps.hash.map(CccAlgorithmInfo._fromEntry).toList(growable: false),
kem: caps.kem.map(CccAlgorithmInfo._fromEntry).toList(growable: false),
);
}
/// All available AEAD algorithms.
List<CccAlgorithmInfo> get availableAead =>
aead.where((a) => a.available).toList(growable: false);
/// All available KDF algorithms.
List<CccAlgorithmInfo> get availableKdf =>
kdf.where((a) => a.available).toList(growable: false);
/// All available MAC algorithms.
List<CccAlgorithmInfo> get availableMac =>
mac.where((a) => a.available).toList(growable: false);
/// All available Hash algorithms.
List<CccAlgorithmInfo> get availableHash =>
hash.where((a) => a.available).toList(growable: false);
/// All available KEM algorithms.
List<CccAlgorithmInfo> get availableKem =>
kem.where((a) => a.available).toList(growable: false);
}
/// Describes a single algorithm's availability and quality metrics.
class CccAlgorithmInfo {
/// Numeric algorithm identifier (matches Rust `#[repr(u32)]`).
final int id;
/// Human-readable algorithm name (e.g. "AES-256-GCM").
final String name;
/// Whether this algorithm is available in the provider.
final bool available;
/// Whether the algorithm produces deterministic output for the same input.
final bool deterministicIo;
/// Provider-reported efficiency score (0100).
final int efficiencyScore;
/// Provider-reported reliability score (0100).
final int reliabilityScore;
const CccAlgorithmInfo({
required this.id,
required this.name,
required this.available,
required this.deterministicIo,
required this.efficiencyScore,
required this.reliabilityScore,
});
factory CccAlgorithmInfo._fromEntry(CccAlgorithmEntry e) {
return CccAlgorithmInfo(
id: e.algoId,
name: e.algoName,
available: e.capability.available,
deterministicIo: e.capability.deterministicIo,
efficiencyScore: e.capability.efficiencyScore,
reliabilityScore: e.capability.reliabilityScore,
);
}
}

68
lib/ccc_self_test.dart Normal file
View File

@ -0,0 +1,68 @@
/// Self-test result wraps the FRB-generated report into Dart-idiomatic types.
library;
import 'package:ccc_cryptography/src/rust/api/dto.dart';
/// Result of a single algorithm's self-test.
class CccAlgorithmTestResult {
/// Numeric algorithm identifier.
final int algoId;
/// Human-readable algorithm name.
final String algoName;
/// Whether the algorithm passed its self-test.
final bool passed;
/// Diagnostic message if the test failed; `null` on success.
final String? errorMessage;
const CccAlgorithmTestResult({
required this.algoId,
required this.algoName,
required this.passed,
this.errorMessage,
});
factory CccAlgorithmTestResult._fromDto(CccAlgoTestResult dto) {
return CccAlgorithmTestResult(
algoId: dto.algoId,
algoName: dto.algoName,
passed: dto.passed,
errorMessage: dto.errorMessage,
);
}
}
/// Aggregate result of the provider self-test.
class CccSelfTestResult {
/// Name of the provider that was tested.
final String providerName;
/// Per-algorithm test results.
final List<CccAlgorithmTestResult> results;
/// Whether all algorithms passed.
final bool allPassed;
const CccSelfTestResult._({
required this.providerName,
required this.results,
required this.allPassed,
});
/// Convert from the FRB-generated self-test report DTO.
factory CccSelfTestResult.fromReport(CccSelfTestReport report) {
return CccSelfTestResult._(
providerName: report.providerName,
results: report.results
.map(CccAlgorithmTestResult._fromDto)
.toList(growable: false),
allPassed: report.allPassed,
);
}
/// Algorithms that failed their self-test.
List<CccAlgorithmTestResult> get failures =>
results.where((r) => !r.passed).toList(growable: false);
}

View File

@ -0,0 +1,137 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'dto.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `default_provider`, `kem_provider`
/// Initialise the CCC cryptographic subsystem.
///
/// Registers the wolfSSL provider in the global registry.
/// Safe to call multiple times (idempotent).
Future<void> cccInit() => RustLib.instance.api.crateApiCryptoCccInit();
/// List all registered provider names.
List<String> cccListProviders() =>
RustLib.instance.api.crateApiCryptoCccListProviders();
/// Return the capabilities of the default provider.
Future<CccCapabilities> cccCapabilities() =>
RustLib.instance.api.crateApiCryptoCccCapabilities();
/// Encrypt with an AEAD algorithm.
///
/// Returns ciphertexttag.
Future<Uint8List> cccAeadEncrypt({
required CccAeadAlgorithm algorithm,
required List<int> key,
required List<int> nonce,
required List<int> plaintext,
required List<int> aad,
}) => RustLib.instance.api.crateApiCryptoCccAeadEncrypt(
algorithm: algorithm,
key: key,
nonce: nonce,
plaintext: plaintext,
aad: aad,
);
/// Decrypt with an AEAD algorithm.
///
/// Expects ciphertexttag as `ciphertext`.
Future<Uint8List> cccAeadDecrypt({
required CccAeadAlgorithm algorithm,
required List<int> key,
required List<int> nonce,
required List<int> ciphertext,
required List<int> aad,
}) => RustLib.instance.api.crateApiCryptoCccAeadDecrypt(
algorithm: algorithm,
key: key,
nonce: nonce,
ciphertext: ciphertext,
aad: aad,
);
/// Derive key material using a KDF.
Future<Uint8List> cccKdfDerive({
required CccKdfAlgorithm algorithm,
required List<int> ikm,
required List<int> salt,
required List<int> info,
required int length,
}) => RustLib.instance.api.crateApiCryptoCccKdfDerive(
algorithm: algorithm,
ikm: ikm,
salt: salt,
info: info,
length: length,
);
/// Compute a MAC tag.
Future<Uint8List> cccMacCompute({
required CccMacAlgorithm algorithm,
required List<int> key,
required List<int> data,
}) => RustLib.instance.api.crateApiCryptoCccMacCompute(
algorithm: algorithm,
key: key,
data: data,
);
/// Verify a MAC tag. Returns `true` if valid.
Future<bool> cccMacVerify({
required CccMacAlgorithm algorithm,
required List<int> key,
required List<int> data,
required List<int> mac,
}) => RustLib.instance.api.crateApiCryptoCccMacVerify(
algorithm: algorithm,
key: key,
data: data,
mac: mac,
);
/// Compute a cryptographic hash.
Future<Uint8List> cccHash({
required CccHashAlgorithm algorithm,
required List<int> data,
}) => RustLib.instance.api.crateApiCryptoCccHash(
algorithm: algorithm,
data: data,
);
/// Generate a KEM key pair.
Future<CccKemKeyPair> cccKemGenerateKeypair({
required CccKemAlgorithm algorithm,
}) => RustLib.instance.api.crateApiCryptoCccKemGenerateKeypair(
algorithm: algorithm,
);
/// KEM encapsulation produce ciphertext + shared secret from a public key.
Future<CccKemEncapResult> cccKemEncapsulate({
required CccKemAlgorithm algorithm,
required List<int> publicKey,
}) => RustLib.instance.api.crateApiCryptoCccKemEncapsulate(
algorithm: algorithm,
publicKey: publicKey,
);
/// KEM decapsulation recover shared secret from ciphertext + private key.
Future<Uint8List> cccKemDecapsulate({
required CccKemAlgorithm algorithm,
required List<int> privateKey,
required List<int> ciphertext,
}) => RustLib.instance.api.crateApiCryptoCccKemDecapsulate(
algorithm: algorithm,
privateKey: privateKey,
ciphertext: ciphertext,
);
/// Run the provider self-test and return a structured report.
Future<CccSelfTestReport> cccSelfTest() =>
RustLib.instance.api.crateApiCryptoCccSelfTest();

260
lib/src/rust/api/dto.dart Normal file
View File

@ -0,0 +1,260 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'dto.freezed.dart';
// These functions are ignored because they are not marked as `pub`: `to_core`, `to_core`, `to_core`, `to_core`, `to_core`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`
/// AEAD algorithm identifiers values match `ccc_rust` `#[repr(u32)]`.
enum CccAeadAlgorithm {
aesGcm256,
chaCha20Poly1305,
xChaCha20Poly1305,
ascon128A,
}
/// Per-algorithm self-test result.
class CccAlgoTestResult {
final int algoId;
final String algoName;
final bool passed;
final String? errorMessage;
const CccAlgoTestResult({
required this.algoId,
required this.algoName,
required this.passed,
this.errorMessage,
});
@override
int get hashCode =>
algoId.hashCode ^
algoName.hashCode ^
passed.hashCode ^
errorMessage.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccAlgoTestResult &&
runtimeType == other.runtimeType &&
algoId == other.algoId &&
algoName == other.algoName &&
passed == other.passed &&
errorMessage == other.errorMessage;
}
/// Per-algorithm availability and quality scores.
class CccAlgorithmCapability {
final bool available;
final bool deterministicIo;
final int efficiencyScore;
final int reliabilityScore;
const CccAlgorithmCapability({
required this.available,
required this.deterministicIo,
required this.efficiencyScore,
required this.reliabilityScore,
});
@override
int get hashCode =>
available.hashCode ^
deterministicIo.hashCode ^
efficiencyScore.hashCode ^
reliabilityScore.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccAlgorithmCapability &&
runtimeType == other.runtimeType &&
available == other.available &&
deterministicIo == other.deterministicIo &&
efficiencyScore == other.efficiencyScore &&
reliabilityScore == other.reliabilityScore;
}
/// An algorithm entry in the capabilities list.
class CccAlgorithmEntry {
final int algoId;
final String algoName;
final CccAlgorithmCapability capability;
const CccAlgorithmEntry({
required this.algoId,
required this.algoName,
required this.capability,
});
@override
int get hashCode => algoId.hashCode ^ algoName.hashCode ^ capability.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccAlgorithmEntry &&
runtimeType == other.runtimeType &&
algoId == other.algoId &&
algoName == other.algoName &&
capability == other.capability;
}
/// Provider capabilities flattened from HashMap to Vec for FRB.
class CccCapabilities {
final String providerName;
final List<CccAlgorithmEntry> aead;
final List<CccAlgorithmEntry> kdf;
final List<CccAlgorithmEntry> mac;
final List<CccAlgorithmEntry> hash;
final List<CccAlgorithmEntry> kem;
const CccCapabilities({
required this.providerName,
required this.aead,
required this.kdf,
required this.mac,
required this.hash,
required this.kem,
});
@override
int get hashCode =>
providerName.hashCode ^
aead.hashCode ^
kdf.hashCode ^
mac.hashCode ^
hash.hashCode ^
kem.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccCapabilities &&
runtimeType == other.runtimeType &&
providerName == other.providerName &&
aead == other.aead &&
kdf == other.kdf &&
mac == other.mac &&
hash == other.hash &&
kem == other.kem;
}
@freezed
sealed class CccCryptoError with _$CccCryptoError implements FrbException {
const CccCryptoError._();
const factory CccCryptoError.unsupportedAlgorithm(String field0) =
CccCryptoError_UnsupportedAlgorithm;
const factory CccCryptoError.invalidKey(String field0) =
CccCryptoError_InvalidKey;
const factory CccCryptoError.invalidNonce(String field0) =
CccCryptoError_InvalidNonce;
const factory CccCryptoError.authenticationFailed() =
CccCryptoError_AuthenticationFailed;
const factory CccCryptoError.invalidInput(String field0) =
CccCryptoError_InvalidInput;
const factory CccCryptoError.featureNotCompiled(String field0) =
CccCryptoError_FeatureNotCompiled;
const factory CccCryptoError.internalError(String field0) =
CccCryptoError_InternalError;
}
/// Hash algorithm identifiers.
enum CccHashAlgorithm { sha256, sha384, sha512, blake2B512, sha3256, sha3512 }
/// KDF algorithm identifiers.
enum CccKdfAlgorithm { sha256, sha384, sha512, blake2B512, argon2Id, kmac256 }
/// KEM algorithm identifiers.
enum CccKemAlgorithm {
x25519,
x448,
mlKem768,
mlKem1024,
classicMcEliece460896,
}
/// KEM encapsulation result ciphertext + shared secret.
class CccKemEncapResult {
final Uint8List ciphertext;
final Uint8List sharedSecret;
const CccKemEncapResult({
required this.ciphertext,
required this.sharedSecret,
});
@override
int get hashCode => ciphertext.hashCode ^ sharedSecret.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccKemEncapResult &&
runtimeType == other.runtimeType &&
ciphertext == other.ciphertext &&
sharedSecret == other.sharedSecret;
}
/// KEM key pair public + private key bytes.
class CccKemKeyPair {
final Uint8List publicKey;
final Uint8List privateKey;
const CccKemKeyPair({required this.publicKey, required this.privateKey});
@override
int get hashCode => publicKey.hashCode ^ privateKey.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccKemKeyPair &&
runtimeType == other.runtimeType &&
publicKey == other.publicKey &&
privateKey == other.privateKey;
}
/// MAC algorithm identifiers.
enum CccMacAlgorithm {
hmacSha256,
hmacSha384,
hmacSha512,
blake2BMac,
poly1305,
}
/// Aggregate self-test report.
class CccSelfTestReport {
final String providerName;
final List<CccAlgoTestResult> results;
final bool allPassed;
const CccSelfTestReport({
required this.providerName,
required this.results,
required this.allPassed,
});
@override
int get hashCode =>
providerName.hashCode ^ results.hashCode ^ allPassed.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CccSelfTestReport &&
runtimeType == other.runtimeType &&
providerName == other.providerName &&
results == other.results &&
allPassed == other.allPassed;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
String greet({required String name}) =>
RustLib.instance.api.crateApiSimpleGreet(name: name);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,324 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api/crypto.dart';
import 'api/dto.dart';
import 'api/simple.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@protected
String dco_decode_String(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
CccAeadAlgorithm dco_decode_ccc_aead_algorithm(dynamic raw);
@protected
CccAlgoTestResult dco_decode_ccc_algo_test_result(dynamic raw);
@protected
CccAlgorithmCapability dco_decode_ccc_algorithm_capability(dynamic raw);
@protected
CccAlgorithmEntry dco_decode_ccc_algorithm_entry(dynamic raw);
@protected
CccCapabilities dco_decode_ccc_capabilities(dynamic raw);
@protected
CccCryptoError dco_decode_ccc_crypto_error(dynamic raw);
@protected
CccHashAlgorithm dco_decode_ccc_hash_algorithm(dynamic raw);
@protected
CccKdfAlgorithm dco_decode_ccc_kdf_algorithm(dynamic raw);
@protected
CccKemAlgorithm dco_decode_ccc_kem_algorithm(dynamic raw);
@protected
CccKemEncapResult dco_decode_ccc_kem_encap_result(dynamic raw);
@protected
CccKemKeyPair dco_decode_ccc_kem_key_pair(dynamic raw);
@protected
CccMacAlgorithm dco_decode_ccc_mac_algorithm(dynamic raw);
@protected
CccSelfTestReport dco_decode_ccc_self_test_report(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@protected
List<String> dco_decode_list_String(dynamic raw);
@protected
List<CccAlgoTestResult> dco_decode_list_ccc_algo_test_result(dynamic raw);
@protected
List<CccAlgorithmEntry> dco_decode_list_ccc_algorithm_entry(dynamic raw);
@protected
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
CccAeadAlgorithm sse_decode_ccc_aead_algorithm(SseDeserializer deserializer);
@protected
CccAlgoTestResult sse_decode_ccc_algo_test_result(
SseDeserializer deserializer,
);
@protected
CccAlgorithmCapability sse_decode_ccc_algorithm_capability(
SseDeserializer deserializer,
);
@protected
CccAlgorithmEntry sse_decode_ccc_algorithm_entry(
SseDeserializer deserializer,
);
@protected
CccCapabilities sse_decode_ccc_capabilities(SseDeserializer deserializer);
@protected
CccCryptoError sse_decode_ccc_crypto_error(SseDeserializer deserializer);
@protected
CccHashAlgorithm sse_decode_ccc_hash_algorithm(SseDeserializer deserializer);
@protected
CccKdfAlgorithm sse_decode_ccc_kdf_algorithm(SseDeserializer deserializer);
@protected
CccKemAlgorithm sse_decode_ccc_kem_algorithm(SseDeserializer deserializer);
@protected
CccKemEncapResult sse_decode_ccc_kem_encap_result(
SseDeserializer deserializer,
);
@protected
CccKemKeyPair sse_decode_ccc_kem_key_pair(SseDeserializer deserializer);
@protected
CccMacAlgorithm sse_decode_ccc_mac_algorithm(SseDeserializer deserializer);
@protected
CccSelfTestReport sse_decode_ccc_self_test_report(
SseDeserializer deserializer,
);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer);
@protected
List<CccAlgoTestResult> sse_decode_list_ccc_algo_test_result(
SseDeserializer deserializer,
);
@protected
List<CccAlgorithmEntry> sse_decode_list_ccc_algorithm_entry(
SseDeserializer deserializer,
);
@protected
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_ccc_aead_algorithm(
CccAeadAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algo_test_result(
CccAlgoTestResult self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algorithm_capability(
CccAlgorithmCapability self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algorithm_entry(
CccAlgorithmEntry self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_capabilities(
CccCapabilities self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_crypto_error(
CccCryptoError self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_hash_algorithm(
CccHashAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kdf_algorithm(
CccKdfAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_algorithm(
CccKemAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_encap_result(
CccKemEncapResult self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_key_pair(
CccKemKeyPair self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_mac_algorithm(
CccMacAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_self_test_report(
CccSelfTestReport self,
SseSerializer serializer,
);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer);
@protected
void sse_encode_list_ccc_algo_test_result(
List<CccAlgoTestResult> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_ccc_algorithm_entry(
List<CccAlgorithmEntry> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
RustLibWire(lib.ffiDynamicLibrary);
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
}

View File

@ -0,0 +1,324 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
// Static analysis wrongly picks the IO variant, thus ignore this
// ignore_for_file: argument_type_not_assignable
import 'api/crypto.dart';
import 'api/dto.dart';
import 'api/simple.dart';
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@protected
String dco_decode_String(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
CccAeadAlgorithm dco_decode_ccc_aead_algorithm(dynamic raw);
@protected
CccAlgoTestResult dco_decode_ccc_algo_test_result(dynamic raw);
@protected
CccAlgorithmCapability dco_decode_ccc_algorithm_capability(dynamic raw);
@protected
CccAlgorithmEntry dco_decode_ccc_algorithm_entry(dynamic raw);
@protected
CccCapabilities dco_decode_ccc_capabilities(dynamic raw);
@protected
CccCryptoError dco_decode_ccc_crypto_error(dynamic raw);
@protected
CccHashAlgorithm dco_decode_ccc_hash_algorithm(dynamic raw);
@protected
CccKdfAlgorithm dco_decode_ccc_kdf_algorithm(dynamic raw);
@protected
CccKemAlgorithm dco_decode_ccc_kem_algorithm(dynamic raw);
@protected
CccKemEncapResult dco_decode_ccc_kem_encap_result(dynamic raw);
@protected
CccKemKeyPair dco_decode_ccc_kem_key_pair(dynamic raw);
@protected
CccMacAlgorithm dco_decode_ccc_mac_algorithm(dynamic raw);
@protected
CccSelfTestReport dco_decode_ccc_self_test_report(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@protected
List<String> dco_decode_list_String(dynamic raw);
@protected
List<CccAlgoTestResult> dco_decode_list_ccc_algo_test_result(dynamic raw);
@protected
List<CccAlgorithmEntry> dco_decode_list_ccc_algorithm_entry(dynamic raw);
@protected
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
CccAeadAlgorithm sse_decode_ccc_aead_algorithm(SseDeserializer deserializer);
@protected
CccAlgoTestResult sse_decode_ccc_algo_test_result(
SseDeserializer deserializer,
);
@protected
CccAlgorithmCapability sse_decode_ccc_algorithm_capability(
SseDeserializer deserializer,
);
@protected
CccAlgorithmEntry sse_decode_ccc_algorithm_entry(
SseDeserializer deserializer,
);
@protected
CccCapabilities sse_decode_ccc_capabilities(SseDeserializer deserializer);
@protected
CccCryptoError sse_decode_ccc_crypto_error(SseDeserializer deserializer);
@protected
CccHashAlgorithm sse_decode_ccc_hash_algorithm(SseDeserializer deserializer);
@protected
CccKdfAlgorithm sse_decode_ccc_kdf_algorithm(SseDeserializer deserializer);
@protected
CccKemAlgorithm sse_decode_ccc_kem_algorithm(SseDeserializer deserializer);
@protected
CccKemEncapResult sse_decode_ccc_kem_encap_result(
SseDeserializer deserializer,
);
@protected
CccKemKeyPair sse_decode_ccc_kem_key_pair(SseDeserializer deserializer);
@protected
CccMacAlgorithm sse_decode_ccc_mac_algorithm(SseDeserializer deserializer);
@protected
CccSelfTestReport sse_decode_ccc_self_test_report(
SseDeserializer deserializer,
);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer);
@protected
List<CccAlgoTestResult> sse_decode_list_ccc_algo_test_result(
SseDeserializer deserializer,
);
@protected
List<CccAlgorithmEntry> sse_decode_list_ccc_algorithm_entry(
SseDeserializer deserializer,
);
@protected
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_ccc_aead_algorithm(
CccAeadAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algo_test_result(
CccAlgoTestResult self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algorithm_capability(
CccAlgorithmCapability self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_algorithm_entry(
CccAlgorithmEntry self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_capabilities(
CccCapabilities self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_crypto_error(
CccCryptoError self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_hash_algorithm(
CccHashAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kdf_algorithm(
CccKdfAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_algorithm(
CccKemAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_encap_result(
CccKemEncapResult self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_kem_key_pair(
CccKemKeyPair self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_mac_algorithm(
CccMacAlgorithm self,
SseSerializer serializer,
);
@protected
void sse_encode_ccc_self_test_report(
CccSelfTestReport self,
SseSerializer serializer,
);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer);
@protected
void sse_encode_list_ccc_algo_test_result(
List<CccAlgoTestResult> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_ccc_algorithm_entry(
List<CccAlgorithmEntry> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
}
@JS('wasm_bindgen')
external RustLibWasmModule get wasmModule;
@JS()
@anonymous
extension type RustLibWasmModule._(JSObject _) implements JSObject {}

View File

@ -7,16 +7,13 @@ cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME "ccc_cryptography")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
# This can be changed to accommodate different builds.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
include("../cargokit/cmake/cargokit.cmake")
apply_cargokit(${PROJECT_NAME} ../rust ccc_cryptography "")
# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(ccc_cryptography_bundled_libraries
# Defined in ../src/CMakeLists.txt.
# This can be changed to accommodate different builds.
$<TARGET_FILE:ccc_cryptography>
"${${PROJECT_NAME}_cargokit_lib}"
PARENT_SCOPE
)

View File

@ -1,3 +0,0 @@
// Relative import to be able to reuse the C sources.
// See the comment in ../ccc_cryptography.podspec for more information.
#include "../../src/ccc_cryptography.c"

View File

@ -0,0 +1 @@
// This is an empty file to force CocoaPods to create a framework.

View File

@ -18,17 +18,27 @@ A new Flutter FFI plugin project.
# paths, so Classes contains a forwarder C file that relatively imports
# `../src/*` so that the C sources can be shared among all target platforms.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
# If your plugin requires a privacy manifest, for example if it collects user
# data, update the PrivacyInfo.xcprivacy file to describe your plugin's
# privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'ccc_cryptography_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
s.source_files = 'Classes/**/*'
s.dependency 'FlutterMacOS'
s.platform = :osx, '10.11'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0'
s.script_phase = {
:name => 'Build Rust library',
# First argument is relative path to the `rust` folder, second is name of rust library
:script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust ccc_cryptography',
:execution_position => :before_compile,
:input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'],
# Let XCode know that the static library referenced in -force_load below is
# created by this build step.
:output_files => ["${BUILT_PRODUCTS_DIR}/libccc_cryptography.a"],
}
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
# Flutter.framework does not contain a i386 slice.
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libccc_cryptography.a',
}
end

View File

@ -1,7 +1,7 @@
name: ccc_cryptography
description: "Copius Cipher Chain Cryptography"
publish_to: 'none'
version: 0.0.1
homepage:
environment:
sdk: ^3.10.1
@ -11,13 +11,19 @@ dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
flutter_rust_bridge: 2.11.1
freezed_annotation: ^2.4.1
meta: ^1.11.0
dev_dependencies:
ffi: ^2.1.3
ffigen: ^13.0.0
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
build_runner: ^2.4.8
freezed: ^2.5.2
integration_test:
sdk: flutter
flutter:
plugin:

1
rust/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

16
rust/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "ccc_cryptography"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
# Local dev paths — switch to git deps for CI/release:
# ccc-crypto-core = { git = "ssh://git@10.0.5.109/j3g/lum_ccc_rust.git", rev = "b1873b2" }
# ccc-crypto-wolfssl = { git = "ssh://git@10.0.5.109/j3g/lum_ccc_rust.git", rev = "b1873b2" }
ccc-crypto-core = { path = "/Volumes/LUM/source/letusmsg_proj/app/lum_ccc_rust/crates/ccc-crypto-core" }
ccc-crypto-wolfssl = { path = "/Volumes/LUM/source/letusmsg_proj/app/lum_ccc_rust/crates/ccc-crypto-wolfssl" }
flutter_rust_bridge = "=2.11.1"
log = "0.4"

208
rust/src/api/crypto.rs Normal file
View File

@ -0,0 +1,208 @@
// Bridge API — thin wrappers that delegate to ccc_rust providers.
//
// No cryptographic logic here; only type conversions and registry lookups.
use ccc_crypto_core::{KemProvider, ProviderRegistry};
use ccc_crypto_wolfssl::WolfSslProvider;
use crate::api::dto::*;
/// Trace-level bridge logging. Visible only when `RUST_LOG=trace` (or
/// equivalent) is set; compiled out in release builds when the `log` crate's
/// `release_max_level_off` feature is enabled.
macro_rules! dbg_bridge {
($($arg:tt)*) => {
log::trace!("[ccc-bridge] {}", format!($($arg)*));
};
}
// ── Helpers ──────────────────────────────────────────────────────────────────
/// Default provider name used when the caller doesn't specify one.
const DEFAULT_PROVIDER: &str = "wolfssl";
/// Get the default provider from the registry or return an error.
fn default_provider(
) -> Result<std::sync::Arc<dyn ccc_crypto_core::provider::CryptoProvider>, CccCryptoError> {
ProviderRegistry::global()
.get(DEFAULT_PROVIDER)
.ok_or_else(|| {
CccCryptoError::InternalError(format!(
"provider '{DEFAULT_PROVIDER}' not registered — call ccc_init() first"
))
})
}
/// A lazily-initialised KemProvider instance.
///
/// `KemProvider` is not part of the `CryptoProvider` supertrait, so we keep
/// a standalone `WolfSslProvider` for KEM operations.
fn kem_provider() -> &'static WolfSslProvider {
use std::sync::OnceLock;
static KEM: OnceLock<WolfSslProvider> = OnceLock::new();
KEM.get_or_init(WolfSslProvider::new)
}
// ── Init ─────────────────────────────────────────────────────────────────────
/// Initialise the CCC cryptographic subsystem.
///
/// Registers the wolfSSL provider in the global registry.
/// Safe to call multiple times (idempotent).
pub fn ccc_init() {
dbg_bridge!("ccc_init: enter");
flutter_rust_bridge::setup_default_user_utils();
if !ProviderRegistry::global().contains(DEFAULT_PROVIDER) {
ccc_crypto_wolfssl::init();
}
dbg_bridge!("ccc_init: exit");
}
// ── Provider info ────────────────────────────────────────────────────────────
/// List all registered provider names.
#[flutter_rust_bridge::frb(sync)]
pub fn ccc_list_providers() -> Vec<String> {
dbg_bridge!("ccc_list_providers");
ProviderRegistry::global().list()
}
/// Return the capabilities of the default provider.
pub fn ccc_capabilities() -> Result<CccCapabilities, CccCryptoError> {
dbg_bridge!("ccc_capabilities");
let provider = default_provider()?;
Ok(CccCapabilities::from(provider.capabilities()))
}
// ── AEAD ─────────────────────────────────────────────────────────────────────
/// Encrypt with an AEAD algorithm.
///
/// Returns ciphertext‖tag.
pub fn ccc_aead_encrypt(
algorithm: CccAeadAlgorithm,
key: Vec<u8>,
nonce: Vec<u8>,
plaintext: Vec<u8>,
aad: Vec<u8>,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_aead_encrypt: pt_len={}", plaintext.len());
let provider = default_provider()?;
Ok(provider.encrypt_aead(algorithm.to_core(), &key, &nonce, &plaintext, &aad)?)
}
/// Decrypt with an AEAD algorithm.
///
/// Expects ciphertext‖tag as `ciphertext`.
pub fn ccc_aead_decrypt(
algorithm: CccAeadAlgorithm,
key: Vec<u8>,
nonce: Vec<u8>,
ciphertext: Vec<u8>,
aad: Vec<u8>,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_aead_decrypt: ct_len={}", ciphertext.len());
let provider = default_provider()?;
Ok(provider.decrypt_aead(algorithm.to_core(), &key, &nonce, &ciphertext, &aad)?)
}
// ── KDF ──────────────────────────────────────────────────────────────────────
/// Derive key material using a KDF.
pub fn ccc_kdf_derive(
algorithm: CccKdfAlgorithm,
ikm: Vec<u8>,
salt: Vec<u8>,
info: Vec<u8>,
length: u32,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_kdf_derive: length={}", length);
let provider = default_provider()?;
let derived = provider.derive_key(
algorithm.to_core(),
&ikm,
&salt,
&info,
length as usize,
)?;
// Move out of Zeroizing wrapper — FRB will copy to Dart.
Ok(derived.to_vec())
}
// ── MAC ──────────────────────────────────────────────────────────────────────
/// Compute a MAC tag.
pub fn ccc_mac_compute(
algorithm: CccMacAlgorithm,
key: Vec<u8>,
data: Vec<u8>,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_mac_compute: data_len={}", data.len());
let provider = default_provider()?;
Ok(provider.compute_mac(algorithm.to_core(), &key, &data)?)
}
/// Verify a MAC tag. Returns `true` if valid.
pub fn ccc_mac_verify(
algorithm: CccMacAlgorithm,
key: Vec<u8>,
data: Vec<u8>,
mac: Vec<u8>,
) -> Result<bool, CccCryptoError> {
dbg_bridge!("ccc_mac_verify: data_len={}", data.len());
let provider = default_provider()?;
Ok(provider.verify_mac(algorithm.to_core(), &key, &data, &mac)?)
}
// ── Hash ─────────────────────────────────────────────────────────────────────
/// Compute a cryptographic hash.
pub fn ccc_hash(
algorithm: CccHashAlgorithm,
data: Vec<u8>,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_hash: data_len={}", data.len());
let provider = default_provider()?;
Ok(provider.hash(algorithm.to_core(), &data)?)
}
// ── KEM ──────────────────────────────────────────────────────────────────────
/// Generate a KEM key pair.
pub fn ccc_kem_generate_keypair(
algorithm: CccKemAlgorithm,
) -> Result<CccKemKeyPair, CccCryptoError> {
dbg_bridge!("ccc_kem_generate_keypair");
let kp = kem_provider().generate_keypair(algorithm.to_core())?;
Ok(CccKemKeyPair::from(kp))
}
/// KEM encapsulation — produce ciphertext + shared secret from a public key.
pub fn ccc_kem_encapsulate(
algorithm: CccKemAlgorithm,
public_key: Vec<u8>,
) -> Result<CccKemEncapResult, CccCryptoError> {
dbg_bridge!("ccc_kem_encapsulate");
let result = kem_provider().encapsulate(algorithm.to_core(), &public_key)?;
Ok(CccKemEncapResult::from(result))
}
/// KEM decapsulation — recover shared secret from ciphertext + private key.
pub fn ccc_kem_decapsulate(
algorithm: CccKemAlgorithm,
private_key: Vec<u8>,
ciphertext: Vec<u8>,
) -> Result<Vec<u8>, CccCryptoError> {
dbg_bridge!("ccc_kem_decapsulate");
let ss = kem_provider().decapsulate(algorithm.to_core(), &private_key, &ciphertext)?;
Ok(ss.to_vec())
}
// ── Self-test ────────────────────────────────────────────────────────────────
/// Run the provider self-test and return a structured report.
pub fn ccc_self_test() -> Result<CccSelfTestReport, CccCryptoError> {
dbg_bridge!("ccc_self_test");
let provider = default_provider()?;
Ok(CccSelfTestReport::from(provider.self_test()))
}

328
rust/src/api/dto.rs Normal file
View File

@ -0,0 +1,328 @@
// Bridge-visible DTOs — no crypto logic, only type conversions.
//
// flutter_rust_bridge codegen picks up all pub types under crate::api
// and generates corresponding Dart classes.
use ccc_crypto_core::{
AeadAlgorithm, AlgoTestResult, AlgorithmCapability, HashAlgorithm, KdfAlgorithm,
KemAlgorithm, KemEncapResult, KemKeyPair, MacAlgorithm, ProviderCapabilities, SelfTestReport,
};
// ── Algorithm ID enums (mirror Rust repr(u32) values) ────────────────────────
/// AEAD algorithm identifiers — values match `ccc_rust` `#[repr(u32)]`.
#[flutter_rust_bridge::frb(dart_metadata=("freezed"))]
pub enum CccAeadAlgorithm {
AesGcm256 = 12,
ChaCha20Poly1305 = 13,
XChaCha20Poly1305 = 14,
Ascon128a = 15,
}
/// KDF algorithm identifiers.
#[flutter_rust_bridge::frb(dart_metadata=("freezed"))]
pub enum CccKdfAlgorithm {
Sha256 = 1,
Sha384 = 2,
Sha512 = 3,
Blake2b512 = 4,
Argon2id = 5,
Kmac256 = 6,
}
/// MAC algorithm identifiers.
#[flutter_rust_bridge::frb(dart_metadata=("freezed"))]
pub enum CccMacAlgorithm {
HmacSha256 = 30,
HmacSha384 = 31,
HmacSha512 = 32,
Blake2bMac = 33,
Poly1305 = 35,
}
/// Hash algorithm identifiers.
#[flutter_rust_bridge::frb(dart_metadata=("freezed"))]
pub enum CccHashAlgorithm {
Sha256 = 40,
Sha384 = 41,
Sha512 = 42,
Blake2b512 = 43,
Sha3_256 = 44,
Sha3_512 = 45,
}
/// KEM algorithm identifiers.
#[flutter_rust_bridge::frb(dart_metadata=("freezed"))]
pub enum CccKemAlgorithm {
X25519 = 50,
X448 = 51,
MlKem768 = 52,
MlKem1024 = 53,
ClassicMcEliece460896 = 54,
}
// ── Enum conversions ─────────────────────────────────────────────────────────
impl CccAeadAlgorithm {
pub(crate) fn to_core(&self) -> AeadAlgorithm {
match self {
Self::AesGcm256 => AeadAlgorithm::AesGcm256,
Self::ChaCha20Poly1305 => AeadAlgorithm::ChaCha20Poly1305,
Self::XChaCha20Poly1305 => AeadAlgorithm::XChaCha20Poly1305,
Self::Ascon128a => AeadAlgorithm::Ascon128a,
}
}
}
impl CccKdfAlgorithm {
pub(crate) fn to_core(&self) -> KdfAlgorithm {
match self {
Self::Sha256 => KdfAlgorithm::Sha256,
Self::Sha384 => KdfAlgorithm::Sha384,
Self::Sha512 => KdfAlgorithm::Sha512,
Self::Blake2b512 => KdfAlgorithm::Blake2b512,
Self::Argon2id => KdfAlgorithm::Argon2id,
Self::Kmac256 => KdfAlgorithm::Kmac256,
}
}
}
impl CccMacAlgorithm {
pub(crate) fn to_core(&self) -> MacAlgorithm {
match self {
Self::HmacSha256 => MacAlgorithm::HmacSha256,
Self::HmacSha384 => MacAlgorithm::HmacSha384,
Self::HmacSha512 => MacAlgorithm::HmacSha512,
Self::Blake2bMac => MacAlgorithm::Blake2bMac,
Self::Poly1305 => MacAlgorithm::Poly1305,
}
}
}
impl CccHashAlgorithm {
pub(crate) fn to_core(&self) -> HashAlgorithm {
match self {
Self::Sha256 => HashAlgorithm::Sha256,
Self::Sha384 => HashAlgorithm::Sha384,
Self::Sha512 => HashAlgorithm::Sha512,
Self::Blake2b512 => HashAlgorithm::Blake2b512,
Self::Sha3_256 => HashAlgorithm::Sha3_256,
Self::Sha3_512 => HashAlgorithm::Sha3_512,
}
}
}
impl CccKemAlgorithm {
pub(crate) fn to_core(&self) -> KemAlgorithm {
match self {
Self::X25519 => KemAlgorithm::X25519,
Self::X448 => KemAlgorithm::X448,
Self::MlKem768 => KemAlgorithm::MlKem768,
Self::MlKem1024 => KemAlgorithm::MlKem1024,
Self::ClassicMcEliece460896 => KemAlgorithm::ClassicMcEliece460896,
}
}
}
// ── DTO structs ──────────────────────────────────────────────────────────────
/// Per-algorithm availability and quality scores.
pub struct CccAlgorithmCapability {
pub available: bool,
pub deterministic_io: bool,
pub efficiency_score: u8,
pub reliability_score: u8,
}
impl From<&AlgorithmCapability> for CccAlgorithmCapability {
fn from(cap: &AlgorithmCapability) -> Self {
Self {
available: cap.available,
deterministic_io: cap.deterministic_io,
efficiency_score: cap.efficiency_score,
reliability_score: cap.reliability_score,
}
}
}
/// An algorithm entry in the capabilities list.
pub struct CccAlgorithmEntry {
pub algo_id: u32,
pub algo_name: String,
pub capability: CccAlgorithmCapability,
}
/// Provider capabilities — flattened from HashMap to Vec for FRB.
pub struct CccCapabilities {
pub provider_name: String,
pub aead: Vec<CccAlgorithmEntry>,
pub kdf: Vec<CccAlgorithmEntry>,
pub mac: Vec<CccAlgorithmEntry>,
pub hash: Vec<CccAlgorithmEntry>,
pub kem: Vec<CccAlgorithmEntry>,
}
impl From<ProviderCapabilities> for CccCapabilities {
fn from(caps: ProviderCapabilities) -> Self {
Self {
provider_name: caps.provider_name,
aead: caps
.aead
.iter()
.map(|(algo, cap)| CccAlgorithmEntry {
algo_id: *algo as u32,
algo_name: algo.name().to_string(),
capability: CccAlgorithmCapability::from(cap),
})
.collect(),
kdf: caps
.kdf
.iter()
.map(|(algo, cap)| CccAlgorithmEntry {
algo_id: *algo as u32,
algo_name: algo.name().to_string(),
capability: CccAlgorithmCapability::from(cap),
})
.collect(),
mac: caps
.mac
.iter()
.map(|(algo, cap)| CccAlgorithmEntry {
algo_id: *algo as u32,
algo_name: algo.name().to_string(),
capability: CccAlgorithmCapability::from(cap),
})
.collect(),
hash: caps
.hash
.iter()
.map(|(algo, cap)| CccAlgorithmEntry {
algo_id: *algo as u32,
algo_name: algo.name().to_string(),
capability: CccAlgorithmCapability::from(cap),
})
.collect(),
kem: caps
.kem
.iter()
.map(|(algo, cap)| CccAlgorithmEntry {
algo_id: *algo as u32,
algo_name: algo.name().to_string(),
capability: CccAlgorithmCapability::from(cap),
})
.collect(),
}
}
}
/// KEM key pair — public + private key bytes.
pub struct CccKemKeyPair {
pub public_key: Vec<u8>,
pub private_key: Vec<u8>,
}
impl From<KemKeyPair> for CccKemKeyPair {
fn from(kp: KemKeyPair) -> Self {
Self {
public_key: kp.public_key.clone(),
private_key: kp.private_key.clone(),
}
}
}
/// KEM encapsulation result — ciphertext + shared secret.
pub struct CccKemEncapResult {
pub ciphertext: Vec<u8>,
pub shared_secret: Vec<u8>,
}
impl From<KemEncapResult> for CccKemEncapResult {
fn from(er: KemEncapResult) -> Self {
Self {
ciphertext: er.ciphertext.clone(),
shared_secret: er.shared_secret.clone(),
}
}
}
/// Per-algorithm self-test result.
pub struct CccAlgoTestResult {
pub algo_id: u32,
pub algo_name: String,
pub passed: bool,
pub error_message: Option<String>,
}
impl From<&AlgoTestResult> for CccAlgoTestResult {
fn from(r: &AlgoTestResult) -> Self {
Self {
algo_id: r.algo_id,
algo_name: r.algo_name.clone(),
passed: r.passed,
error_message: r.error_message.clone(),
}
}
}
/// Aggregate self-test report.
pub struct CccSelfTestReport {
pub provider_name: String,
pub results: Vec<CccAlgoTestResult>,
pub all_passed: bool,
}
impl From<SelfTestReport> for CccSelfTestReport {
fn from(report: SelfTestReport) -> Self {
Self {
provider_name: report.provider_name,
results: report.results.iter().map(CccAlgoTestResult::from).collect(),
all_passed: report.all_passed,
}
}
}
// ── Bridge error ─────────────────────────────────────────────────────────────
/// Bridge-visible error type — maps 1:1 from `ccc_rust::CryptoError`.
///
/// FRB translates this into a Dart exception.
#[derive(Debug)]
pub enum CccCryptoError {
UnsupportedAlgorithm(String),
InvalidKey(String),
InvalidNonce(String),
AuthenticationFailed,
InvalidInput(String),
FeatureNotCompiled(String),
InternalError(String),
}
impl std::fmt::Display for CccCryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedAlgorithm(s) => write!(f, "unsupported algorithm: {s}"),
Self::InvalidKey(s) => write!(f, "invalid key: {s}"),
Self::InvalidNonce(s) => write!(f, "invalid nonce: {s}"),
Self::AuthenticationFailed => write!(f, "authentication failed"),
Self::InvalidInput(s) => write!(f, "invalid input: {s}"),
Self::FeatureNotCompiled(s) => write!(f, "feature not compiled: {s}"),
Self::InternalError(s) => write!(f, "internal error: {s}"),
}
}
}
impl std::error::Error for CccCryptoError {}
impl From<ccc_crypto_core::CryptoError> for CccCryptoError {
fn from(e: ccc_crypto_core::CryptoError) -> Self {
match e {
ccc_crypto_core::CryptoError::UnsupportedAlgorithm(s) => Self::UnsupportedAlgorithm(s),
ccc_crypto_core::CryptoError::InvalidKey(s) => Self::InvalidKey(s),
ccc_crypto_core::CryptoError::InvalidNonce(s) => Self::InvalidNonce(s),
ccc_crypto_core::CryptoError::AuthenticationFailed => Self::AuthenticationFailed,
ccc_crypto_core::CryptoError::InvalidInput(s) => Self::InvalidInput(s),
ccc_crypto_core::CryptoError::FeatureNotCompiled(s) => Self::FeatureNotCompiled(s),
ccc_crypto_core::CryptoError::InternalError(s) => Self::InternalError(s),
}
}
}

3
rust/src/api/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod crypto;
pub mod dto;
pub mod simple;

10
rust/src/api/simple.rs Normal file
View File

@ -0,0 +1,10 @@
#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet(name: String) -> String {
format!("Hello, {name}!")
}
#[flutter_rust_bridge::frb(init)]
pub fn init_app() {
// Default utilities - feel free to customize
flutter_rust_bridge::setup_default_user_utils();
}

1612
rust/src/frb_generated.rs Normal file

File diff suppressed because it is too large Load Diff

2
rust/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod api;
mod frb_generated;

View File

@ -1,22 +0,0 @@
# The Flutter tooling requires that developers have CMake 3.10 or later
# installed. You should not increase this version, as doing so will cause
# the plugin to fail to compile for some customers of the plugin.
cmake_minimum_required(VERSION 3.10)
project(ccc_cryptography_library VERSION 0.0.1 LANGUAGES C)
add_library(ccc_cryptography SHARED
"ccc_cryptography.c"
)
set_target_properties(ccc_cryptography PROPERTIES
PUBLIC_HEADER ccc_cryptography.h
OUTPUT_NAME "ccc_cryptography"
)
target_compile_definitions(ccc_cryptography PUBLIC DART_SHARED_LIB)
if (ANDROID)
# Support Android 15 16k page size
target_link_options(ccc_cryptography PRIVATE "-Wl,-z,max-page-size=16384")
endif()

View File

@ -1,23 +0,0 @@
#include "ccc_cryptography.h"
// A very short-lived native function.
//
// For very short-lived functions, it is fine to call them on the main isolate.
// They will block the Dart execution while running the native function, so
// only do this for native functions which are guaranteed to be short-lived.
FFI_PLUGIN_EXPORT int sum(int a, int b) { return a + b; }
// A longer-lived native function, which occupies the thread calling it.
//
// Do not call these kind of native functions in the main isolate. They will
// block Dart execution. This will cause dropped frames in Flutter applications.
// Instead, call these native functions on a separate isolate.
FFI_PLUGIN_EXPORT int sum_long_running(int a, int b) {
// Simulate work.
#if _WIN32
Sleep(5000);
#else
usleep(5000 * 1000);
#endif
return a + b;
}

View File

@ -1,30 +0,0 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#if _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <unistd.h>
#endif
#if _WIN32
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FFI_PLUGIN_EXPORT
#endif
// A very short-lived native function.
//
// For very short-lived functions, it is fine to call them on the main isolate.
// They will block the Dart execution while running the native function, so
// only do this for native functions which are guaranteed to be short-lived.
FFI_PLUGIN_EXPORT int sum(int a, int b);
// A longer lived native function, which occupies the thread calling it.
//
// Do not call these kind of native functions in the main isolate. They will
// block Dart execution. This will cause dropped frames in Flutter applications.
// Instead, call these native functions on a separate isolate.
FFI_PLUGIN_EXPORT int sum_long_running(int a, int b);

View File

@ -0,0 +1,3 @@
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();

View File

@ -8,16 +8,13 @@ cmake_minimum_required(VERSION 3.14)
set(PROJECT_NAME "ccc_cryptography")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
# This can be changed to accommodate different builds.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
include("../cargokit/cmake/cargokit.cmake")
apply_cargokit(${PROJECT_NAME} ../rust ccc_cryptography "")
# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(ccc_cryptography_bundled_libraries
# Defined in ../src/CMakeLists.txt.
# This can be changed to accommodate different builds.
$<TARGET_FILE:ccc_cryptography>
"${${PROJECT_NAME}_cargokit_lib}"
PARENT_SCOPE
)