From 0b332bdd6185c4c9d274bcc0d1ce580d26c00d8c Mon Sep 17 00:00:00 2001 From: Tien Do Nam <38380847+Tienisto@users.noreply.github.com> Date: Sat, 12 Jul 2025 18:59:45 +0200 Subject: [PATCH] feat: webrtc discovery (#2204) --- app/flutter_rust_bridge.yaml | 3 + app/ios/MyLocalSendStoreKit.storekit | 12 +- app/ios/Podfile.lock | 6 + app/lib/config/init.dart | 15 + app/lib/config/init_error.dart | 12 +- app/lib/model/cross_file.mapper.dart | 49 +- app/lib/model/log_entry.mapper.dart | 42 +- .../persistence/favorite_device.mapper.dart | 65 +- .../receive_history_entry.mapper.dart | 70 +- app/lib/model/state/nearby_devices_state.dart | 43 + .../state/nearby_devices_state.mapper.dart | 109 +- app/lib/model/state/network_state.mapper.dart | 57 +- .../model/state/purchase_state.mapper.dart | 77 +- .../model/state/send/send_session_state.dart | 11 +- .../state/send/send_session_state.mapper.dart | 101 +- .../model/state/send/sending_file.mapper.dart | 52 +- .../state/send/web/web_send_file.mapper.dart | 62 +- .../send/web/web_send_session.mapper.dart | 72 +- .../state/send/web/web_send_state.mapper.dart | 75 +- .../state/server/receive_session_state.dart | 16 +- .../server/receive_session_state.mapper.dart | 114 +- .../state/server/receiving_file.mapper.dart | 60 +- .../state/server/server_state.mapper.dart | 59 +- .../model/state/settings_state.mapper.dart | 179 +- app/lib/pages/donation/donation_page.dart | 2 +- app/lib/pages/progress_page.dart | 21 +- app/lib/pages/receive_history_page.dart | 31 +- app/lib/pages/receive_options_page.dart | 37 +- app/lib/pages/receive_page.dart | 310 +- app/lib/pages/receive_page_controller.dart | 194 - .../pages/receive_page_controller.mapper.dart | 182 - app/lib/pages/tabs/send_tab.dart | 4 +- app/lib/pages/tabs/send_tab_vm.dart | 2 +- app/lib/pages/tabs/settings_tab.dart | 2 +- .../pages/tabs/settings_tab_vm.mapper.dart | 240 +- app/lib/provider/device_info_provider.dart | 2 + .../network/nearby_devices_provider.dart | 44 +- .../server/controller/receive_controller.dart | 55 +- .../network/webrtc/signaling_provider.dart | 231 ++ .../webrtc/signaling_provider.mapper.dart | 164 + .../network/webrtc/webrtc_receiver.dart | 218 + .../webrtc/webrtc_receiver.mapper.dart | 166 + .../param/apk_provider_param.mapper.dart | 83 +- .../cached_apk_provider_param.mapper.dart | 95 +- app/lib/provider/persistence_provider.dart | 30 + app/lib/rust/api/crypto.dart | 35 + app/lib/rust/api/logging.dart | 10 + app/lib/rust/api/model.dart | 82 + app/lib/rust/api/webrtc.dart | 352 ++ app/lib/rust/api/webrtc.freezed.dart | 2626 ++++++++++++ app/lib/rust/frb_generated.dart | 3079 ++++++++++++++ app/lib/rust/frb_generated.io.dart | 987 +++++ app/lib/rust/frb_generated.web.dart | 927 +++++ .../channel/android_channel.mapper.dart | 114 +- app/lib/util/security_helper.dart | 30 +- .../widget/list_tile/device_list_tile.dart | 17 +- app/linux/flutter/generated_plugins.cmake | 1 + app/macos/Podfile.lock | 56 +- app/pubspec.lock | 45 +- app/pubspec.yaml | 14 +- app/rust/.gitignore | 1 + app/rust/Cargo.lock | 3520 +++++++++++++++++ app/rust/Cargo.toml | 17 + app/rust/src/api/crypto.rs | 21 + app/rust/src/api/logging.rs | 11 + app/rust/src/api/mod.rs | 4 + app/rust/src/api/model.rs | 72 + app/rust/src/api/webrtc.rs | 514 +++ app/rust/src/frb_generated.rs | 3392 ++++++++++++++++ app/rust/src/lib.rs | 3 + app/rust/src/util/bytes.rs | 35 + app/rust/src/util/mod.rs | 1 + app/rust_builder/.gitignore | 29 + app/rust_builder/README.md | 1 + app/rust_builder/android/.gitignore | 9 + app/rust_builder/android/build.gradle | 56 + app/rust_builder/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + app/rust_builder/cargokit/.gitignore | 4 + app/rust_builder/cargokit/LICENSE | 42 + app/rust_builder/cargokit/README | 11 + app/rust_builder/cargokit/build_pod.sh | 58 + .../cargokit/build_tool/README.md | 5 + .../cargokit/build_tool/analysis_options.yaml | 34 + .../cargokit/build_tool/bin/build_tool.dart | 8 + .../cargokit/build_tool/lib/build_tool.dart | 8 + .../lib/src/android_environment.dart | 195 + .../lib/src/artifacts_provider.dart | 266 ++ .../build_tool/lib/src/build_cmake.dart | 40 + .../build_tool/lib/src/build_gradle.dart | 49 + .../build_tool/lib/src/build_pod.dart | 89 + .../build_tool/lib/src/build_tool.dart | 271 ++ .../cargokit/build_tool/lib/src/builder.dart | 198 + .../cargokit/build_tool/lib/src/cargo.dart | 48 + .../build_tool/lib/src/crate_hash.dart | 124 + .../build_tool/lib/src/environment.dart | 68 + .../cargokit/build_tool/lib/src/logging.dart | 52 + .../cargokit/build_tool/lib/src/options.dart | 309 ++ .../lib/src/precompile_binaries.dart | 202 + .../cargokit/build_tool/lib/src/rustup.dart | 136 + .../cargokit/build_tool/lib/src/target.dart | 140 + .../cargokit/build_tool/lib/src/util.dart | 172 + .../build_tool/lib/src/verify_binaries.dart | 84 + .../cargokit/build_tool/pubspec.lock | 453 +++ .../cargokit/build_tool/pubspec.yaml | 33 + .../cargokit/cmake/cargokit.cmake | 99 + .../cargokit/cmake/resolve_symlinks.ps1 | 27 + .../cargokit/gradle/plugin.gradle | 179 + app/rust_builder/cargokit/run_build_tool.cmd | 91 + app/rust_builder/cargokit/run_build_tool.sh | 94 + app/rust_builder/ios/Classes/dummy_file.c | 1 + .../ios/rust_lib_localsend_app.podspec | 45 + app/rust_builder/linux/CMakeLists.txt | 19 + app/rust_builder/macos/Classes/dummy_file.c | 1 + .../macos/rust_lib_localsend_app.podspec | 44 + app/rust_builder/pubspec.yaml | 34 + app/rust_builder/windows/.gitignore | 17 + app/rust_builder/windows/CMakeLists.txt | 20 + app/test/mocks.mocks.dart | 66 +- .../provider/last_devices_provider_test.dart | 2 + .../unit/util/api_route_builder_test.dart | 2 + app/test/unit/util/security_helper_test.dart | 17 +- app/windows/flutter/generated_plugins.cmake | 1 + common/lib/model/device.dart | 81 +- common/lib/model/device.mapper.dart | 354 +- common/lib/model/dto/file_dto.dart | 30 +- common/lib/model/dto/info_dto.dart | 4 +- common/lib/model/dto/info_register_dto.dart | 4 +- common/lib/model/dto/multicast_dto.dart | 2 + common/lib/model/dto/register_dto.dart | 4 +- .../task/discovery/http_target_discovery.dart | 2 +- common/pubspec.yaml | 2 +- common/test/unit/model/device_test.dart | 60 + core/.gitignore | 1 + core/Cargo.lock | 3350 ++++++++++++++++ core/Cargo.toml | 42 + core/src/crypto/cert.rs | 186 + core/src/crypto/hash.rs | 7 + core/src/crypto/mod.rs | 4 + core/src/crypto/nonce.rs | 11 + core/src/crypto/token.rs | 254 ++ core/src/http/client.rs | 207 + core/src/http/mod.rs | 11 + core/src/http/server.rs | 274 ++ core/src/lib.rs | 7 + core/src/main.rs | 480 +++ core/src/model/discovery.rs | 58 + core/src/model/mod.rs | 2 + core/src/model/transfer.rs | 40 + core/src/util/base64.rs | 13 + core/src/util/mod.rs | 2 + core/src/util/time.rs | 7 + core/src/webrtc/mod.rs | 3 + core/src/webrtc/signaling.rs | 528 +++ core/src/webrtc/webrtc.rs | 1401 +++++++ server/.gitignore | 1 + server/Cargo.lock | 1293 ++++++ server/Cargo.toml | 18 + server/Dockerfile | 24 + server/README.md | 3 + server/src/config/error.rs | 59 + server/src/config/init.rs | 20 + server/src/config/mod.rs | 4 + server/src/config/scheduler.rs | 24 + server/src/config/state.rs | 33 + server/src/controller/mod.rs | 1 + server/src/controller/ws_controller.rs | 369 ++ server/src/main.rs | 33 + server/src/util/base64.rs | 9 + server/src/util/ip.rs | 40 + server/src/util/mod.rs | 2 + 171 files changed, 31552 insertions(+), 1209 deletions(-) create mode 100644 app/flutter_rust_bridge.yaml delete mode 100644 app/lib/pages/receive_page_controller.dart delete mode 100644 app/lib/pages/receive_page_controller.mapper.dart create mode 100644 app/lib/provider/network/webrtc/signaling_provider.dart create mode 100644 app/lib/provider/network/webrtc/signaling_provider.mapper.dart create mode 100644 app/lib/provider/network/webrtc/webrtc_receiver.dart create mode 100644 app/lib/provider/network/webrtc/webrtc_receiver.mapper.dart create mode 100644 app/lib/rust/api/crypto.dart create mode 100644 app/lib/rust/api/logging.dart create mode 100644 app/lib/rust/api/model.dart create mode 100644 app/lib/rust/api/webrtc.dart create mode 100644 app/lib/rust/api/webrtc.freezed.dart create mode 100644 app/lib/rust/frb_generated.dart create mode 100644 app/lib/rust/frb_generated.io.dart create mode 100644 app/lib/rust/frb_generated.web.dart create mode 100644 app/rust/.gitignore create mode 100644 app/rust/Cargo.lock create mode 100644 app/rust/Cargo.toml create mode 100644 app/rust/src/api/crypto.rs create mode 100644 app/rust/src/api/logging.rs create mode 100644 app/rust/src/api/mod.rs create mode 100644 app/rust/src/api/model.rs create mode 100644 app/rust/src/api/webrtc.rs create mode 100644 app/rust/src/frb_generated.rs create mode 100644 app/rust/src/lib.rs create mode 100644 app/rust/src/util/bytes.rs create mode 100644 app/rust/src/util/mod.rs create mode 100644 app/rust_builder/.gitignore create mode 100644 app/rust_builder/README.md create mode 100644 app/rust_builder/android/.gitignore create mode 100644 app/rust_builder/android/build.gradle create mode 100644 app/rust_builder/android/settings.gradle create mode 100644 app/rust_builder/android/src/main/AndroidManifest.xml create mode 100644 app/rust_builder/cargokit/.gitignore create mode 100644 app/rust_builder/cargokit/LICENSE create mode 100644 app/rust_builder/cargokit/README create mode 100644 app/rust_builder/cargokit/build_pod.sh create mode 100644 app/rust_builder/cargokit/build_tool/README.md create mode 100644 app/rust_builder/cargokit/build_tool/analysis_options.yaml create mode 100644 app/rust_builder/cargokit/build_tool/bin/build_tool.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/build_tool.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/android_environment.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/build_pod.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/build_tool.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/builder.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/cargo.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/environment.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/logging.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/options.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/rustup.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/target.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/util.dart create mode 100644 app/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart create mode 100644 app/rust_builder/cargokit/build_tool/pubspec.lock create mode 100644 app/rust_builder/cargokit/build_tool/pubspec.yaml create mode 100644 app/rust_builder/cargokit/cmake/cargokit.cmake create mode 100644 app/rust_builder/cargokit/cmake/resolve_symlinks.ps1 create mode 100644 app/rust_builder/cargokit/gradle/plugin.gradle create mode 100644 app/rust_builder/cargokit/run_build_tool.cmd create mode 100644 app/rust_builder/cargokit/run_build_tool.sh create mode 100644 app/rust_builder/ios/Classes/dummy_file.c create mode 100644 app/rust_builder/ios/rust_lib_localsend_app.podspec create mode 100644 app/rust_builder/linux/CMakeLists.txt create mode 100644 app/rust_builder/macos/Classes/dummy_file.c create mode 100644 app/rust_builder/macos/rust_lib_localsend_app.podspec create mode 100644 app/rust_builder/pubspec.yaml create mode 100644 app/rust_builder/windows/.gitignore create mode 100644 app/rust_builder/windows/CMakeLists.txt create mode 100644 common/test/unit/model/device_test.dart create mode 100644 core/.gitignore create mode 100644 core/Cargo.lock create mode 100644 core/Cargo.toml create mode 100644 core/src/crypto/cert.rs create mode 100644 core/src/crypto/hash.rs create mode 100644 core/src/crypto/mod.rs create mode 100644 core/src/crypto/nonce.rs create mode 100644 core/src/crypto/token.rs create mode 100644 core/src/http/client.rs create mode 100644 core/src/http/mod.rs create mode 100644 core/src/http/server.rs create mode 100644 core/src/lib.rs create mode 100644 core/src/main.rs create mode 100644 core/src/model/discovery.rs create mode 100644 core/src/model/mod.rs create mode 100644 core/src/model/transfer.rs create mode 100644 core/src/util/base64.rs create mode 100644 core/src/util/mod.rs create mode 100644 core/src/util/time.rs create mode 100644 core/src/webrtc/mod.rs create mode 100644 core/src/webrtc/signaling.rs create mode 100644 core/src/webrtc/webrtc.rs create mode 100644 server/.gitignore create mode 100644 server/Cargo.lock create mode 100644 server/Cargo.toml create mode 100644 server/Dockerfile create mode 100644 server/README.md create mode 100644 server/src/config/error.rs create mode 100644 server/src/config/init.rs create mode 100644 server/src/config/mod.rs create mode 100644 server/src/config/scheduler.rs create mode 100644 server/src/config/state.rs create mode 100644 server/src/controller/mod.rs create mode 100644 server/src/controller/ws_controller.rs create mode 100644 server/src/main.rs create mode 100644 server/src/util/base64.rs create mode 100644 server/src/util/ip.rs create mode 100644 server/src/util/mod.rs diff --git a/app/flutter_rust_bridge.yaml b/app/flutter_rust_bridge.yaml new file mode 100644 index 00000000..69fc9296 --- /dev/null +++ b/app/flutter_rust_bridge.yaml @@ -0,0 +1,3 @@ +rust_input: crate::api +rust_root: rust/ +dart_output: lib/rust diff --git a/app/ios/MyLocalSendStoreKit.storekit b/app/ios/MyLocalSendStoreKit.storekit index bc3b7119..c563e477 100644 --- a/app/ios/MyLocalSendStoreKit.storekit +++ b/app/ios/MyLocalSendStoreKit.storekit @@ -1,4 +1,14 @@ { + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, "identifier" : "D8FDEAE9", "nonRenewingSubscriptions" : [ @@ -124,7 +134,7 @@ ], "version" : { - "major" : 3, + "major" : 4, "minor" : 0 } } diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index b49b3c8d..fe57c6ef 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -67,6 +67,8 @@ PODS: - FlutterMacOS - rhttp (0.0.1): - Flutter + - rust_lib_localsend_app (0.0.1): + - Flutter - SDWebImage (5.20.0): - SDWebImage/Core (= 5.20.0) - SDWebImage/Core (5.20.0) @@ -111,6 +113,7 @@ DEPENDENCIES: - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - rhttp (from `.symlinks/plugins/rhttp/ios`) + - rust_lib_localsend_app (from `.symlinks/plugins/rust_lib_localsend_app/ios`) - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -160,6 +163,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/photo_manager/ios" rhttp: :path: ".symlinks/plugins/rhttp/ios" + rust_lib_localsend_app: + :path: ".symlinks/plugins/rust_lib_localsend_app/ios" share_handler_ios: :path: ".symlinks/plugins/share_handler_ios/ios" share_handler_ios_models: @@ -196,6 +201,7 @@ SPEC CHECKSUMS: permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a rhttp: 367a8162e63311c6dde543169b591cc04454dcd6 + rust_lib_localsend_app: 7613d2267cc6c941255182b0c88dac5f638f94ed SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 share_handler_ios: 6dd3a4ac5ca0d955274aec712ba0ecdcaf583e7c share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 diff --git a/app/lib/config/init.dart b/app/lib/config/init.dart index c884afcf..cf886b01 100644 --- a/app/lib/config/init.dart +++ b/app/lib/config/init.dart @@ -22,6 +22,7 @@ import 'package:localsend_app/provider/app_arguments_provider.dart'; import 'package:localsend_app/provider/device_info_provider.dart'; import 'package:localsend_app/provider/network/nearby_devices_provider.dart'; import 'package:localsend_app/provider/network/server/server_provider.dart'; +import 'package:localsend_app/provider/network/webrtc/signaling_provider.dart'; import 'package:localsend_app/provider/persistence_provider.dart'; // [FOSS_REMOVE_START] @@ -32,6 +33,8 @@ import 'package:localsend_app/provider/selection/selected_sending_files_provider import 'package:localsend_app/provider/settings_provider.dart'; import 'package:localsend_app/provider/tv_provider.dart'; import 'package:localsend_app/provider/window_dimensions_provider.dart'; +import 'package:localsend_app/rust/api/logging.dart' as rust_logging; +import 'package:localsend_app/rust/frb_generated.dart'; import 'package:localsend_app/util/i18n.dart'; import 'package:localsend_app/util/native/autostart_helper.dart'; import 'package:localsend_app/util/native/cache_helper.dart'; @@ -60,6 +63,16 @@ Future preInit(List args) async { initLogger(args.contains('-v') || args.contains('--verbose') ? Level.ALL : Level.INFO); MapperContainer.globals.use(const FileDtoMapper()); + await RustLib.init(); + + if (kDebugMode) { + try { + await rust_logging.enableDebugLogging(); + } catch (e) { + _logger.warning('Enabling debug logging failed', e); + } + } + await Rhttp.init(); final dynamicColors = await getDynamicColors(); @@ -206,6 +219,8 @@ Future postInit(BuildContext context, Ref ref, bool appStart) async { _logger.warning('Starting multicast listener failed', e); } + ref.redux(signalingProvider).dispatch(SetupSignalingConnection()); + if (appStart) { if (defaultTargetPlatform == TargetPlatform.macOS) { // handle dropped files diff --git a/app/lib/config/init_error.dart b/app/lib/config/init_error.dart index d9e2e1a5..85f082a3 100644 --- a/app/lib/config/init_error.dart +++ b/app/lib/config/init_error.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:localsend_app/util/native/platform_check.dart'; +import 'package:localsend_app/util/native/tray_helper.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:refena_flutter/refena_flutter.dart'; import 'package:window_manager/window_manager.dart'; final _logger = Logger('Init'); @@ -18,10 +20,14 @@ void showInitErrorApp({ await WindowManager.instance.show(); } - runApp(_ErrorApp( - error: error, - stackTrace: stackTrace, + runApp(RefenaScope( + child: _ErrorApp( + error: error, + stackTrace: stackTrace, + ), )); + + await showFromTray(); } class _ErrorApp extends StatefulWidget { diff --git a/app/lib/model/cross_file.mapper.dart b/app/lib/model/cross_file.mapper.dart index 497abec9..3239f28d 100644 --- a/app/lib/model/cross_file.mapper.dart +++ b/app/lib/model/cross_file.mapper.dart @@ -24,11 +24,13 @@ class CrossFileMapper extends ClassMapperBase { static String _$name(CrossFile v) => v.name; static const Field _f$name = Field('name', _$name); static FileType _$fileType(CrossFile v) => v.fileType; - static const Field _f$fileType = Field('fileType', _$fileType); + static const Field _f$fileType = + Field('fileType', _$fileType); static int _$size(CrossFile v) => v.size; static const Field _f$size = Field('size', _$size); static Uint8List? _$thumbnail(CrossFile v) => v.thumbnail; - static const Field _f$thumbnail = Field('thumbnail', _$thumbnail); + static const Field _f$thumbnail = + Field('thumbnail', _$thumbnail); static AssetEntity? _$asset(CrossFile v) => v.asset; static const Field _f$asset = Field('asset', _$asset); static String? _$path(CrossFile v) => v.path; @@ -36,9 +38,11 @@ class CrossFileMapper extends ClassMapperBase { static List? _$bytes(CrossFile v) => v.bytes; static const Field> _f$bytes = Field('bytes', _$bytes); static DateTime? _$lastModified(CrossFile v) => v.lastModified; - static const Field _f$lastModified = Field('lastModified', _$lastModified); + static const Field _f$lastModified = + Field('lastModified', _$lastModified); static DateTime? _$lastAccessed(CrossFile v) => v.lastAccessed; - static const Field _f$lastAccessed = Field('lastAccessed', _$lastAccessed); + static const Field _f$lastAccessed = + Field('lastAccessed', _$lastAccessed); @override final MappableFields fields = const { @@ -80,22 +84,27 @@ class CrossFileMapper extends ClassMapperBase { mixin CrossFileMappable { String serialize() { - return CrossFileMapper.ensureInitialized().encodeJson(this as CrossFile); + return CrossFileMapper.ensureInitialized() + .encodeJson(this as CrossFile); } Map toJson() { - return CrossFileMapper.ensureInitialized().encodeMap(this as CrossFile); + return CrossFileMapper.ensureInitialized() + .encodeMap(this as CrossFile); } - CrossFileCopyWith get copyWith => _CrossFileCopyWithImpl(this as CrossFile, $identity, $identity); + CrossFileCopyWith get copyWith => + _CrossFileCopyWithImpl(this as CrossFile, $identity, $identity); @override String toString() { - return CrossFileMapper.ensureInitialized().stringifyValue(this as CrossFile); + return CrossFileMapper.ensureInitialized() + .stringifyValue(this as CrossFile); } @override bool operator ==(Object other) { - return CrossFileMapper.ensureInitialized().equalsValue(this as CrossFile, other); + return CrossFileMapper.ensureInitialized() + .equalsValue(this as CrossFile, other); } @override @@ -105,10 +114,12 @@ mixin CrossFileMappable { } extension CrossFileValueCopy<$R, $Out> on ObjectCopyWith<$R, CrossFile, $Out> { - CrossFileCopyWith<$R, CrossFile, $Out> get $asCrossFile => $base.as((v, t, t2) => _CrossFileCopyWithImpl(v, t, t2)); + CrossFileCopyWith<$R, CrossFile, $Out> get $asCrossFile => + $base.as((v, t, t2) => _CrossFileCopyWithImpl(v, t, t2)); } -abstract class CrossFileCopyWith<$R, $In extends CrossFile, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class CrossFileCopyWith<$R, $In extends CrossFile, $Out> + implements ClassCopyWith<$R, $In, $Out> { ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes; $R call( {String? name, @@ -123,14 +134,20 @@ abstract class CrossFileCopyWith<$R, $In extends CrossFile, $Out> implements Cla CrossFileCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _CrossFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, CrossFile, $Out> implements CrossFileCopyWith<$R, CrossFile, $Out> { +class _CrossFileCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, CrossFile, $Out> + implements CrossFileCopyWith<$R, CrossFile, $Out> { _CrossFileCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = CrossFileMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + CrossFileMapper.ensureInitialized(); @override ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes => - $value.bytes != null ? ListCopyWith($value.bytes!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) : null; + $value.bytes != null + ? ListCopyWith($value.bytes!, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) + : null; @override $R call( {String? name, @@ -166,5 +183,7 @@ class _CrossFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, CrossFile, lastAccessed: data.get(#lastAccessed, or: $value.lastAccessed)); @override - CrossFileCopyWith<$R2, CrossFile, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _CrossFileCopyWithImpl($value, $cast, t); + CrossFileCopyWith<$R2, CrossFile, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _CrossFileCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/log_entry.mapper.dart b/app/lib/model/log_entry.mapper.dart index 9c663f51..491d90c6 100644 --- a/app/lib/model/log_entry.mapper.dart +++ b/app/lib/model/log_entry.mapper.dart @@ -21,7 +21,8 @@ class LogEntryMapper extends ClassMapperBase { final String id = 'LogEntry'; static DateTime _$timestamp(LogEntry v) => v.timestamp; - static const Field _f$timestamp = Field('timestamp', _$timestamp); + static const Field _f$timestamp = + Field('timestamp', _$timestamp); static String _$log(LogEntry v) => v.log; static const Field _f$log = Field('log', _$log); @@ -49,14 +50,17 @@ class LogEntryMapper extends ClassMapperBase { mixin LogEntryMappable { String serialize() { - return LogEntryMapper.ensureInitialized().encodeJson(this as LogEntry); + return LogEntryMapper.ensureInitialized() + .encodeJson(this as LogEntry); } Map toJson() { - return LogEntryMapper.ensureInitialized().encodeMap(this as LogEntry); + return LogEntryMapper.ensureInitialized() + .encodeMap(this as LogEntry); } - LogEntryCopyWith get copyWith => _LogEntryCopyWithImpl(this as LogEntry, $identity, $identity); + LogEntryCopyWith get copyWith => + _LogEntryCopyWithImpl(this as LogEntry, $identity, $identity); @override String toString() { return LogEntryMapper.ensureInitialized().stringifyValue(this as LogEntry); @@ -64,7 +68,8 @@ mixin LogEntryMappable { @override bool operator ==(Object other) { - return LogEntryMapper.ensureInitialized().equalsValue(this as LogEntry, other); + return LogEntryMapper.ensureInitialized() + .equalsValue(this as LogEntry, other); } @override @@ -74,25 +79,36 @@ mixin LogEntryMappable { } extension LogEntryValueCopy<$R, $Out> on ObjectCopyWith<$R, LogEntry, $Out> { - LogEntryCopyWith<$R, LogEntry, $Out> get $asLogEntry => $base.as((v, t, t2) => _LogEntryCopyWithImpl(v, t, t2)); + LogEntryCopyWith<$R, LogEntry, $Out> get $asLogEntry => + $base.as((v, t, t2) => _LogEntryCopyWithImpl(v, t, t2)); } -abstract class LogEntryCopyWith<$R, $In extends LogEntry, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class LogEntryCopyWith<$R, $In extends LogEntry, $Out> + implements ClassCopyWith<$R, $In, $Out> { $R call({DateTime? timestamp, String? log}); LogEntryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _LogEntryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, LogEntry, $Out> implements LogEntryCopyWith<$R, LogEntry, $Out> { +class _LogEntryCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, LogEntry, $Out> + implements LogEntryCopyWith<$R, LogEntry, $Out> { _LogEntryCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = LogEntryMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + LogEntryMapper.ensureInitialized(); @override - $R call({DateTime? timestamp, String? log}) => - $apply(FieldCopyWithData({if (timestamp != null) #timestamp: timestamp, if (log != null) #log: log})); + $R call({DateTime? timestamp, String? log}) => $apply(FieldCopyWithData({ + if (timestamp != null) #timestamp: timestamp, + if (log != null) #log: log + })); @override - LogEntry $make(CopyWithData data) => LogEntry(timestamp: data.get(#timestamp, or: $value.timestamp), log: data.get(#log, or: $value.log)); + LogEntry $make(CopyWithData data) => LogEntry( + timestamp: data.get(#timestamp, or: $value.timestamp), + log: data.get(#log, or: $value.log)); @override - LogEntryCopyWith<$R2, LogEntry, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _LogEntryCopyWithImpl($value, $cast, t); + LogEntryCopyWith<$R2, LogEntry, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _LogEntryCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/persistence/favorite_device.mapper.dart b/app/lib/model/persistence/favorite_device.mapper.dart index 69b6f152..fe5a64fc 100644 --- a/app/lib/model/persistence/favorite_device.mapper.dart +++ b/app/lib/model/persistence/favorite_device.mapper.dart @@ -23,7 +23,8 @@ class FavoriteDeviceMapper extends ClassMapperBase { static String _$id(FavoriteDevice v) => v.id; static const Field _f$id = Field('id', _$id); static String _$fingerprint(FavoriteDevice v) => v.fingerprint; - static const Field _f$fingerprint = Field('fingerprint', _$fingerprint); + static const Field _f$fingerprint = + Field('fingerprint', _$fingerprint); static String _$ip(FavoriteDevice v) => v.ip; static const Field _f$ip = Field('ip', _$ip); static int _$port(FavoriteDevice v) => v.port; @@ -31,7 +32,8 @@ class FavoriteDeviceMapper extends ClassMapperBase { static String _$alias(FavoriteDevice v) => v.alias; static const Field _f$alias = Field('alias', _$alias); static bool _$customAlias(FavoriteDevice v) => v.customAlias; - static const Field _f$customAlias = Field('customAlias', _$customAlias, opt: true, def: false); + static const Field _f$customAlias = + Field('customAlias', _$customAlias, opt: true, def: false); @override final MappableFields fields = const { @@ -67,48 +69,73 @@ class FavoriteDeviceMapper extends ClassMapperBase { mixin FavoriteDeviceMappable { String serialize() { - return FavoriteDeviceMapper.ensureInitialized().encodeJson(this as FavoriteDevice); + return FavoriteDeviceMapper.ensureInitialized() + .encodeJson(this as FavoriteDevice); } Map toJson() { - return FavoriteDeviceMapper.ensureInitialized().encodeMap(this as FavoriteDevice); + return FavoriteDeviceMapper.ensureInitialized() + .encodeMap(this as FavoriteDevice); } - FavoriteDeviceCopyWith get copyWith => - _FavoriteDeviceCopyWithImpl(this as FavoriteDevice, $identity, $identity); + FavoriteDeviceCopyWith + get copyWith => _FavoriteDeviceCopyWithImpl( + this as FavoriteDevice, $identity, $identity); @override String toString() { - return FavoriteDeviceMapper.ensureInitialized().stringifyValue(this as FavoriteDevice); + return FavoriteDeviceMapper.ensureInitialized() + .stringifyValue(this as FavoriteDevice); } @override bool operator ==(Object other) { - return FavoriteDeviceMapper.ensureInitialized().equalsValue(this as FavoriteDevice, other); + return FavoriteDeviceMapper.ensureInitialized() + .equalsValue(this as FavoriteDevice, other); } @override int get hashCode { - return FavoriteDeviceMapper.ensureInitialized().hashValue(this as FavoriteDevice); + return FavoriteDeviceMapper.ensureInitialized() + .hashValue(this as FavoriteDevice); } } -extension FavoriteDeviceValueCopy<$R, $Out> on ObjectCopyWith<$R, FavoriteDevice, $Out> { - FavoriteDeviceCopyWith<$R, FavoriteDevice, $Out> get $asFavoriteDevice => $base.as((v, t, t2) => _FavoriteDeviceCopyWithImpl(v, t, t2)); +extension FavoriteDeviceValueCopy<$R, $Out> + on ObjectCopyWith<$R, FavoriteDevice, $Out> { + FavoriteDeviceCopyWith<$R, FavoriteDevice, $Out> get $asFavoriteDevice => + $base.as((v, t, t2) => _FavoriteDeviceCopyWithImpl(v, t, t2)); } -abstract class FavoriteDeviceCopyWith<$R, $In extends FavoriteDevice, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call({String? id, String? fingerprint, String? ip, int? port, String? alias, bool? customAlias}); - FavoriteDeviceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +abstract class FavoriteDeviceCopyWith<$R, $In extends FavoriteDevice, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call( + {String? id, + String? fingerprint, + String? ip, + int? port, + String? alias, + bool? customAlias}); + FavoriteDeviceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _FavoriteDeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, FavoriteDevice, $Out> +class _FavoriteDeviceCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, FavoriteDevice, $Out> implements FavoriteDeviceCopyWith<$R, FavoriteDevice, $Out> { _FavoriteDeviceCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = FavoriteDeviceMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + FavoriteDeviceMapper.ensureInitialized(); @override - $R call({String? id, String? fingerprint, String? ip, int? port, String? alias, bool? customAlias}) => $apply(FieldCopyWithData({ + $R call( + {String? id, + String? fingerprint, + String? ip, + int? port, + String? alias, + bool? customAlias}) => + $apply(FieldCopyWithData({ if (id != null) #id: id, if (fingerprint != null) #fingerprint: fingerprint, if (ip != null) #ip: ip, @@ -126,5 +153,7 @@ class _FavoriteDeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Favori customAlias: data.get(#customAlias, or: $value.customAlias)); @override - FavoriteDeviceCopyWith<$R2, FavoriteDevice, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _FavoriteDeviceCopyWithImpl($value, $cast, t); + FavoriteDeviceCopyWith<$R2, FavoriteDevice, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _FavoriteDeviceCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/persistence/receive_history_entry.mapper.dart b/app/lib/model/persistence/receive_history_entry.mapper.dart index 00c47528..b72dba92 100644 --- a/app/lib/model/persistence/receive_history_entry.mapper.dart +++ b/app/lib/model/persistence/receive_history_entry.mapper.dart @@ -24,21 +24,29 @@ class ReceiveHistoryEntryMapper extends ClassMapperBase { static String _$id(ReceiveHistoryEntry v) => v.id; static const Field _f$id = Field('id', _$id); static String _$fileName(ReceiveHistoryEntry v) => v.fileName; - static const Field _f$fileName = Field('fileName', _$fileName); + static const Field _f$fileName = + Field('fileName', _$fileName); static FileType _$fileType(ReceiveHistoryEntry v) => v.fileType; - static const Field _f$fileType = Field('fileType', _$fileType); + static const Field _f$fileType = + Field('fileType', _$fileType); static String? _$path(ReceiveHistoryEntry v) => v.path; - static const Field _f$path = Field('path', _$path); + static const Field _f$path = + Field('path', _$path); static bool _$savedToGallery(ReceiveHistoryEntry v) => v.savedToGallery; - static const Field _f$savedToGallery = Field('savedToGallery', _$savedToGallery); + static const Field _f$savedToGallery = + Field('savedToGallery', _$savedToGallery); static bool _$isMessage(ReceiveHistoryEntry v) => v.isMessage; - static const Field _f$isMessage = Field('isMessage', _$isMessage, hook: IsMessageHook()); + static const Field _f$isMessage = + Field('isMessage', _$isMessage, hook: IsMessageHook()); static int _$fileSize(ReceiveHistoryEntry v) => v.fileSize; - static const Field _f$fileSize = Field('fileSize', _$fileSize); + static const Field _f$fileSize = + Field('fileSize', _$fileSize); static String _$senderAlias(ReceiveHistoryEntry v) => v.senderAlias; - static const Field _f$senderAlias = Field('senderAlias', _$senderAlias); + static const Field _f$senderAlias = + Field('senderAlias', _$senderAlias); static DateTime _$timestamp(ReceiveHistoryEntry v) => v.timestamp; - static const Field _f$timestamp = Field('timestamp', _$timestamp); + static const Field _f$timestamp = + Field('timestamp', _$timestamp); @override final MappableFields fields = const { @@ -80,37 +88,47 @@ class ReceiveHistoryEntryMapper extends ClassMapperBase { mixin ReceiveHistoryEntryMappable { String serialize() { - return ReceiveHistoryEntryMapper.ensureInitialized().encodeJson(this as ReceiveHistoryEntry); + return ReceiveHistoryEntryMapper.ensureInitialized() + .encodeJson(this as ReceiveHistoryEntry); } Map toJson() { - return ReceiveHistoryEntryMapper.ensureInitialized().encodeMap(this as ReceiveHistoryEntry); + return ReceiveHistoryEntryMapper.ensureInitialized() + .encodeMap(this as ReceiveHistoryEntry); } - ReceiveHistoryEntryCopyWith get copyWith => - _ReceiveHistoryEntryCopyWithImpl(this as ReceiveHistoryEntry, $identity, $identity); + ReceiveHistoryEntryCopyWith + get copyWith => _ReceiveHistoryEntryCopyWithImpl( + this as ReceiveHistoryEntry, $identity, $identity); @override String toString() { - return ReceiveHistoryEntryMapper.ensureInitialized().stringifyValue(this as ReceiveHistoryEntry); + return ReceiveHistoryEntryMapper.ensureInitialized() + .stringifyValue(this as ReceiveHistoryEntry); } @override bool operator ==(Object other) { - return ReceiveHistoryEntryMapper.ensureInitialized().equalsValue(this as ReceiveHistoryEntry, other); + return ReceiveHistoryEntryMapper.ensureInitialized() + .equalsValue(this as ReceiveHistoryEntry, other); } @override int get hashCode { - return ReceiveHistoryEntryMapper.ensureInitialized().hashValue(this as ReceiveHistoryEntry); + return ReceiveHistoryEntryMapper.ensureInitialized() + .hashValue(this as ReceiveHistoryEntry); } } -extension ReceiveHistoryEntryValueCopy<$R, $Out> on ObjectCopyWith<$R, ReceiveHistoryEntry, $Out> { - ReceiveHistoryEntryCopyWith<$R, ReceiveHistoryEntry, $Out> get $asReceiveHistoryEntry => - $base.as((v, t, t2) => _ReceiveHistoryEntryCopyWithImpl(v, t, t2)); +extension ReceiveHistoryEntryValueCopy<$R, $Out> + on ObjectCopyWith<$R, ReceiveHistoryEntry, $Out> { + ReceiveHistoryEntryCopyWith<$R, ReceiveHistoryEntry, $Out> + get $asReceiveHistoryEntry => + $base.as((v, t, t2) => _ReceiveHistoryEntryCopyWithImpl(v, t, t2)); } -abstract class ReceiveHistoryEntryCopyWith<$R, $In extends ReceiveHistoryEntry, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class ReceiveHistoryEntryCopyWith<$R, $In extends ReceiveHistoryEntry, + $Out> implements ClassCopyWith<$R, $In, $Out> { $R call( {String? id, String? fileName, @@ -121,15 +139,18 @@ abstract class ReceiveHistoryEntryCopyWith<$R, $In extends ReceiveHistoryEntry, int? fileSize, String? senderAlias, DateTime? timestamp}); - ReceiveHistoryEntryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); + ReceiveHistoryEntryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _ReceiveHistoryEntryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ReceiveHistoryEntry, $Out> +class _ReceiveHistoryEntryCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ReceiveHistoryEntry, $Out> implements ReceiveHistoryEntryCopyWith<$R, ReceiveHistoryEntry, $Out> { _ReceiveHistoryEntryCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = ReceiveHistoryEntryMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + ReceiveHistoryEntryMapper.ensureInitialized(); @override $R call( {String? id, @@ -165,6 +186,7 @@ class _ReceiveHistoryEntryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, R timestamp: data.get(#timestamp, or: $value.timestamp)); @override - ReceiveHistoryEntryCopyWith<$R2, ReceiveHistoryEntry, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _ReceiveHistoryEntryCopyWithImpl($value, $cast, t); + ReceiveHistoryEntryCopyWith<$R2, ReceiveHistoryEntry, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _ReceiveHistoryEntryCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/nearby_devices_state.dart b/app/lib/model/state/nearby_devices_state.dart index ffb1386e..3ac4c967 100644 --- a/app/lib/model/state/nearby_devices_state.dart +++ b/app/lib/model/state/nearby_devices_state.dart @@ -9,9 +9,52 @@ class NearbyDevicesState with NearbyDevicesStateMappable { final Set runningIps; // list of local ips final Map devices; // ip -> device + /// Devices that are discovered via signaling server. + /// The key is the fingerprint of the device. + /// We do not trust the fingerprint, so we allow multiple devices with the same fingerprint. + final Map> signalingDevices; + const NearbyDevicesState({ required this.runningFavoriteScan, required this.runningIps, required this.devices, + required this.signalingDevices, }); + + Map get allDevices { + final Map allDevices = {}; + allDevices.addAll(devices); + for (final devices in signalingDevices.values) { + for (final device in devices) { + final currentDevice = allDevices[device.fingerprint]; + if (currentDevice != null && currentDevice.alias == device.alias) { + allDevices[device.fingerprint] = currentDevice.merge(device); + } else { + allDevices[device.fingerprint] = device; + } + } + } + return allDevices; + } +} + +extension on Device { + Device merge(Device other) { + return Device( + signalingId: signalingId ?? other.signalingId, + ip: ip ?? other.ip, + version: version, + port: port, + https: https, + fingerprint: fingerprint, + alias: alias, + deviceModel: deviceModel, + deviceType: deviceType, + download: download, + discoveryMethods: { + ...discoveryMethods, + ...other.discoveryMethods, + }, + ); + } } diff --git a/app/lib/model/state/nearby_devices_state.mapper.dart b/app/lib/model/state/nearby_devices_state.mapper.dart index 69d91c52..8f513c98 100644 --- a/app/lib/model/state/nearby_devices_state.mapper.dart +++ b/app/lib/model/state/nearby_devices_state.mapper.dart @@ -21,23 +21,35 @@ class NearbyDevicesStateMapper extends ClassMapperBase { @override final String id = 'NearbyDevicesState'; - static bool _$runningFavoriteScan(NearbyDevicesState v) => v.runningFavoriteScan; - static const Field _f$runningFavoriteScan = Field('runningFavoriteScan', _$runningFavoriteScan); + static bool _$runningFavoriteScan(NearbyDevicesState v) => + v.runningFavoriteScan; + static const Field _f$runningFavoriteScan = + Field('runningFavoriteScan', _$runningFavoriteScan); static Set _$runningIps(NearbyDevicesState v) => v.runningIps; - static const Field> _f$runningIps = Field('runningIps', _$runningIps); + static const Field> _f$runningIps = + Field('runningIps', _$runningIps); static Map _$devices(NearbyDevicesState v) => v.devices; - static const Field> _f$devices = Field('devices', _$devices); + static const Field> _f$devices = + Field('devices', _$devices); + static Map> _$signalingDevices(NearbyDevicesState v) => + v.signalingDevices; + static const Field>> + _f$signalingDevices = Field('signalingDevices', _$signalingDevices); @override final MappableFields fields = const { #runningFavoriteScan: _f$runningFavoriteScan, #runningIps: _f$runningIps, #devices: _f$devices, + #signalingDevices: _f$signalingDevices, }; static NearbyDevicesState _instantiate(DecodingData data) { return NearbyDevicesState( - runningFavoriteScan: data.dec(_f$runningFavoriteScan), runningIps: data.dec(_f$runningIps), devices: data.dec(_f$devices)); + runningFavoriteScan: data.dec(_f$runningFavoriteScan), + runningIps: data.dec(_f$runningIps), + devices: data.dec(_f$devices), + signalingDevices: data.dec(_f$signalingDevices)); } @override @@ -54,64 +66,103 @@ class NearbyDevicesStateMapper extends ClassMapperBase { mixin NearbyDevicesStateMappable { String serialize() { - return NearbyDevicesStateMapper.ensureInitialized().encodeJson(this as NearbyDevicesState); + return NearbyDevicesStateMapper.ensureInitialized() + .encodeJson(this as NearbyDevicesState); } Map toJson() { - return NearbyDevicesStateMapper.ensureInitialized().encodeMap(this as NearbyDevicesState); + return NearbyDevicesStateMapper.ensureInitialized() + .encodeMap(this as NearbyDevicesState); } - NearbyDevicesStateCopyWith get copyWith => - _NearbyDevicesStateCopyWithImpl(this as NearbyDevicesState, $identity, $identity); + NearbyDevicesStateCopyWith + get copyWith => _NearbyDevicesStateCopyWithImpl( + this as NearbyDevicesState, $identity, $identity); @override String toString() { - return NearbyDevicesStateMapper.ensureInitialized().stringifyValue(this as NearbyDevicesState); + return NearbyDevicesStateMapper.ensureInitialized() + .stringifyValue(this as NearbyDevicesState); } @override bool operator ==(Object other) { - return NearbyDevicesStateMapper.ensureInitialized().equalsValue(this as NearbyDevicesState, other); + return NearbyDevicesStateMapper.ensureInitialized() + .equalsValue(this as NearbyDevicesState, other); } @override int get hashCode { - return NearbyDevicesStateMapper.ensureInitialized().hashValue(this as NearbyDevicesState); + return NearbyDevicesStateMapper.ensureInitialized() + .hashValue(this as NearbyDevicesState); } } -extension NearbyDevicesStateValueCopy<$R, $Out> on ObjectCopyWith<$R, NearbyDevicesState, $Out> { - NearbyDevicesStateCopyWith<$R, NearbyDevicesState, $Out> get $asNearbyDevicesState => - $base.as((v, t, t2) => _NearbyDevicesStateCopyWithImpl(v, t, t2)); +extension NearbyDevicesStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, NearbyDevicesState, $Out> { + NearbyDevicesStateCopyWith<$R, NearbyDevicesState, $Out> + get $asNearbyDevicesState => + $base.as((v, t, t2) => _NearbyDevicesStateCopyWithImpl(v, t, t2)); } -abstract class NearbyDevicesStateCopyWith<$R, $In extends NearbyDevicesState, $Out> implements ClassCopyWith<$R, $In, $Out> { - MapCopyWith<$R, String, Device, DeviceCopyWith<$R, Device, Device>> get devices; - $R call({bool? runningFavoriteScan, Set? runningIps, Map? devices}); - NearbyDevicesStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +abstract class NearbyDevicesStateCopyWith<$R, $In extends NearbyDevicesState, + $Out> implements ClassCopyWith<$R, $In, $Out> { + MapCopyWith<$R, String, Device, DeviceCopyWith<$R, Device, Device>> + get devices; + MapCopyWith<$R, String, Set, + ObjectCopyWith<$R, Set, Set>> get signalingDevices; + $R call( + {bool? runningFavoriteScan, + Set? runningIps, + Map? devices, + Map>? signalingDevices}); + NearbyDevicesStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _NearbyDevicesStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, NearbyDevicesState, $Out> +class _NearbyDevicesStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, NearbyDevicesState, $Out> implements NearbyDevicesStateCopyWith<$R, NearbyDevicesState, $Out> { _NearbyDevicesStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = NearbyDevicesStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + NearbyDevicesStateMapper.ensureInitialized(); @override - MapCopyWith<$R, String, Device, DeviceCopyWith<$R, Device, Device>> get devices => - MapCopyWith($value.devices, (v, t) => v.copyWith.$chain(t), (v) => call(devices: v)); + MapCopyWith<$R, String, Device, DeviceCopyWith<$R, Device, Device>> + get devices => MapCopyWith($value.devices, (v, t) => v.copyWith.$chain(t), + (v) => call(devices: v)); @override - $R call({bool? runningFavoriteScan, Set? runningIps, Map? devices}) => $apply(FieldCopyWithData({ - if (runningFavoriteScan != null) #runningFavoriteScan: runningFavoriteScan, + MapCopyWith<$R, String, Set, + ObjectCopyWith<$R, Set, Set>> + get signalingDevices => MapCopyWith( + $value.signalingDevices, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(signalingDevices: v)); + @override + $R call( + {bool? runningFavoriteScan, + Set? runningIps, + Map? devices, + Map>? signalingDevices}) => + $apply(FieldCopyWithData({ + if (runningFavoriteScan != null) + #runningFavoriteScan: runningFavoriteScan, if (runningIps != null) #runningIps: runningIps, - if (devices != null) #devices: devices + if (devices != null) #devices: devices, + if (signalingDevices != null) #signalingDevices: signalingDevices })); @override NearbyDevicesState $make(CopyWithData data) => NearbyDevicesState( - runningFavoriteScan: data.get(#runningFavoriteScan, or: $value.runningFavoriteScan), + runningFavoriteScan: + data.get(#runningFavoriteScan, or: $value.runningFavoriteScan), runningIps: data.get(#runningIps, or: $value.runningIps), - devices: data.get(#devices, or: $value.devices)); + devices: data.get(#devices, or: $value.devices), + signalingDevices: + data.get(#signalingDevices, or: $value.signalingDevices)); @override - NearbyDevicesStateCopyWith<$R2, NearbyDevicesState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + NearbyDevicesStateCopyWith<$R2, NearbyDevicesState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => _NearbyDevicesStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/network_state.mapper.dart b/app/lib/model/state/network_state.mapper.dart index 997b7929..199774f2 100644 --- a/app/lib/model/state/network_state.mapper.dart +++ b/app/lib/model/state/network_state.mapper.dart @@ -21,9 +21,11 @@ class NetworkStateMapper extends ClassMapperBase { final String id = 'NetworkState'; static List _$localIps(NetworkState v) => v.localIps; - static const Field> _f$localIps = Field('localIps', _$localIps); + static const Field> _f$localIps = + Field('localIps', _$localIps); static bool _$initialized(NetworkState v) => v.initialized; - static const Field _f$initialized = Field('initialized', _$initialized); + static const Field _f$initialized = + Field('initialized', _$initialized); @override final MappableFields fields = const { @@ -32,7 +34,8 @@ class NetworkStateMapper extends ClassMapperBase { }; static NetworkState _instantiate(DecodingData data) { - return NetworkState(localIps: data.dec(_f$localIps), initialized: data.dec(_f$initialized)); + return NetworkState( + localIps: data.dec(_f$localIps), initialized: data.dec(_f$initialized)); } @override @@ -49,56 +52,74 @@ class NetworkStateMapper extends ClassMapperBase { mixin NetworkStateMappable { String serialize() { - return NetworkStateMapper.ensureInitialized().encodeJson(this as NetworkState); + return NetworkStateMapper.ensureInitialized() + .encodeJson(this as NetworkState); } Map toJson() { - return NetworkStateMapper.ensureInitialized().encodeMap(this as NetworkState); + return NetworkStateMapper.ensureInitialized() + .encodeMap(this as NetworkState); } NetworkStateCopyWith get copyWith => _NetworkStateCopyWithImpl(this as NetworkState, $identity, $identity); @override String toString() { - return NetworkStateMapper.ensureInitialized().stringifyValue(this as NetworkState); + return NetworkStateMapper.ensureInitialized() + .stringifyValue(this as NetworkState); } @override bool operator ==(Object other) { - return NetworkStateMapper.ensureInitialized().equalsValue(this as NetworkState, other); + return NetworkStateMapper.ensureInitialized() + .equalsValue(this as NetworkState, other); } @override int get hashCode { - return NetworkStateMapper.ensureInitialized().hashValue(this as NetworkState); + return NetworkStateMapper.ensureInitialized() + .hashValue(this as NetworkState); } } -extension NetworkStateValueCopy<$R, $Out> on ObjectCopyWith<$R, NetworkState, $Out> { - NetworkStateCopyWith<$R, NetworkState, $Out> get $asNetworkState => $base.as((v, t, t2) => _NetworkStateCopyWithImpl(v, t, t2)); +extension NetworkStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, NetworkState, $Out> { + NetworkStateCopyWith<$R, NetworkState, $Out> get $asNetworkState => + $base.as((v, t, t2) => _NetworkStateCopyWithImpl(v, t, t2)); } -abstract class NetworkStateCopyWith<$R, $In extends NetworkState, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class NetworkStateCopyWith<$R, $In extends NetworkState, $Out> + implements ClassCopyWith<$R, $In, $Out> { ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get localIps; $R call({List? localIps, bool? initialized}); NetworkStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _NetworkStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, NetworkState, $Out> implements NetworkStateCopyWith<$R, NetworkState, $Out> { +class _NetworkStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, NetworkState, $Out> + implements NetworkStateCopyWith<$R, NetworkState, $Out> { _NetworkStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = NetworkStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + NetworkStateMapper.ensureInitialized(); @override ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get localIps => - ListCopyWith($value.localIps, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(localIps: v)); + ListCopyWith($value.localIps, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(localIps: v)); @override $R call({List? localIps, bool? initialized}) => - $apply(FieldCopyWithData({if (localIps != null) #localIps: localIps, if (initialized != null) #initialized: initialized})); + $apply(FieldCopyWithData({ + if (localIps != null) #localIps: localIps, + if (initialized != null) #initialized: initialized + })); @override - NetworkState $make(CopyWithData data) => - NetworkState(localIps: data.get(#localIps, or: $value.localIps), initialized: data.get(#initialized, or: $value.initialized)); + NetworkState $make(CopyWithData data) => NetworkState( + localIps: data.get(#localIps, or: $value.localIps), + initialized: data.get(#initialized, or: $value.initialized)); @override - NetworkStateCopyWith<$R2, NetworkState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _NetworkStateCopyWithImpl($value, $cast, t); + NetworkStateCopyWith<$R2, NetworkState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _NetworkStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/purchase_state.mapper.dart b/app/lib/model/state/purchase_state.mapper.dart index 6d850997..7f90c33c 100644 --- a/app/lib/model/state/purchase_state.mapper.dart +++ b/app/lib/model/state/purchase_state.mapper.dart @@ -21,11 +21,14 @@ class PurchaseStateMapper extends ClassMapperBase { final String id = 'PurchaseState'; static Map _$prices(PurchaseState v) => v.prices; - static const Field> _f$prices = Field('prices', _$prices); + static const Field> _f$prices = + Field('prices', _$prices); static Set _$purchases(PurchaseState v) => v.purchases; - static const Field> _f$purchases = Field('purchases', _$purchases); + static const Field> _f$purchases = + Field('purchases', _$purchases); static bool _$pending(PurchaseState v) => v.pending; - static const Field _f$pending = Field('pending', _$pending); + static const Field _f$pending = + Field('pending', _$pending); @override final MappableFields fields = const { @@ -35,7 +38,10 @@ class PurchaseStateMapper extends ClassMapperBase { }; static PurchaseState _instantiate(DecodingData data) { - return PurchaseState(prices: data.dec(_f$prices), purchases: data.dec(_f$purchases), pending: data.dec(_f$pending)); + return PurchaseState( + prices: data.dec(_f$prices), + purchases: data.dec(_f$purchases), + pending: data.dec(_f$pending)); } @override @@ -52,53 +58,76 @@ class PurchaseStateMapper extends ClassMapperBase { mixin PurchaseStateMappable { String serialize() { - return PurchaseStateMapper.ensureInitialized().encodeJson(this as PurchaseState); + return PurchaseStateMapper.ensureInitialized() + .encodeJson(this as PurchaseState); } Map toJson() { - return PurchaseStateMapper.ensureInitialized().encodeMap(this as PurchaseState); + return PurchaseStateMapper.ensureInitialized() + .encodeMap(this as PurchaseState); } - PurchaseStateCopyWith get copyWith => - _PurchaseStateCopyWithImpl(this as PurchaseState, $identity, $identity); + PurchaseStateCopyWith + get copyWith => _PurchaseStateCopyWithImpl( + this as PurchaseState, $identity, $identity); @override String toString() { - return PurchaseStateMapper.ensureInitialized().stringifyValue(this as PurchaseState); + return PurchaseStateMapper.ensureInitialized() + .stringifyValue(this as PurchaseState); } @override bool operator ==(Object other) { - return PurchaseStateMapper.ensureInitialized().equalsValue(this as PurchaseState, other); + return PurchaseStateMapper.ensureInitialized() + .equalsValue(this as PurchaseState, other); } @override int get hashCode { - return PurchaseStateMapper.ensureInitialized().hashValue(this as PurchaseState); + return PurchaseStateMapper.ensureInitialized() + .hashValue(this as PurchaseState); } } -extension PurchaseStateValueCopy<$R, $Out> on ObjectCopyWith<$R, PurchaseState, $Out> { - PurchaseStateCopyWith<$R, PurchaseState, $Out> get $asPurchaseState => $base.as((v, t, t2) => _PurchaseStateCopyWithImpl(v, t, t2)); +extension PurchaseStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, PurchaseState, $Out> { + PurchaseStateCopyWith<$R, PurchaseState, $Out> get $asPurchaseState => + $base.as((v, t, t2) => _PurchaseStateCopyWithImpl(v, t, t2)); } -abstract class PurchaseStateCopyWith<$R, $In extends PurchaseState, $Out> implements ClassCopyWith<$R, $In, $Out> { - MapCopyWith<$R, PurchaseItem, String, ObjectCopyWith<$R, String, String>> get prices; - $R call({Map? prices, Set? purchases, bool? pending}); +abstract class PurchaseStateCopyWith<$R, $In extends PurchaseState, $Out> + implements ClassCopyWith<$R, $In, $Out> { + MapCopyWith<$R, PurchaseItem, String, ObjectCopyWith<$R, String, String>> + get prices; + $R call( + {Map? prices, + Set? purchases, + bool? pending}); PurchaseStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _PurchaseStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, PurchaseState, $Out> +class _PurchaseStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, PurchaseState, $Out> implements PurchaseStateCopyWith<$R, PurchaseState, $Out> { _PurchaseStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = PurchaseStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + PurchaseStateMapper.ensureInitialized(); @override - MapCopyWith<$R, PurchaseItem, String, ObjectCopyWith<$R, String, String>> get prices => - MapCopyWith($value.prices, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(prices: v)); + MapCopyWith<$R, PurchaseItem, String, ObjectCopyWith<$R, String, String>> + get prices => MapCopyWith($value.prices, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(prices: v)); @override - $R call({Map? prices, Set? purchases, bool? pending}) => $apply( - FieldCopyWithData({if (prices != null) #prices: prices, if (purchases != null) #purchases: purchases, if (pending != null) #pending: pending})); + $R call( + {Map? prices, + Set? purchases, + bool? pending}) => + $apply(FieldCopyWithData({ + if (prices != null) #prices: prices, + if (purchases != null) #purchases: purchases, + if (pending != null) #pending: pending + })); @override PurchaseState $make(CopyWithData data) => PurchaseState( prices: data.get(#prices, or: $value.prices), @@ -106,5 +135,7 @@ class _PurchaseStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Purchas pending: data.get(#pending, or: $value.pending)); @override - PurchaseStateCopyWith<$R2, PurchaseState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _PurchaseStateCopyWithImpl($value, $cast, t); + PurchaseStateCopyWith<$R2, PurchaseState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _PurchaseStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/send/send_session_state.dart b/app/lib/model/state/send/send_session_state.dart index cec99a1a..87a7b93f 100644 --- a/app/lib/model/state/send/send_session_state.dart +++ b/app/lib/model/state/send/send_session_state.dart @@ -2,19 +2,28 @@ import 'package:common/model/device.dart'; import 'package:common/model/session_status.dart'; import 'package:dart_mappable/dart_mappable.dart'; import 'package:localsend_app/model/state/send/sending_file.dart'; +import 'package:localsend_app/model/state/server/receive_session_state.dart'; part 'send_session_state.mapper.dart'; @MappableClass() -class SendSessionState with SendSessionStateMappable { +class SendSessionState with SendSessionStateMappable implements SessionState { final String sessionId; final String? remoteSessionId; // v2 final bool background; + + @override final SessionStatus status; + final Device target; final Map files; // file id as key + + @override final int? startTime; + + @override final int? endTime; + final List? sendingTasks; // used to cancel tasks final String? errorMessage; diff --git a/app/lib/model/state/send/send_session_state.mapper.dart b/app/lib/model/state/send/send_session_state.mapper.dart index ac31f2bc..8da6e6fa 100644 --- a/app/lib/model/state/send/send_session_state.mapper.dart +++ b/app/lib/model/state/send/send_session_state.mapper.dart @@ -23,25 +23,36 @@ class SendSessionStateMapper extends ClassMapperBase { final String id = 'SendSessionState'; static String _$sessionId(SendSessionState v) => v.sessionId; - static const Field _f$sessionId = Field('sessionId', _$sessionId); + static const Field _f$sessionId = + Field('sessionId', _$sessionId); static String? _$remoteSessionId(SendSessionState v) => v.remoteSessionId; - static const Field _f$remoteSessionId = Field('remoteSessionId', _$remoteSessionId); + static const Field _f$remoteSessionId = + Field('remoteSessionId', _$remoteSessionId); static bool _$background(SendSessionState v) => v.background; - static const Field _f$background = Field('background', _$background); + static const Field _f$background = + Field('background', _$background); static SessionStatus _$status(SendSessionState v) => v.status; - static const Field _f$status = Field('status', _$status); + static const Field _f$status = + Field('status', _$status); static Device _$target(SendSessionState v) => v.target; - static const Field _f$target = Field('target', _$target); + static const Field _f$target = + Field('target', _$target); static Map _$files(SendSessionState v) => v.files; - static const Field> _f$files = Field('files', _$files); + static const Field> _f$files = + Field('files', _$files); static int? _$startTime(SendSessionState v) => v.startTime; - static const Field _f$startTime = Field('startTime', _$startTime); + static const Field _f$startTime = + Field('startTime', _$startTime); static int? _$endTime(SendSessionState v) => v.endTime; - static const Field _f$endTime = Field('endTime', _$endTime); - static List? _$sendingTasks(SendSessionState v) => v.sendingTasks; - static const Field> _f$sendingTasks = Field('sendingTasks', _$sendingTasks); + static const Field _f$endTime = + Field('endTime', _$endTime); + static List? _$sendingTasks(SendSessionState v) => + v.sendingTasks; + static const Field> _f$sendingTasks = + Field('sendingTasks', _$sendingTasks); static String? _$errorMessage(SendSessionState v) => v.errorMessage; - static const Field _f$errorMessage = Field('errorMessage', _$errorMessage); + static const Field _f$errorMessage = + Field('errorMessage', _$errorMessage); @override final MappableFields fields = const { @@ -85,39 +96,51 @@ class SendSessionStateMapper extends ClassMapperBase { mixin SendSessionStateMappable { String serialize() { - return SendSessionStateMapper.ensureInitialized().encodeJson(this as SendSessionState); + return SendSessionStateMapper.ensureInitialized() + .encodeJson(this as SendSessionState); } Map toJson() { - return SendSessionStateMapper.ensureInitialized().encodeMap(this as SendSessionState); + return SendSessionStateMapper.ensureInitialized() + .encodeMap(this as SendSessionState); } - SendSessionStateCopyWith get copyWith => - _SendSessionStateCopyWithImpl(this as SendSessionState, $identity, $identity); + SendSessionStateCopyWith + get copyWith => _SendSessionStateCopyWithImpl( + this as SendSessionState, $identity, $identity); @override String toString() { - return SendSessionStateMapper.ensureInitialized().stringifyValue(this as SendSessionState); + return SendSessionStateMapper.ensureInitialized() + .stringifyValue(this as SendSessionState); } @override bool operator ==(Object other) { - return SendSessionStateMapper.ensureInitialized().equalsValue(this as SendSessionState, other); + return SendSessionStateMapper.ensureInitialized() + .equalsValue(this as SendSessionState, other); } @override int get hashCode { - return SendSessionStateMapper.ensureInitialized().hashValue(this as SendSessionState); + return SendSessionStateMapper.ensureInitialized() + .hashValue(this as SendSessionState); } } -extension SendSessionStateValueCopy<$R, $Out> on ObjectCopyWith<$R, SendSessionState, $Out> { - SendSessionStateCopyWith<$R, SendSessionState, $Out> get $asSendSessionState => $base.as((v, t, t2) => _SendSessionStateCopyWithImpl(v, t, t2)); +extension SendSessionStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, SendSessionState, $Out> { + SendSessionStateCopyWith<$R, SendSessionState, $Out> + get $asSendSessionState => + $base.as((v, t, t2) => _SendSessionStateCopyWithImpl(v, t, t2)); } -abstract class SendSessionStateCopyWith<$R, $In extends SendSessionState, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class SendSessionStateCopyWith<$R, $In extends SendSessionState, $Out> + implements ClassCopyWith<$R, $In, $Out> { DeviceCopyWith<$R, Device, Device> get target; - MapCopyWith<$R, String, SendingFile, SendingFileCopyWith<$R, SendingFile, SendingFile>> get files; - ListCopyWith<$R, SendingTask, ObjectCopyWith<$R, SendingTask, SendingTask>>? get sendingTasks; + MapCopyWith<$R, String, SendingFile, + SendingFileCopyWith<$R, SendingFile, SendingFile>> get files; + ListCopyWith<$R, SendingTask, ObjectCopyWith<$R, SendingTask, SendingTask>>? + get sendingTasks; $R call( {String? sessionId, String? remoteSessionId, @@ -129,24 +152,34 @@ abstract class SendSessionStateCopyWith<$R, $In extends SendSessionState, $Out> int? endTime, List? sendingTasks, String? errorMessage}); - SendSessionStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); + SendSessionStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _SendSessionStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SendSessionState, $Out> +class _SendSessionStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SendSessionState, $Out> implements SendSessionStateCopyWith<$R, SendSessionState, $Out> { _SendSessionStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = SendSessionStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + SendSessionStateMapper.ensureInitialized(); @override - DeviceCopyWith<$R, Device, Device> get target => $value.target.copyWith.$chain((v) => call(target: v)); + DeviceCopyWith<$R, Device, Device> get target => + $value.target.copyWith.$chain((v) => call(target: v)); @override - MapCopyWith<$R, String, SendingFile, SendingFileCopyWith<$R, SendingFile, SendingFile>> get files => - MapCopyWith($value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); + MapCopyWith<$R, String, SendingFile, + SendingFileCopyWith<$R, SendingFile, SendingFile>> + get files => MapCopyWith( + $value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); @override - ListCopyWith<$R, SendingTask, ObjectCopyWith<$R, SendingTask, SendingTask>>? get sendingTasks => $value.sendingTasks != null - ? ListCopyWith($value.sendingTasks!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(sendingTasks: v)) - : null; + ListCopyWith<$R, SendingTask, ObjectCopyWith<$R, SendingTask, SendingTask>>? + get sendingTasks => $value.sendingTasks != null + ? ListCopyWith( + $value.sendingTasks!, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(sendingTasks: v)) + : null; @override $R call( {String? sessionId, @@ -185,5 +218,7 @@ class _SendSessionStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Send errorMessage: data.get(#errorMessage, or: $value.errorMessage)); @override - SendSessionStateCopyWith<$R2, SendSessionState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _SendSessionStateCopyWithImpl($value, $cast, t); + SendSessionStateCopyWith<$R2, SendSessionState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SendSessionStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/send/sending_file.mapper.dart b/app/lib/model/state/send/sending_file.mapper.dart index 8d019b71..8807f0b2 100644 --- a/app/lib/model/state/send/sending_file.mapper.dart +++ b/app/lib/model/state/send/sending_file.mapper.dart @@ -23,19 +23,23 @@ class SendingFileMapper extends ClassMapperBase { static FileDto _$file(SendingFile v) => v.file; static const Field _f$file = Field('file', _$file); static FileStatus _$status(SendingFile v) => v.status; - static const Field _f$status = Field('status', _$status); + static const Field _f$status = + Field('status', _$status); static String? _$token(SendingFile v) => v.token; static const Field _f$token = Field('token', _$token); static Uint8List? _$thumbnail(SendingFile v) => v.thumbnail; - static const Field _f$thumbnail = Field('thumbnail', _$thumbnail); + static const Field _f$thumbnail = + Field('thumbnail', _$thumbnail); static AssetEntity? _$asset(SendingFile v) => v.asset; - static const Field _f$asset = Field('asset', _$asset); + static const Field _f$asset = + Field('asset', _$asset); static String? _$path(SendingFile v) => v.path; static const Field _f$path = Field('path', _$path); static List? _$bytes(SendingFile v) => v.bytes; static const Field> _f$bytes = Field('bytes', _$bytes); static String? _$errorMessage(SendingFile v) => v.errorMessage; - static const Field _f$errorMessage = Field('errorMessage', _$errorMessage); + static const Field _f$errorMessage = + Field('errorMessage', _$errorMessage); @override final MappableFields fields = const { @@ -75,22 +79,27 @@ class SendingFileMapper extends ClassMapperBase { mixin SendingFileMappable { String serialize() { - return SendingFileMapper.ensureInitialized().encodeJson(this as SendingFile); + return SendingFileMapper.ensureInitialized() + .encodeJson(this as SendingFile); } Map toJson() { - return SendingFileMapper.ensureInitialized().encodeMap(this as SendingFile); + return SendingFileMapper.ensureInitialized() + .encodeMap(this as SendingFile); } - SendingFileCopyWith get copyWith => _SendingFileCopyWithImpl(this as SendingFile, $identity, $identity); + SendingFileCopyWith get copyWith => + _SendingFileCopyWithImpl(this as SendingFile, $identity, $identity); @override String toString() { - return SendingFileMapper.ensureInitialized().stringifyValue(this as SendingFile); + return SendingFileMapper.ensureInitialized() + .stringifyValue(this as SendingFile); } @override bool operator ==(Object other) { - return SendingFileMapper.ensureInitialized().equalsValue(this as SendingFile, other); + return SendingFileMapper.ensureInitialized() + .equalsValue(this as SendingFile, other); } @override @@ -99,11 +108,14 @@ mixin SendingFileMappable { } } -extension SendingFileValueCopy<$R, $Out> on ObjectCopyWith<$R, SendingFile, $Out> { - SendingFileCopyWith<$R, SendingFile, $Out> get $asSendingFile => $base.as((v, t, t2) => _SendingFileCopyWithImpl(v, t, t2)); +extension SendingFileValueCopy<$R, $Out> + on ObjectCopyWith<$R, SendingFile, $Out> { + SendingFileCopyWith<$R, SendingFile, $Out> get $asSendingFile => + $base.as((v, t, t2) => _SendingFileCopyWithImpl(v, t, t2)); } -abstract class SendingFileCopyWith<$R, $In extends SendingFile, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class SendingFileCopyWith<$R, $In extends SendingFile, $Out> + implements ClassCopyWith<$R, $In, $Out> { ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes; $R call( {FileDto? file, @@ -117,14 +129,20 @@ abstract class SendingFileCopyWith<$R, $In extends SendingFile, $Out> implements SendingFileCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _SendingFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SendingFile, $Out> implements SendingFileCopyWith<$R, SendingFile, $Out> { +class _SendingFileCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SendingFile, $Out> + implements SendingFileCopyWith<$R, SendingFile, $Out> { _SendingFileCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = SendingFileMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + SendingFileMapper.ensureInitialized(); @override ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes => - $value.bytes != null ? ListCopyWith($value.bytes!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) : null; + $value.bytes != null + ? ListCopyWith($value.bytes!, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) + : null; @override $R call( {FileDto? file, @@ -157,5 +175,7 @@ class _SendingFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SendingFi errorMessage: data.get(#errorMessage, or: $value.errorMessage)); @override - SendingFileCopyWith<$R2, SendingFile, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _SendingFileCopyWithImpl($value, $cast, t); + SendingFileCopyWith<$R2, SendingFile, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SendingFileCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/send/web/web_send_file.mapper.dart b/app/lib/model/state/send/web/web_send_file.mapper.dart index ed04ece0..530fa84e 100644 --- a/app/lib/model/state/send/web/web_send_file.mapper.dart +++ b/app/lib/model/state/send/web/web_send_file.mapper.dart @@ -23,7 +23,8 @@ class WebSendFileMapper extends ClassMapperBase { static FileDto _$file(WebSendFile v) => v.file; static const Field _f$file = Field('file', _$file); static AssetEntity? _$asset(WebSendFile v) => v.asset; - static const Field _f$asset = Field('asset', _$asset); + static const Field _f$asset = + Field('asset', _$asset); static String? _$path(WebSendFile v) => v.path; static const Field _f$path = Field('path', _$path); static List? _$bytes(WebSendFile v) => v.bytes; @@ -38,7 +39,11 @@ class WebSendFileMapper extends ClassMapperBase { }; static WebSendFile _instantiate(DecodingData data) { - return WebSendFile(file: data.dec(_f$file), asset: data.dec(_f$asset), path: data.dec(_f$path), bytes: data.dec(_f$bytes)); + return WebSendFile( + file: data.dec(_f$file), + asset: data.dec(_f$asset), + path: data.dec(_f$path), + bytes: data.dec(_f$bytes)); } @override @@ -55,22 +60,27 @@ class WebSendFileMapper extends ClassMapperBase { mixin WebSendFileMappable { String serialize() { - return WebSendFileMapper.ensureInitialized().encodeJson(this as WebSendFile); + return WebSendFileMapper.ensureInitialized() + .encodeJson(this as WebSendFile); } Map toJson() { - return WebSendFileMapper.ensureInitialized().encodeMap(this as WebSendFile); + return WebSendFileMapper.ensureInitialized() + .encodeMap(this as WebSendFile); } - WebSendFileCopyWith get copyWith => _WebSendFileCopyWithImpl(this as WebSendFile, $identity, $identity); + WebSendFileCopyWith get copyWith => + _WebSendFileCopyWithImpl(this as WebSendFile, $identity, $identity); @override String toString() { - return WebSendFileMapper.ensureInitialized().stringifyValue(this as WebSendFile); + return WebSendFileMapper.ensureInitialized() + .stringifyValue(this as WebSendFile); } @override bool operator ==(Object other) { - return WebSendFileMapper.ensureInitialized().equalsValue(this as WebSendFile, other); + return WebSendFileMapper.ensureInitialized() + .equalsValue(this as WebSendFile, other); } @override @@ -79,27 +89,45 @@ mixin WebSendFileMappable { } } -extension WebSendFileValueCopy<$R, $Out> on ObjectCopyWith<$R, WebSendFile, $Out> { - WebSendFileCopyWith<$R, WebSendFile, $Out> get $asWebSendFile => $base.as((v, t, t2) => _WebSendFileCopyWithImpl(v, t, t2)); +extension WebSendFileValueCopy<$R, $Out> + on ObjectCopyWith<$R, WebSendFile, $Out> { + WebSendFileCopyWith<$R, WebSendFile, $Out> get $asWebSendFile => + $base.as((v, t, t2) => _WebSendFileCopyWithImpl(v, t, t2)); } -abstract class WebSendFileCopyWith<$R, $In extends WebSendFile, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class WebSendFileCopyWith<$R, $In extends WebSendFile, $Out> + implements ClassCopyWith<$R, $In, $Out> { ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes; $R call({FileDto? file, AssetEntity? asset, String? path, List? bytes}); WebSendFileCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _WebSendFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSendFile, $Out> implements WebSendFileCopyWith<$R, WebSendFile, $Out> { +class _WebSendFileCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WebSendFile, $Out> + implements WebSendFileCopyWith<$R, WebSendFile, $Out> { _WebSendFileCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = WebSendFileMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + WebSendFileMapper.ensureInitialized(); @override ListCopyWith<$R, int, ObjectCopyWith<$R, int, int>>? get bytes => - $value.bytes != null ? ListCopyWith($value.bytes!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) : null; + $value.bytes != null + ? ListCopyWith($value.bytes!, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(bytes: v)) + : null; @override - $R call({FileDto? file, Object? asset = $none, Object? path = $none, Object? bytes = $none}) => $apply(FieldCopyWithData( - {if (file != null) #file: file, if (asset != $none) #asset: asset, if (path != $none) #path: path, if (bytes != $none) #bytes: bytes})); + $R call( + {FileDto? file, + Object? asset = $none, + Object? path = $none, + Object? bytes = $none}) => + $apply(FieldCopyWithData({ + if (file != null) #file: file, + if (asset != $none) #asset: asset, + if (path != $none) #path: path, + if (bytes != $none) #bytes: bytes + })); @override WebSendFile $make(CopyWithData data) => WebSendFile( file: data.get(#file, or: $value.file), @@ -108,5 +136,7 @@ class _WebSendFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSendFi bytes: data.get(#bytes, or: $value.bytes)); @override - WebSendFileCopyWith<$R2, WebSendFile, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _WebSendFileCopyWithImpl($value, $cast, t); + WebSendFileCopyWith<$R2, WebSendFile, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _WebSendFileCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/send/web/web_send_session.mapper.dart b/app/lib/model/state/send/web/web_send_session.mapper.dart index 6aa91de8..3f75661b 100644 --- a/app/lib/model/state/send/web/web_send_session.mapper.dart +++ b/app/lib/model/state/send/web/web_send_session.mapper.dart @@ -21,13 +21,17 @@ class WebSendSessionMapper extends ClassMapperBase { final String id = 'WebSendSession'; static String _$sessionId(WebSendSession v) => v.sessionId; - static const Field _f$sessionId = Field('sessionId', _$sessionId); - static StreamController? _$responseHandler(WebSendSession v) => v.responseHandler; - static const Field> _f$responseHandler = Field('responseHandler', _$responseHandler); + static const Field _f$sessionId = + Field('sessionId', _$sessionId); + static StreamController? _$responseHandler(WebSendSession v) => + v.responseHandler; + static const Field> + _f$responseHandler = Field('responseHandler', _$responseHandler); static String _$ip(WebSendSession v) => v.ip; static const Field _f$ip = Field('ip', _$ip); static String _$deviceInfo(WebSendSession v) => v.deviceInfo; - static const Field _f$deviceInfo = Field('deviceInfo', _$deviceInfo); + static const Field _f$deviceInfo = + Field('deviceInfo', _$deviceInfo); @override final MappableFields fields = const { @@ -39,7 +43,10 @@ class WebSendSessionMapper extends ClassMapperBase { static WebSendSession _instantiate(DecodingData data) { return WebSendSession( - sessionId: data.dec(_f$sessionId), responseHandler: data.dec(_f$responseHandler), ip: data.dec(_f$ip), deviceInfo: data.dec(_f$deviceInfo)); + sessionId: data.dec(_f$sessionId), + responseHandler: data.dec(_f$responseHandler), + ip: data.dec(_f$ip), + deviceInfo: data.dec(_f$deviceInfo)); } @override @@ -56,48 +63,69 @@ class WebSendSessionMapper extends ClassMapperBase { mixin WebSendSessionMappable { String serialize() { - return WebSendSessionMapper.ensureInitialized().encodeJson(this as WebSendSession); + return WebSendSessionMapper.ensureInitialized() + .encodeJson(this as WebSendSession); } Map toJson() { - return WebSendSessionMapper.ensureInitialized().encodeMap(this as WebSendSession); + return WebSendSessionMapper.ensureInitialized() + .encodeMap(this as WebSendSession); } - WebSendSessionCopyWith get copyWith => - _WebSendSessionCopyWithImpl(this as WebSendSession, $identity, $identity); + WebSendSessionCopyWith + get copyWith => _WebSendSessionCopyWithImpl( + this as WebSendSession, $identity, $identity); @override String toString() { - return WebSendSessionMapper.ensureInitialized().stringifyValue(this as WebSendSession); + return WebSendSessionMapper.ensureInitialized() + .stringifyValue(this as WebSendSession); } @override bool operator ==(Object other) { - return WebSendSessionMapper.ensureInitialized().equalsValue(this as WebSendSession, other); + return WebSendSessionMapper.ensureInitialized() + .equalsValue(this as WebSendSession, other); } @override int get hashCode { - return WebSendSessionMapper.ensureInitialized().hashValue(this as WebSendSession); + return WebSendSessionMapper.ensureInitialized() + .hashValue(this as WebSendSession); } } -extension WebSendSessionValueCopy<$R, $Out> on ObjectCopyWith<$R, WebSendSession, $Out> { - WebSendSessionCopyWith<$R, WebSendSession, $Out> get $asWebSendSession => $base.as((v, t, t2) => _WebSendSessionCopyWithImpl(v, t, t2)); +extension WebSendSessionValueCopy<$R, $Out> + on ObjectCopyWith<$R, WebSendSession, $Out> { + WebSendSessionCopyWith<$R, WebSendSession, $Out> get $asWebSendSession => + $base.as((v, t, t2) => _WebSendSessionCopyWithImpl(v, t, t2)); } -abstract class WebSendSessionCopyWith<$R, $In extends WebSendSession, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call({String? sessionId, StreamController? responseHandler, String? ip, String? deviceInfo}); - WebSendSessionCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +abstract class WebSendSessionCopyWith<$R, $In extends WebSendSession, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call( + {String? sessionId, + StreamController? responseHandler, + String? ip, + String? deviceInfo}); + WebSendSessionCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _WebSendSessionCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSendSession, $Out> +class _WebSendSessionCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WebSendSession, $Out> implements WebSendSessionCopyWith<$R, WebSendSession, $Out> { _WebSendSessionCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = WebSendSessionMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + WebSendSessionMapper.ensureInitialized(); @override - $R call({String? sessionId, Object? responseHandler = $none, String? ip, String? deviceInfo}) => $apply(FieldCopyWithData({ + $R call( + {String? sessionId, + Object? responseHandler = $none, + String? ip, + String? deviceInfo}) => + $apply(FieldCopyWithData({ if (sessionId != null) #sessionId: sessionId, if (responseHandler != $none) #responseHandler: responseHandler, if (ip != null) #ip: ip, @@ -111,5 +139,7 @@ class _WebSendSessionCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSen deviceInfo: data.get(#deviceInfo, or: $value.deviceInfo)); @override - WebSendSessionCopyWith<$R2, WebSendSession, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _WebSendSessionCopyWithImpl($value, $cast, t); + WebSendSessionCopyWith<$R2, WebSendSession, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _WebSendSessionCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/send/web/web_send_state.mapper.dart b/app/lib/model/state/send/web/web_send_state.mapper.dart index c96ccbe5..3150f2ba 100644 --- a/app/lib/model/state/send/web/web_send_state.mapper.dart +++ b/app/lib/model/state/send/web/web_send_state.mapper.dart @@ -23,15 +23,19 @@ class WebSendStateMapper extends ClassMapperBase { final String id = 'WebSendState'; static Map _$sessions(WebSendState v) => v.sessions; - static const Field> _f$sessions = Field('sessions', _$sessions); + static const Field> _f$sessions = + Field('sessions', _$sessions); static Map _$files(WebSendState v) => v.files; - static const Field> _f$files = Field('files', _$files); + static const Field> _f$files = + Field('files', _$files); static bool _$autoAccept(WebSendState v) => v.autoAccept; - static const Field _f$autoAccept = Field('autoAccept', _$autoAccept); + static const Field _f$autoAccept = + Field('autoAccept', _$autoAccept); static String? _$pin(WebSendState v) => v.pin; static const Field _f$pin = Field('pin', _$pin); static Map _$pinAttempts(WebSendState v) => v.pinAttempts; - static const Field> _f$pinAttempts = Field('pinAttempts', _$pinAttempts); + static const Field> _f$pinAttempts = + Field('pinAttempts', _$pinAttempts); @override final MappableFields fields = const { @@ -65,57 +69,80 @@ class WebSendStateMapper extends ClassMapperBase { mixin WebSendStateMappable { String serialize() { - return WebSendStateMapper.ensureInitialized().encodeJson(this as WebSendState); + return WebSendStateMapper.ensureInitialized() + .encodeJson(this as WebSendState); } Map toJson() { - return WebSendStateMapper.ensureInitialized().encodeMap(this as WebSendState); + return WebSendStateMapper.ensureInitialized() + .encodeMap(this as WebSendState); } WebSendStateCopyWith get copyWith => _WebSendStateCopyWithImpl(this as WebSendState, $identity, $identity); @override String toString() { - return WebSendStateMapper.ensureInitialized().stringifyValue(this as WebSendState); + return WebSendStateMapper.ensureInitialized() + .stringifyValue(this as WebSendState); } @override bool operator ==(Object other) { - return WebSendStateMapper.ensureInitialized().equalsValue(this as WebSendState, other); + return WebSendStateMapper.ensureInitialized() + .equalsValue(this as WebSendState, other); } @override int get hashCode { - return WebSendStateMapper.ensureInitialized().hashValue(this as WebSendState); + return WebSendStateMapper.ensureInitialized() + .hashValue(this as WebSendState); } } -extension WebSendStateValueCopy<$R, $Out> on ObjectCopyWith<$R, WebSendState, $Out> { - WebSendStateCopyWith<$R, WebSendState, $Out> get $asWebSendState => $base.as((v, t, t2) => _WebSendStateCopyWithImpl(v, t, t2)); +extension WebSendStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, WebSendState, $Out> { + WebSendStateCopyWith<$R, WebSendState, $Out> get $asWebSendState => + $base.as((v, t, t2) => _WebSendStateCopyWithImpl(v, t, t2)); } -abstract class WebSendStateCopyWith<$R, $In extends WebSendState, $Out> implements ClassCopyWith<$R, $In, $Out> { - MapCopyWith<$R, String, WebSendSession, WebSendSessionCopyWith<$R, WebSendSession, WebSendSession>> get sessions; - MapCopyWith<$R, String, WebSendFile, WebSendFileCopyWith<$R, WebSendFile, WebSendFile>> get files; +abstract class WebSendStateCopyWith<$R, $In extends WebSendState, $Out> + implements ClassCopyWith<$R, $In, $Out> { + MapCopyWith<$R, String, WebSendSession, + WebSendSessionCopyWith<$R, WebSendSession, WebSendSession>> get sessions; + MapCopyWith<$R, String, WebSendFile, + WebSendFileCopyWith<$R, WebSendFile, WebSendFile>> get files; MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get pinAttempts; - $R call({Map? sessions, Map? files, bool? autoAccept, String? pin, Map? pinAttempts}); + $R call( + {Map? sessions, + Map? files, + bool? autoAccept, + String? pin, + Map? pinAttempts}); WebSendStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _WebSendStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSendState, $Out> implements WebSendStateCopyWith<$R, WebSendState, $Out> { +class _WebSendStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WebSendState, $Out> + implements WebSendStateCopyWith<$R, WebSendState, $Out> { _WebSendStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = WebSendStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + WebSendStateMapper.ensureInitialized(); @override - MapCopyWith<$R, String, WebSendSession, WebSendSessionCopyWith<$R, WebSendSession, WebSendSession>> get sessions => - MapCopyWith($value.sessions, (v, t) => v.copyWith.$chain(t), (v) => call(sessions: v)); + MapCopyWith<$R, String, WebSendSession, + WebSendSessionCopyWith<$R, WebSendSession, WebSendSession>> + get sessions => MapCopyWith($value.sessions, + (v, t) => v.copyWith.$chain(t), (v) => call(sessions: v)); @override - MapCopyWith<$R, String, WebSendFile, WebSendFileCopyWith<$R, WebSendFile, WebSendFile>> get files => - MapCopyWith($value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); + MapCopyWith<$R, String, WebSendFile, + WebSendFileCopyWith<$R, WebSendFile, WebSendFile>> + get files => MapCopyWith( + $value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); @override MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get pinAttempts => - MapCopyWith($value.pinAttempts, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(pinAttempts: v)); + MapCopyWith($value.pinAttempts, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(pinAttempts: v)); @override $R call( {Map? sessions, @@ -139,5 +166,7 @@ class _WebSendStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, WebSendS pinAttempts: data.get(#pinAttempts, or: $value.pinAttempts)); @override - WebSendStateCopyWith<$R2, WebSendState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _WebSendStateCopyWithImpl($value, $cast, t); + WebSendStateCopyWith<$R2, WebSendState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _WebSendStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/server/receive_session_state.dart b/app/lib/model/state/server/receive_session_state.dart index a8335f6c..0498c39e 100644 --- a/app/lib/model/state/server/receive_session_state.dart +++ b/app/lib/model/state/server/receive_session_state.dart @@ -8,18 +8,32 @@ import 'package:localsend_app/model/state/server/receiving_file.dart'; part 'receive_session_state.mapper.dart'; +abstract class SessionState { + SessionStatus get status; + int? get startTime; + int? get endTime; +} + @MappableClass() -class ReceiveSessionState with ReceiveSessionStateMappable { +class ReceiveSessionState with ReceiveSessionStateMappable implements SessionState { final String sessionId; + + @override final SessionStatus status; + final Device sender; // Might not be the same as sender.alias since it can be overridden as a favorite final String senderAlias; final Map files; // file id as key + + @override final int? startTime; + + @override final int? endTime; + final String destinationDirectory; final String cacheDirectory; final bool saveToGallery; diff --git a/app/lib/model/state/server/receive_session_state.mapper.dart b/app/lib/model/state/server/receive_session_state.mapper.dart index a421144f..d94c51c6 100644 --- a/app/lib/model/state/server/receive_session_state.mapper.dart +++ b/app/lib/model/state/server/receive_session_state.mapper.dart @@ -23,29 +23,46 @@ class ReceiveSessionStateMapper extends ClassMapperBase { final String id = 'ReceiveSessionState'; static String _$sessionId(ReceiveSessionState v) => v.sessionId; - static const Field _f$sessionId = Field('sessionId', _$sessionId); + static const Field _f$sessionId = + Field('sessionId', _$sessionId); static SessionStatus _$status(ReceiveSessionState v) => v.status; - static const Field _f$status = Field('status', _$status); + static const Field _f$status = + Field('status', _$status); static Device _$sender(ReceiveSessionState v) => v.sender; - static const Field _f$sender = Field('sender', _$sender); + static const Field _f$sender = + Field('sender', _$sender); static String _$senderAlias(ReceiveSessionState v) => v.senderAlias; - static const Field _f$senderAlias = Field('senderAlias', _$senderAlias); + static const Field _f$senderAlias = + Field('senderAlias', _$senderAlias); static Map _$files(ReceiveSessionState v) => v.files; - static const Field> _f$files = Field('files', _$files); + static const Field> _f$files = + Field('files', _$files); static int? _$startTime(ReceiveSessionState v) => v.startTime; - static const Field _f$startTime = Field('startTime', _$startTime); + static const Field _f$startTime = + Field('startTime', _$startTime); static int? _$endTime(ReceiveSessionState v) => v.endTime; - static const Field _f$endTime = Field('endTime', _$endTime); - static String _$destinationDirectory(ReceiveSessionState v) => v.destinationDirectory; - static const Field _f$destinationDirectory = Field('destinationDirectory', _$destinationDirectory); + static const Field _f$endTime = + Field('endTime', _$endTime); + static String _$destinationDirectory(ReceiveSessionState v) => + v.destinationDirectory; + static const Field _f$destinationDirectory = + Field('destinationDirectory', _$destinationDirectory); static String _$cacheDirectory(ReceiveSessionState v) => v.cacheDirectory; - static const Field _f$cacheDirectory = Field('cacheDirectory', _$cacheDirectory); + static const Field _f$cacheDirectory = + Field('cacheDirectory', _$cacheDirectory); static bool _$saveToGallery(ReceiveSessionState v) => v.saveToGallery; - static const Field _f$saveToGallery = Field('saveToGallery', _$saveToGallery); - static Set _$createdDirectories(ReceiveSessionState v) => v.createdDirectories; - static const Field> _f$createdDirectories = Field('createdDirectories', _$createdDirectories); - static StreamController?>? _$responseHandler(ReceiveSessionState v) => v.responseHandler; - static const Field?>> _f$responseHandler = Field('responseHandler', _$responseHandler); + static const Field _f$saveToGallery = + Field('saveToGallery', _$saveToGallery); + static Set _$createdDirectories(ReceiveSessionState v) => + v.createdDirectories; + static const Field> _f$createdDirectories = + Field('createdDirectories', _$createdDirectories); + static StreamController?>? _$responseHandler( + ReceiveSessionState v) => + v.responseHandler; + static const Field?>> _f$responseHandler = + Field('responseHandler', _$responseHandler); @override final MappableFields fields = const { @@ -93,39 +110,50 @@ class ReceiveSessionStateMapper extends ClassMapperBase { mixin ReceiveSessionStateMappable { String serialize() { - return ReceiveSessionStateMapper.ensureInitialized().encodeJson(this as ReceiveSessionState); + return ReceiveSessionStateMapper.ensureInitialized() + .encodeJson(this as ReceiveSessionState); } Map toJson() { - return ReceiveSessionStateMapper.ensureInitialized().encodeMap(this as ReceiveSessionState); + return ReceiveSessionStateMapper.ensureInitialized() + .encodeMap(this as ReceiveSessionState); } - ReceiveSessionStateCopyWith get copyWith => - _ReceiveSessionStateCopyWithImpl(this as ReceiveSessionState, $identity, $identity); + ReceiveSessionStateCopyWith + get copyWith => _ReceiveSessionStateCopyWithImpl( + this as ReceiveSessionState, $identity, $identity); @override String toString() { - return ReceiveSessionStateMapper.ensureInitialized().stringifyValue(this as ReceiveSessionState); + return ReceiveSessionStateMapper.ensureInitialized() + .stringifyValue(this as ReceiveSessionState); } @override bool operator ==(Object other) { - return ReceiveSessionStateMapper.ensureInitialized().equalsValue(this as ReceiveSessionState, other); + return ReceiveSessionStateMapper.ensureInitialized() + .equalsValue(this as ReceiveSessionState, other); } @override int get hashCode { - return ReceiveSessionStateMapper.ensureInitialized().hashValue(this as ReceiveSessionState); + return ReceiveSessionStateMapper.ensureInitialized() + .hashValue(this as ReceiveSessionState); } } -extension ReceiveSessionStateValueCopy<$R, $Out> on ObjectCopyWith<$R, ReceiveSessionState, $Out> { - ReceiveSessionStateCopyWith<$R, ReceiveSessionState, $Out> get $asReceiveSessionState => - $base.as((v, t, t2) => _ReceiveSessionStateCopyWithImpl(v, t, t2)); +extension ReceiveSessionStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, ReceiveSessionState, $Out> { + ReceiveSessionStateCopyWith<$R, ReceiveSessionState, $Out> + get $asReceiveSessionState => + $base.as((v, t, t2) => _ReceiveSessionStateCopyWithImpl(v, t, t2)); } -abstract class ReceiveSessionStateCopyWith<$R, $In extends ReceiveSessionState, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class ReceiveSessionStateCopyWith<$R, $In extends ReceiveSessionState, + $Out> implements ClassCopyWith<$R, $In, $Out> { DeviceCopyWith<$R, Device, Device> get sender; - MapCopyWith<$R, String, ReceivingFile, ReceivingFileCopyWith<$R, ReceivingFile, ReceivingFile>> get files; + MapCopyWith<$R, String, ReceivingFile, + ReceivingFileCopyWith<$R, ReceivingFile, ReceivingFile>> get files; $R call( {String? sessionId, SessionStatus? status, @@ -139,20 +167,26 @@ abstract class ReceiveSessionStateCopyWith<$R, $In extends ReceiveSessionState, bool? saveToGallery, Set? createdDirectories, StreamController?>? responseHandler}); - ReceiveSessionStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); + ReceiveSessionStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _ReceiveSessionStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ReceiveSessionState, $Out> +class _ReceiveSessionStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ReceiveSessionState, $Out> implements ReceiveSessionStateCopyWith<$R, ReceiveSessionState, $Out> { _ReceiveSessionStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = ReceiveSessionStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + ReceiveSessionStateMapper.ensureInitialized(); @override - DeviceCopyWith<$R, Device, Device> get sender => $value.sender.copyWith.$chain((v) => call(sender: v)); + DeviceCopyWith<$R, Device, Device> get sender => + $value.sender.copyWith.$chain((v) => call(sender: v)); @override - MapCopyWith<$R, String, ReceivingFile, ReceivingFileCopyWith<$R, ReceivingFile, ReceivingFile>> get files => - MapCopyWith($value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); + MapCopyWith<$R, String, ReceivingFile, + ReceivingFileCopyWith<$R, ReceivingFile, ReceivingFile>> + get files => MapCopyWith( + $value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); @override $R call( {String? sessionId, @@ -175,7 +209,8 @@ class _ReceiveSessionStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, R if (files != null) #files: files, if (startTime != $none) #startTime: startTime, if (endTime != $none) #endTime: endTime, - if (destinationDirectory != null) #destinationDirectory: destinationDirectory, + if (destinationDirectory != null) + #destinationDirectory: destinationDirectory, if (cacheDirectory != null) #cacheDirectory: cacheDirectory, if (saveToGallery != null) #saveToGallery: saveToGallery, if (createdDirectories != null) #createdDirectories: createdDirectories, @@ -190,13 +225,16 @@ class _ReceiveSessionStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, R files: data.get(#files, or: $value.files), startTime: data.get(#startTime, or: $value.startTime), endTime: data.get(#endTime, or: $value.endTime), - destinationDirectory: data.get(#destinationDirectory, or: $value.destinationDirectory), + destinationDirectory: + data.get(#destinationDirectory, or: $value.destinationDirectory), cacheDirectory: data.get(#cacheDirectory, or: $value.cacheDirectory), saveToGallery: data.get(#saveToGallery, or: $value.saveToGallery), - createdDirectories: data.get(#createdDirectories, or: $value.createdDirectories), + createdDirectories: + data.get(#createdDirectories, or: $value.createdDirectories), responseHandler: data.get(#responseHandler, or: $value.responseHandler)); @override - ReceiveSessionStateCopyWith<$R2, ReceiveSessionState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _ReceiveSessionStateCopyWithImpl($value, $cast, t); + ReceiveSessionStateCopyWith<$R2, ReceiveSessionState, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _ReceiveSessionStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/server/receiving_file.mapper.dart b/app/lib/model/state/server/receiving_file.mapper.dart index 3b99d4e0..d898d63a 100644 --- a/app/lib/model/state/server/receiving_file.mapper.dart +++ b/app/lib/model/state/server/receiving_file.mapper.dart @@ -23,17 +23,21 @@ class ReceivingFileMapper extends ClassMapperBase { static FileDto _$file(ReceivingFile v) => v.file; static const Field _f$file = Field('file', _$file); static FileStatus _$status(ReceivingFile v) => v.status; - static const Field _f$status = Field('status', _$status); + static const Field _f$status = + Field('status', _$status); static String? _$token(ReceivingFile v) => v.token; static const Field _f$token = Field('token', _$token); static String? _$desiredName(ReceivingFile v) => v.desiredName; - static const Field _f$desiredName = Field('desiredName', _$desiredName); + static const Field _f$desiredName = + Field('desiredName', _$desiredName); static String? _$path(ReceivingFile v) => v.path; static const Field _f$path = Field('path', _$path); static bool _$savedToGallery(ReceivingFile v) => v.savedToGallery; - static const Field _f$savedToGallery = Field('savedToGallery', _$savedToGallery); + static const Field _f$savedToGallery = + Field('savedToGallery', _$savedToGallery); static String? _$errorMessage(ReceivingFile v) => v.errorMessage; - static const Field _f$errorMessage = Field('errorMessage', _$errorMessage); + static const Field _f$errorMessage = + Field('errorMessage', _$errorMessage); @override final MappableFields fields = const { @@ -71,46 +75,64 @@ class ReceivingFileMapper extends ClassMapperBase { mixin ReceivingFileMappable { String serialize() { - return ReceivingFileMapper.ensureInitialized().encodeJson(this as ReceivingFile); + return ReceivingFileMapper.ensureInitialized() + .encodeJson(this as ReceivingFile); } Map toJson() { - return ReceivingFileMapper.ensureInitialized().encodeMap(this as ReceivingFile); + return ReceivingFileMapper.ensureInitialized() + .encodeMap(this as ReceivingFile); } - ReceivingFileCopyWith get copyWith => - _ReceivingFileCopyWithImpl(this as ReceivingFile, $identity, $identity); + ReceivingFileCopyWith + get copyWith => _ReceivingFileCopyWithImpl( + this as ReceivingFile, $identity, $identity); @override String toString() { - return ReceivingFileMapper.ensureInitialized().stringifyValue(this as ReceivingFile); + return ReceivingFileMapper.ensureInitialized() + .stringifyValue(this as ReceivingFile); } @override bool operator ==(Object other) { - return ReceivingFileMapper.ensureInitialized().equalsValue(this as ReceivingFile, other); + return ReceivingFileMapper.ensureInitialized() + .equalsValue(this as ReceivingFile, other); } @override int get hashCode { - return ReceivingFileMapper.ensureInitialized().hashValue(this as ReceivingFile); + return ReceivingFileMapper.ensureInitialized() + .hashValue(this as ReceivingFile); } } -extension ReceivingFileValueCopy<$R, $Out> on ObjectCopyWith<$R, ReceivingFile, $Out> { - ReceivingFileCopyWith<$R, ReceivingFile, $Out> get $asReceivingFile => $base.as((v, t, t2) => _ReceivingFileCopyWithImpl(v, t, t2)); +extension ReceivingFileValueCopy<$R, $Out> + on ObjectCopyWith<$R, ReceivingFile, $Out> { + ReceivingFileCopyWith<$R, ReceivingFile, $Out> get $asReceivingFile => + $base.as((v, t, t2) => _ReceivingFileCopyWithImpl(v, t, t2)); } -abstract class ReceivingFileCopyWith<$R, $In extends ReceivingFile, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call({FileDto? file, FileStatus? status, String? token, String? desiredName, String? path, bool? savedToGallery, String? errorMessage}); +abstract class ReceivingFileCopyWith<$R, $In extends ReceivingFile, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call( + {FileDto? file, + FileStatus? status, + String? token, + String? desiredName, + String? path, + bool? savedToGallery, + String? errorMessage}); ReceivingFileCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _ReceivingFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ReceivingFile, $Out> +class _ReceivingFileCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ReceivingFile, $Out> implements ReceivingFileCopyWith<$R, ReceivingFile, $Out> { _ReceivingFileCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = ReceivingFileMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + ReceivingFileMapper.ensureInitialized(); @override $R call( {FileDto? file, @@ -140,5 +162,7 @@ class _ReceivingFileCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Receivi errorMessage: data.get(#errorMessage, or: $value.errorMessage)); @override - ReceivingFileCopyWith<$R2, ReceivingFile, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _ReceivingFileCopyWithImpl($value, $cast, t); + ReceivingFileCopyWith<$R2, ReceivingFile, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _ReceivingFileCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/server/server_state.mapper.dart b/app/lib/model/state/server/server_state.mapper.dart index 4d44dc78..ceb15f41 100644 --- a/app/lib/model/state/server/server_state.mapper.dart +++ b/app/lib/model/state/server/server_state.mapper.dart @@ -23,7 +23,8 @@ class ServerStateMapper extends ClassMapperBase { final String id = 'ServerState'; static SimpleServer _$httpServer(ServerState v) => v.httpServer; - static const Field _f$httpServer = Field('httpServer', _$httpServer); + static const Field _f$httpServer = + Field('httpServer', _$httpServer); static String _$alias(ServerState v) => v.alias; static const Field _f$alias = Field('alias', _$alias); static int _$port(ServerState v) => v.port; @@ -31,11 +32,14 @@ class ServerStateMapper extends ClassMapperBase { static bool _$https(ServerState v) => v.https; static const Field _f$https = Field('https', _$https); static ReceiveSessionState? _$session(ServerState v) => v.session; - static const Field _f$session = Field('session', _$session); + static const Field _f$session = + Field('session', _$session); static WebSendState? _$webSendState(ServerState v) => v.webSendState; - static const Field _f$webSendState = Field('webSendState', _$webSendState); + static const Field _f$webSendState = + Field('webSendState', _$webSendState); static Map _$pinAttempts(ServerState v) => v.pinAttempts; - static const Field> _f$pinAttempts = Field('pinAttempts', _$pinAttempts); + static const Field> _f$pinAttempts = + Field('pinAttempts', _$pinAttempts); @override final MappableFields fields = const { @@ -73,22 +77,27 @@ class ServerStateMapper extends ClassMapperBase { mixin ServerStateMappable { String serialize() { - return ServerStateMapper.ensureInitialized().encodeJson(this as ServerState); + return ServerStateMapper.ensureInitialized() + .encodeJson(this as ServerState); } Map toJson() { - return ServerStateMapper.ensureInitialized().encodeMap(this as ServerState); + return ServerStateMapper.ensureInitialized() + .encodeMap(this as ServerState); } - ServerStateCopyWith get copyWith => _ServerStateCopyWithImpl(this as ServerState, $identity, $identity); + ServerStateCopyWith get copyWith => + _ServerStateCopyWithImpl(this as ServerState, $identity, $identity); @override String toString() { - return ServerStateMapper.ensureInitialized().stringifyValue(this as ServerState); + return ServerStateMapper.ensureInitialized() + .stringifyValue(this as ServerState); } @override bool operator ==(Object other) { - return ServerStateMapper.ensureInitialized().equalsValue(this as ServerState, other); + return ServerStateMapper.ensureInitialized() + .equalsValue(this as ServerState, other); } @override @@ -97,12 +106,16 @@ mixin ServerStateMappable { } } -extension ServerStateValueCopy<$R, $Out> on ObjectCopyWith<$R, ServerState, $Out> { - ServerStateCopyWith<$R, ServerState, $Out> get $asServerState => $base.as((v, t, t2) => _ServerStateCopyWithImpl(v, t, t2)); +extension ServerStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, ServerState, $Out> { + ServerStateCopyWith<$R, ServerState, $Out> get $asServerState => + $base.as((v, t, t2) => _ServerStateCopyWithImpl(v, t, t2)); } -abstract class ServerStateCopyWith<$R, $In extends ServerState, $Out> implements ClassCopyWith<$R, $In, $Out> { - ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? get session; +abstract class ServerStateCopyWith<$R, $In extends ServerState, $Out> + implements ClassCopyWith<$R, $In, $Out> { + ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? + get session; WebSendStateCopyWith<$R, WebSendState, WebSendState>? get webSendState; MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get pinAttempts; $R call( @@ -116,18 +129,24 @@ abstract class ServerStateCopyWith<$R, $In extends ServerState, $Out> implements ServerStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _ServerStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ServerState, $Out> implements ServerStateCopyWith<$R, ServerState, $Out> { +class _ServerStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ServerState, $Out> + implements ServerStateCopyWith<$R, ServerState, $Out> { _ServerStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = ServerStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + ServerStateMapper.ensureInitialized(); @override - ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? get session => $value.session?.copyWith.$chain((v) => call(session: v)); + ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? + get session => $value.session?.copyWith.$chain((v) => call(session: v)); @override - WebSendStateCopyWith<$R, WebSendState, WebSendState>? get webSendState => $value.webSendState?.copyWith.$chain((v) => call(webSendState: v)); + WebSendStateCopyWith<$R, WebSendState, WebSendState>? get webSendState => + $value.webSendState?.copyWith.$chain((v) => call(webSendState: v)); @override MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get pinAttempts => - MapCopyWith($value.pinAttempts, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(pinAttempts: v)); + MapCopyWith($value.pinAttempts, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(pinAttempts: v)); @override $R call( {SimpleServer? httpServer, @@ -157,5 +176,7 @@ class _ServerStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ServerSta pinAttempts: data.get(#pinAttempts, or: $value.pinAttempts)); @override - ServerStateCopyWith<$R2, ServerState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _ServerStateCopyWithImpl($value, $cast, t); + ServerStateCopyWith<$R2, ServerState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _ServerStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/model/state/settings_state.mapper.dart b/app/lib/model/state/settings_state.mapper.dart index 7d31759b..540679a3 100644 --- a/app/lib/model/state/settings_state.mapper.dart +++ b/app/lib/model/state/settings_state.mapper.dart @@ -22,57 +22,84 @@ class SettingsStateMapper extends ClassMapperBase { final String id = 'SettingsState'; static String _$showToken(SettingsState v) => v.showToken; - static const Field _f$showToken = Field('showToken', _$showToken); + static const Field _f$showToken = + Field('showToken', _$showToken); static String _$alias(SettingsState v) => v.alias; static const Field _f$alias = Field('alias', _$alias); static ThemeMode _$theme(SettingsState v) => v.theme; - static const Field _f$theme = Field('theme', _$theme); + static const Field _f$theme = + Field('theme', _$theme); static ColorMode _$colorMode(SettingsState v) => v.colorMode; - static const Field _f$colorMode = Field('colorMode', _$colorMode); + static const Field _f$colorMode = + Field('colorMode', _$colorMode); static AppLocale? _$locale(SettingsState v) => v.locale; - static const Field _f$locale = Field('locale', _$locale); + static const Field _f$locale = + Field('locale', _$locale); static int _$port(SettingsState v) => v.port; static const Field _f$port = Field('port', _$port); - static List? _$networkWhitelist(SettingsState v) => v.networkWhitelist; - static const Field> _f$networkWhitelist = Field('networkWhitelist', _$networkWhitelist); - static List? _$networkBlacklist(SettingsState v) => v.networkBlacklist; - static const Field> _f$networkBlacklist = Field('networkBlacklist', _$networkBlacklist); + static List? _$networkWhitelist(SettingsState v) => + v.networkWhitelist; + static const Field> _f$networkWhitelist = + Field('networkWhitelist', _$networkWhitelist); + static List? _$networkBlacklist(SettingsState v) => + v.networkBlacklist; + static const Field> _f$networkBlacklist = + Field('networkBlacklist', _$networkBlacklist); static String _$multicastGroup(SettingsState v) => v.multicastGroup; - static const Field _f$multicastGroup = Field('multicastGroup', _$multicastGroup); + static const Field _f$multicastGroup = + Field('multicastGroup', _$multicastGroup); static String? _$destination(SettingsState v) => v.destination; - static const Field _f$destination = Field('destination', _$destination); + static const Field _f$destination = + Field('destination', _$destination); static bool _$saveToGallery(SettingsState v) => v.saveToGallery; - static const Field _f$saveToGallery = Field('saveToGallery', _$saveToGallery); + static const Field _f$saveToGallery = + Field('saveToGallery', _$saveToGallery); static bool _$saveToHistory(SettingsState v) => v.saveToHistory; - static const Field _f$saveToHistory = Field('saveToHistory', _$saveToHistory); + static const Field _f$saveToHistory = + Field('saveToHistory', _$saveToHistory); static bool _$quickSave(SettingsState v) => v.quickSave; - static const Field _f$quickSave = Field('quickSave', _$quickSave); - static bool _$quickSaveFromFavorites(SettingsState v) => v.quickSaveFromFavorites; - static const Field _f$quickSaveFromFavorites = Field('quickSaveFromFavorites', _$quickSaveFromFavorites); + static const Field _f$quickSave = + Field('quickSave', _$quickSave); + static bool _$quickSaveFromFavorites(SettingsState v) => + v.quickSaveFromFavorites; + static const Field _f$quickSaveFromFavorites = + Field('quickSaveFromFavorites', _$quickSaveFromFavorites); static String? _$receivePin(SettingsState v) => v.receivePin; - static const Field _f$receivePin = Field('receivePin', _$receivePin); + static const Field _f$receivePin = + Field('receivePin', _$receivePin); static bool _$autoFinish(SettingsState v) => v.autoFinish; - static const Field _f$autoFinish = Field('autoFinish', _$autoFinish); + static const Field _f$autoFinish = + Field('autoFinish', _$autoFinish); static bool _$minimizeToTray(SettingsState v) => v.minimizeToTray; - static const Field _f$minimizeToTray = Field('minimizeToTray', _$minimizeToTray); + static const Field _f$minimizeToTray = + Field('minimizeToTray', _$minimizeToTray); static bool _$https(SettingsState v) => v.https; static const Field _f$https = Field('https', _$https); static SendMode _$sendMode(SettingsState v) => v.sendMode; - static const Field _f$sendMode = Field('sendMode', _$sendMode); + static const Field _f$sendMode = + Field('sendMode', _$sendMode); static bool _$saveWindowPlacement(SettingsState v) => v.saveWindowPlacement; - static const Field _f$saveWindowPlacement = Field('saveWindowPlacement', _$saveWindowPlacement); + static const Field _f$saveWindowPlacement = + Field('saveWindowPlacement', _$saveWindowPlacement); static bool _$enableAnimations(SettingsState v) => v.enableAnimations; - static const Field _f$enableAnimations = Field('enableAnimations', _$enableAnimations); + static const Field _f$enableAnimations = + Field('enableAnimations', _$enableAnimations); static DeviceType? _$deviceType(SettingsState v) => v.deviceType; - static const Field _f$deviceType = Field('deviceType', _$deviceType); + static const Field _f$deviceType = + Field('deviceType', _$deviceType); static String? _$deviceModel(SettingsState v) => v.deviceModel; - static const Field _f$deviceModel = Field('deviceModel', _$deviceModel); - static bool _$shareViaLinkAutoAccept(SettingsState v) => v.shareViaLinkAutoAccept; - static const Field _f$shareViaLinkAutoAccept = Field('shareViaLinkAutoAccept', _$shareViaLinkAutoAccept); + static const Field _f$deviceModel = + Field('deviceModel', _$deviceModel); + static bool _$shareViaLinkAutoAccept(SettingsState v) => + v.shareViaLinkAutoAccept; + static const Field _f$shareViaLinkAutoAccept = + Field('shareViaLinkAutoAccept', _$shareViaLinkAutoAccept); static int _$discoveryTimeout(SettingsState v) => v.discoveryTimeout; - static const Field _f$discoveryTimeout = Field('discoveryTimeout', _$discoveryTimeout); + static const Field _f$discoveryTimeout = + Field('discoveryTimeout', _$discoveryTimeout); static bool _$advancedSettings(SettingsState v) => v.advancedSettings; - static const Field _f$advancedSettings = Field('advancedSettings', _$advancedSettings); + static const Field _f$advancedSettings = + Field('advancedSettings', _$advancedSettings); @override final MappableFields fields = const { @@ -148,38 +175,49 @@ class SettingsStateMapper extends ClassMapperBase { mixin SettingsStateMappable { String serialize() { - return SettingsStateMapper.ensureInitialized().encodeJson(this as SettingsState); + return SettingsStateMapper.ensureInitialized() + .encodeJson(this as SettingsState); } Map toJson() { - return SettingsStateMapper.ensureInitialized().encodeMap(this as SettingsState); + return SettingsStateMapper.ensureInitialized() + .encodeMap(this as SettingsState); } - SettingsStateCopyWith get copyWith => - _SettingsStateCopyWithImpl(this as SettingsState, $identity, $identity); + SettingsStateCopyWith + get copyWith => _SettingsStateCopyWithImpl( + this as SettingsState, $identity, $identity); @override String toString() { - return SettingsStateMapper.ensureInitialized().stringifyValue(this as SettingsState); + return SettingsStateMapper.ensureInitialized() + .stringifyValue(this as SettingsState); } @override bool operator ==(Object other) { - return SettingsStateMapper.ensureInitialized().equalsValue(this as SettingsState, other); + return SettingsStateMapper.ensureInitialized() + .equalsValue(this as SettingsState, other); } @override int get hashCode { - return SettingsStateMapper.ensureInitialized().hashValue(this as SettingsState); + return SettingsStateMapper.ensureInitialized() + .hashValue(this as SettingsState); } } -extension SettingsStateValueCopy<$R, $Out> on ObjectCopyWith<$R, SettingsState, $Out> { - SettingsStateCopyWith<$R, SettingsState, $Out> get $asSettingsState => $base.as((v, t, t2) => _SettingsStateCopyWithImpl(v, t, t2)); +extension SettingsStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, SettingsState, $Out> { + SettingsStateCopyWith<$R, SettingsState, $Out> get $asSettingsState => + $base.as((v, t, t2) => _SettingsStateCopyWithImpl(v, t, t2)); } -abstract class SettingsStateCopyWith<$R, $In extends SettingsState, $Out> implements ClassCopyWith<$R, $In, $Out> { - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get networkWhitelist; - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get networkBlacklist; +abstract class SettingsStateCopyWith<$R, $In extends SettingsState, $Out> + implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? + get networkWhitelist; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? + get networkBlacklist; $R call( {String? showToken, String? alias, @@ -210,20 +248,30 @@ abstract class SettingsStateCopyWith<$R, $In extends SettingsState, $Out> implem SettingsStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SettingsState, $Out> +class _SettingsStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SettingsState, $Out> implements SettingsStateCopyWith<$R, SettingsState, $Out> { _SettingsStateCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = SettingsStateMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + SettingsStateMapper.ensureInitialized(); @override - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get networkWhitelist => $value.networkWhitelist != null - ? ListCopyWith($value.networkWhitelist!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(networkWhitelist: v)) - : null; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? + get networkWhitelist => $value.networkWhitelist != null + ? ListCopyWith( + $value.networkWhitelist!, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(networkWhitelist: v)) + : null; @override - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? get networkBlacklist => $value.networkBlacklist != null - ? ListCopyWith($value.networkBlacklist!, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(networkBlacklist: v)) - : null; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>>? + get networkBlacklist => $value.networkBlacklist != null + ? ListCopyWith( + $value.networkBlacklist!, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(networkBlacklist: v)) + : null; @override $R call( {String? showToken, @@ -266,17 +314,20 @@ class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting if (saveToGallery != null) #saveToGallery: saveToGallery, if (saveToHistory != null) #saveToHistory: saveToHistory, if (quickSave != null) #quickSave: quickSave, - if (quickSaveFromFavorites != null) #quickSaveFromFavorites: quickSaveFromFavorites, + if (quickSaveFromFavorites != null) + #quickSaveFromFavorites: quickSaveFromFavorites, if (receivePin != $none) #receivePin: receivePin, if (autoFinish != null) #autoFinish: autoFinish, if (minimizeToTray != null) #minimizeToTray: minimizeToTray, if (https != null) #https: https, if (sendMode != null) #sendMode: sendMode, - if (saveWindowPlacement != null) #saveWindowPlacement: saveWindowPlacement, + if (saveWindowPlacement != null) + #saveWindowPlacement: saveWindowPlacement, if (enableAnimations != null) #enableAnimations: enableAnimations, if (deviceType != $none) #deviceType: deviceType, if (deviceModel != $none) #deviceModel: deviceModel, - if (shareViaLinkAutoAccept != null) #shareViaLinkAutoAccept: shareViaLinkAutoAccept, + if (shareViaLinkAutoAccept != null) + #shareViaLinkAutoAccept: shareViaLinkAutoAccept, if (discoveryTimeout != null) #discoveryTimeout: discoveryTimeout, if (advancedSettings != null) #advancedSettings: advancedSettings })); @@ -288,27 +339,37 @@ class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting colorMode: data.get(#colorMode, or: $value.colorMode), locale: data.get(#locale, or: $value.locale), port: data.get(#port, or: $value.port), - networkWhitelist: data.get(#networkWhitelist, or: $value.networkWhitelist), - networkBlacklist: data.get(#networkBlacklist, or: $value.networkBlacklist), + networkWhitelist: + data.get(#networkWhitelist, or: $value.networkWhitelist), + networkBlacklist: + data.get(#networkBlacklist, or: $value.networkBlacklist), multicastGroup: data.get(#multicastGroup, or: $value.multicastGroup), destination: data.get(#destination, or: $value.destination), saveToGallery: data.get(#saveToGallery, or: $value.saveToGallery), saveToHistory: data.get(#saveToHistory, or: $value.saveToHistory), quickSave: data.get(#quickSave, or: $value.quickSave), - quickSaveFromFavorites: data.get(#quickSaveFromFavorites, or: $value.quickSaveFromFavorites), + quickSaveFromFavorites: + data.get(#quickSaveFromFavorites, or: $value.quickSaveFromFavorites), receivePin: data.get(#receivePin, or: $value.receivePin), autoFinish: data.get(#autoFinish, or: $value.autoFinish), minimizeToTray: data.get(#minimizeToTray, or: $value.minimizeToTray), https: data.get(#https, or: $value.https), sendMode: data.get(#sendMode, or: $value.sendMode), - saveWindowPlacement: data.get(#saveWindowPlacement, or: $value.saveWindowPlacement), - enableAnimations: data.get(#enableAnimations, or: $value.enableAnimations), + saveWindowPlacement: + data.get(#saveWindowPlacement, or: $value.saveWindowPlacement), + enableAnimations: + data.get(#enableAnimations, or: $value.enableAnimations), deviceType: data.get(#deviceType, or: $value.deviceType), deviceModel: data.get(#deviceModel, or: $value.deviceModel), - shareViaLinkAutoAccept: data.get(#shareViaLinkAutoAccept, or: $value.shareViaLinkAutoAccept), - discoveryTimeout: data.get(#discoveryTimeout, or: $value.discoveryTimeout), - advancedSettings: data.get(#advancedSettings, or: $value.advancedSettings)); + shareViaLinkAutoAccept: + data.get(#shareViaLinkAutoAccept, or: $value.shareViaLinkAutoAccept), + discoveryTimeout: + data.get(#discoveryTimeout, or: $value.discoveryTimeout), + advancedSettings: + data.get(#advancedSettings, or: $value.advancedSettings)); @override - SettingsStateCopyWith<$R2, SettingsState, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _SettingsStateCopyWithImpl($value, $cast, t); + SettingsStateCopyWith<$R2, SettingsState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SettingsStateCopyWithImpl($value, $cast, t); } diff --git a/app/lib/pages/donation/donation_page.dart b/app/lib/pages/donation/donation_page.dart index 78da0dbb..82c59e76 100644 --- a/app/lib/pages/donation/donation_page.dart +++ b/app/lib/pages/donation/donation_page.dart @@ -16,7 +16,7 @@ class DonationPage extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder( - provider: donationPageVmProvider, + provider: (ref) => donationPageVmProvider, // [FOSS_REMOVE_START] init: (context) => context.redux(purchaseProvider).dispatchAsync(FetchPricesAndPurchasesAction()), // ignore: discarded_futures // [FOSS_REMOVE_END] diff --git a/app/lib/pages/progress_page.dart b/app/lib/pages/progress_page.dart index 5facf702..ec3620a7 100644 --- a/app/lib/pages/progress_page.dart +++ b/app/lib/pages/progress_page.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:localsend_app/config/theme.dart'; import 'package:localsend_app/gen/strings.g.dart'; +import 'package:localsend_app/model/state/server/receive_session_state.dart'; import 'package:localsend_app/provider/network/send_provider.dart'; import 'package:localsend_app/provider/network/server/server_provider.dart'; import 'package:localsend_app/provider/progress_provider.dart'; @@ -184,7 +185,15 @@ class _ProgressPageState extends State with Refena { final receiveSession = ref.watch(serverProvider.select((s) => s?.session)); final sendSession = ref.watch(sendProvider)[widget.sessionId]; - final SessionStatus? status = receiveSession?.status ?? sendSession?.status; + final SessionState? commonSessionState = receiveSession ?? sendSession; + + if (commonSessionState == null) { + return Scaffold( + body: Container(), + ); + } + + final status = commonSessionState.status; if (status == SessionStatus.sending) { // ignore: discarded_futures @@ -195,15 +204,9 @@ class _ProgressPageState extends State with Refena { TaskbarHelper.visualizeStatus(status); } - if (status == null) { - return Scaffold( - body: Container(), - ); - } - final title = receiveSession != null ? t.progressPage.titleReceiving : t.progressPage.titleSending; - final startTime = receiveSession?.startTime ?? sendSession?.startTime; - final endTime = receiveSession?.endTime ?? sendSession?.endTime; + final startTime = commonSessionState.startTime; + final endTime = commonSessionState.endTime; final int? speedInBytes; if (startTime != null && currBytes >= 500 * 1024) { speedInBytes = getFileSpeed(start: startTime, end: endTime ?? DateTime.now().millisecondsSinceEpoch, bytes: currBytes); diff --git a/app/lib/pages/receive_history_page.dart b/app/lib/pages/receive_history_page.dart index 9c571135..de90ff68 100644 --- a/app/lib/pages/receive_history_page.dart +++ b/app/lib/pages/receive_history_page.dart @@ -1,11 +1,12 @@ import 'dart:io'; +import 'package:common/model/device.dart'; +import 'package:common/model/session_status.dart'; import 'package:flutter/material.dart'; import 'package:localsend_app/config/theme.dart'; import 'package:localsend_app/gen/strings.g.dart'; import 'package:localsend_app/model/persistence/receive_history_entry.dart'; import 'package:localsend_app/pages/receive_page.dart'; -import 'package:localsend_app/pages/receive_page_controller.dart'; import 'package:localsend_app/provider/receive_history_provider.dart'; import 'package:localsend_app/provider/settings_provider.dart'; import 'package:localsend_app/util/file_size_helper.dart'; @@ -129,9 +130,33 @@ class ReceiveHistoryPage extends StatelessWidget { onTap: entry.path != null || entry.isMessage ? () async { if (entry.isMessage) { - context.redux(receivePageControllerProvider).dispatch(InitReceivePageFromHistoryMessageAction(entry: entry)); + final vm = ViewProvider((ref) { + return ReceivePageVm( + status: SessionStatus.waiting, + sender: Device( + signalingId: null, + ip: '0.0.0.0', + version: '1.0.0', + port: 8080, + https: false, + fingerprint: 'fingerprint', + alias: entry.senderAlias, + deviceModel: 'deviceModel', + deviceType: DeviceType.web, + download: true, + discoveryMethods: const {}, + ), + showSenderInfo: false, + files: [], + message: entry.fileName, + onAccept: () {}, + onDecline: () {}, + onClose: () {}, + ); + }); + // ignore: unawaited_futures - context.push(() => const ReceivePage()); + context.push(() => ReceivePage(vm)); return; } diff --git a/app/lib/pages/receive_options_page.dart b/app/lib/pages/receive_options_page.dart index f38cd638..35ffdee5 100644 --- a/app/lib/pages/receive_options_page.dart +++ b/app/lib/pages/receive_options_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:localsend_app/gen/strings.g.dart'; +import 'package:localsend_app/pages/receive_page.dart'; import 'package:localsend_app/provider/network/server/server_provider.dart'; import 'package:localsend_app/provider/selection/selected_receiving_files_provider.dart'; import 'package:localsend_app/util/file_size_helper.dart'; @@ -14,7 +15,9 @@ import 'package:localsend_app/widget/responsive_list_view.dart'; import 'package:refena_flutter/refena_flutter.dart'; class ReceiveOptionsPage extends StatelessWidget { - const ReceiveOptionsPage({super.key}); + final ReceivePageVm vm; + + const ReceiveOptionsPage(this.vm); @override Widget build(BuildContext context) { @@ -109,42 +112,38 @@ class ReceiveOptionsPage extends StatelessWidget { Tooltip( message: t.general.reset, child: CustomIconButton( - onPressed: () async { - ref.notifier(selectedReceivingFilesProvider).setFiles(receiveSession.files.values.map((f) => f.file).toList()); - }, + onPressed: () => ref.notifier(selectedReceivingFilesProvider).setFiles(vm.files), child: const Icon(Icons.undo), ), ), ], ), const SizedBox(height: 5), - ...receiveSession.files.values.map((file) { + ...vm.files.map((file) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(file.file.fileType.icon, size: 46), + Icon(file.fileType.icon, size: 46), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - selectState[file.file.id] ?? file.file.fileName, + selectState[file.id] ?? file.fileName, style: const TextStyle(fontSize: 16), maxLines: 1, overflow: TextOverflow.fade, softWrap: false, ), Text( - '${!selectState.containsKey(file.file.id) ? t.general.skipped : (selectState[file.file.id] == file.file.fileName ? t.general.unchanged : t.general.renamed)} - ${file.file.size.asReadableFileSize}', + '${!selectState.containsKey(file.id) ? t.general.skipped : (selectState[file.id] == file.fileName ? t.general.unchanged : t.general.renamed)} - ${file.size.asReadableFileSize}', style: TextStyle( - color: !selectState.containsKey(file.file.id) + color: !selectState.containsKey(file.id) ? Colors.grey - : (selectState[file.file.id] == file.file.fileName - ? Theme.of(context).colorScheme.onSecondaryContainer - : Colors.orange)), + : (selectState[file.id] == file.fileName ? Theme.of(context).colorScheme.onSecondaryContainer : Colors.orange)), ) ], ), @@ -153,31 +152,31 @@ class ReceiveOptionsPage extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ CustomIconButton( - onPressed: selectState[file.file.id] == null + onPressed: selectState[file.id] == null ? null : () async { final result = await showDialog( context: context, builder: (_) => FileNameInputDialog( - originalName: file.file.fileName, - initialName: selectState[file.file.id]!, + originalName: file.fileName, + initialName: selectState[file.id]!, ), ); if (result != null) { - ref.notifier(selectedReceivingFilesProvider).rename(file.file.id, result); + ref.notifier(selectedReceivingFilesProvider).rename(file.id, result); } }, child: const Icon(Icons.edit), ), Checkbox( - value: selectState.containsKey(file.file.id), + value: selectState.containsKey(file.id), activeColor: Theme.of(context).colorScheme.onSurface, checkColor: Theme.of(context).colorScheme.surface, onChanged: (selected) { if (selected == true) { - ref.notifier(selectedReceivingFilesProvider).select(file.file); + ref.notifier(selectedReceivingFilesProvider).select(file); } else { - ref.notifier(selectedReceivingFilesProvider).unselect(file.file.id); + ref.notifier(selectedReceivingFilesProvider).unselect(file.id); } }, ), diff --git a/app/lib/pages/receive_page.dart b/app/lib/pages/receive_page.dart index d41a392a..264cf37b 100644 --- a/app/lib/pages/receive_page.dart +++ b/app/lib/pages/receive_page.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:common/model/device.dart'; +import 'package:common/model/dto/file_dto.dart'; import 'package:common/model/session_status.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,7 +9,6 @@ import 'package:localsend_app/config/theme.dart'; import 'package:localsend_app/gen/strings.g.dart'; import 'package:localsend_app/model/persistence/color_mode.dart'; import 'package:localsend_app/pages/receive_options_page.dart'; -import 'package:localsend_app/pages/receive_page_controller.dart'; import 'package:localsend_app/provider/favorites_provider.dart'; import 'package:localsend_app/provider/selection/selected_receiving_files_provider.dart'; import 'package:localsend_app/provider/settings_provider.dart'; @@ -23,23 +24,46 @@ import 'package:refena_flutter/refena_flutter.dart'; import 'package:routerino/routerino.dart'; import 'package:url_launcher/url_launcher.dart'; +class ReceivePageVm { + final SessionStatus? status; + final Device sender; + + /// Show hashtag and device model. + final bool showSenderInfo; + final List files; + final String? message; + final bool isLink; + final void Function() onAccept; + final void Function() onDecline; + final void Function() onClose; + + ReceivePageVm({ + required this.status, + required this.sender, + required this.showSenderInfo, + required this.files, + required this.message, + required this.onAccept, + required this.onDecline, + required this.onClose, + }) : isLink = message != null && (Uri.tryParse(message)?.isAbsolute ?? false); +} + class ReceivePage extends StatefulWidget { - const ReceivePage({super.key}); + final ViewProvider vm; + + const ReceivePage(this.vm); @override State createState() => _ReceivePageState(); } class _ReceivePageState extends State with Refena { - @override - void dispose() { - super.dispose(); - unawaited(TaskbarHelper.clearProgressBar()); - } + bool _showFullIp = false; @override Widget build(BuildContext context) { - final vm = context.watch(receivePageControllerProvider, listener: (prev, next) { + final vm = context.watch(widget.vm, listener: (prev, next) { if (prev.status != next.status) { // ignore: discarded_futures TaskbarHelper.visualizeStatus(next.status); @@ -54,148 +78,170 @@ class _ReceivePageState extends State with Refena { final senderFavoriteEntry = ref.watch(favoritesProvider.select((state) => state.findDevice(vm.sender))); - return PopScope( - onPopInvokedWithResult: (didPop, result) { - if (didPop) { - vm.onDecline(); - } - }, - canPop: true, - child: Scaffold( - body: SafeArea( - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: ResponsiveListView.defaultMaxWidth), - child: Builder( - builder: (context) { - final height = MediaQuery.of(context).size.height; - final smallUi = vm.message != null && height < 600; - return Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: smallUi ? 20 : 30), - child: Column( - children: [ - Expanded( + return ViewModelBuilder( + provider: (ref) => widget.vm, + onFirstFrame: (context, vm) { + ref.notifier(selectedReceivingFilesProvider).setFiles(vm.files); + }, + dispose: (ref) { + ref.dispose(widget.vm); + unawaited(TaskbarHelper.clearProgressBar()); + }, + builder: (context, vm) { + return PopScope( + onPopInvokedWithResult: (didPop, result) { + if (didPop) { + vm.onDecline(); + } + }, + canPop: true, + child: Scaffold( + body: SafeArea( + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: ResponsiveListView.defaultMaxWidth), + child: Builder( + builder: (context) { + final height = MediaQuery.of(context).size.height; + final smallUi = vm.message != null && height < 600; + return Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: smallUi ? 20 : 30), child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ - if (vm.showSenderInfo && !smallUi) - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Icon(vm.sender.deviceType.icon, size: 64), - ), - FittedBox( - child: Text( - senderFavoriteEntry?.alias ?? vm.sender.alias, - style: TextStyle(fontSize: smallUi ? 32 : 48), - textAlign: TextAlign.center, - ), - ), - if (vm.showSenderInfo) ...[ - const SizedBox(height: 10), - Row( + Expanded( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - InkWell( - onTap: () { - context.redux(receivePageControllerProvider).dispatch(SetShowFullIpAction(!vm.showFullIp)); - }, - child: DeviceBadge( - backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer, - foregroundColor: Theme.of(context).colorScheme.onInverseSurface, - label: vm.showFullIp ? vm.sender.ip : '#${vm.sender.ip.visualId}', + if (vm.showSenderInfo && !smallUi) + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Icon(vm.sender.deviceType.icon, size: 64), ), - ), - if (vm.sender.deviceModel != null) ...[ - const SizedBox(width: 10), - DeviceBadge( - backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer, - foregroundColor: Theme.of(context).colorScheme.onInverseSurface, - label: vm.sender.deviceModel!, + Builder(builder: (context) { + final alias = senderFavoriteEntry?.alias ?? vm.sender.alias; + if (alias.isEmpty) { + return Text('', style: TextStyle(fontSize: smallUi ? 32 : 48)); + } + return FittedBox( + child: Text( + alias, + style: TextStyle(fontSize: smallUi ? 32 : 48), + textAlign: TextAlign.center, + ), + ); + }), + if (vm.showSenderInfo) ...[ + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + setState(() { + _showFullIp = !_showFullIp; + }); + }, + child: DeviceBadge( + backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer, + foregroundColor: Theme.of(context).colorScheme.onInverseSurface, + label: switch (vm.sender.ip) { + String ip => _showFullIp ? ip : '#${ip.visualId}', + null => 'WebRTC', + }, + ), + ), + if (vm.sender.deviceModel != null) ...[ + const SizedBox(width: 10), + DeviceBadge( + backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer, + foregroundColor: Theme.of(context).colorScheme.onInverseSurface, + label: vm.sender.deviceModel!, + ), + ], + ], ), ], - ], - ), - ], - const SizedBox(height: 40), - Text( - vm.message != null - ? (vm.isLink ? t.receivePage.subTitleLink : t.receivePage.subTitleMessage) - : t.receivePage.subTitle(n: vm.fileCount), - style: smallUi ? null : Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - if (vm.message != null) - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.only(top: 20), - child: SizedBox( - height: 100, - child: Card( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(10), - child: SelectableText( - vm.message!, - ), - ), - ), - ), - ), + const SizedBox(height: 40), + Text( + vm.message != null + ? (vm.isLink ? t.receivePage.subTitleLink : t.receivePage.subTitleMessage) + : t.receivePage.subTitle(n: vm.files.length), + style: smallUi ? null : Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - unawaited( - Clipboard.setData(ClipboardData(text: vm.message!)), - ); - if (checkPlatformIsDesktop()) { - context.showSnackBar(t.general.copiedToClipboard); - } - vm.onAccept(); - context.pop(); - }, - child: Text(t.general.copy), - ), - if (vm.isLink) + if (vm.message != null) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ Padding( - padding: const EdgeInsetsDirectional.only(start: 20), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: Theme.of(context).colorScheme.onPrimary, + padding: const EdgeInsets.only(top: 20), + child: SizedBox( + height: 100, + child: Card( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(10), + child: SelectableText( + vm.message!, + ), + ), + ), ), - onPressed: () { - // ignore: discarded_futures - launchUrl(Uri.parse(vm.message!), mode: LaunchMode.externalApplication); - vm.onAccept(); - context.pop(); - }, - child: Text(t.general.open), ), ), - ], - ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + unawaited( + Clipboard.setData(ClipboardData(text: vm.message!)), + ); + if (checkPlatformIsDesktop()) { + context.showSnackBar(t.general.copiedToClipboard); + } + vm.onAccept(); + context.pop(); + }, + child: Text(t.general.copy), + ), + if (vm.isLink) + Padding( + padding: const EdgeInsetsDirectional.only(start: 20), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () { + // ignore: discarded_futures + launchUrl(Uri.parse(vm.message!), mode: LaunchMode.externalApplication); + vm.onAccept(); + context.pop(); + }, + child: Text(t.general.open), + ), + ), + ], + ), + ], + ), ], ), + ), + _Actions(vm), ], ), - ), - _Actions(vm), - ], + ); + }, ), - ); - }, + ), + ), ), ), - ), - ), - ), - ); + ); + }); } } @@ -259,7 +305,7 @@ class _Actions extends StatelessWidget { foregroundColor: Theme.of(context).colorScheme.onSurface, ), onPressed: () async { - await context.push(() => const ReceiveOptionsPage()); + await context.push(() => ReceiveOptionsPage(vm)); }, icon: const Icon(Icons.settings), label: Text(t.receiveOptionsPage.title), diff --git a/app/lib/pages/receive_page_controller.dart b/app/lib/pages/receive_page_controller.dart deleted file mode 100644 index 7c9c2ee5..00000000 --- a/app/lib/pages/receive_page_controller.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'package:common/model/device.dart'; -import 'package:common/model/session_status.dart'; -import 'package:dart_mappable/dart_mappable.dart'; -import 'package:localsend_app/model/persistence/receive_history_entry.dart'; -import 'package:localsend_app/pages/progress_page.dart'; -import 'package:localsend_app/pages/receive_page.dart'; -import 'package:localsend_app/provider/network/server/server_provider.dart'; -import 'package:localsend_app/provider/selection/selected_receiving_files_provider.dart'; -import 'package:refena_flutter/refena_flutter.dart'; -import 'package:routerino/routerino.dart'; - -part 'receive_page_controller.mapper.dart'; - -@MappableClass() -class ReceivePageVm with ReceivePageVmMappable { - final SessionStatus? status; - final Device sender; - - /// Show hashtag and device model. - final bool showSenderInfo; - final int fileCount; - final String? message; - final bool isLink; - final bool showFullIp; - final void Function() onAccept; - final void Function() onDecline; - final void Function() onClose; - - ReceivePageVm({ - required this.status, - required this.sender, - required this.showSenderInfo, - required this.fileCount, - required this.message, - required this.isLink, - required this.showFullIp, - required this.onAccept, - required this.onDecline, - required this.onClose, - }); -} - -final receivePageControllerProvider = ReduxProvider((ref) { - return ReceivePageController( - server: ref.notifier(serverProvider), - selectedReceivingFiles: ref.notifier(selectedReceivingFilesProvider), - ); -}); - -class ReceivePageController extends ReduxNotifier { - final ServerService _server; - final SelectedReceivingFilesNotifier _selectedReceivingFiles; - - ReceivePageController({ - required ServerService server, - required SelectedReceivingFilesNotifier selectedReceivingFiles, - }) : _server = server, - _selectedReceivingFiles = selectedReceivingFiles; - - @override - ReceivePageVm init() { - return ReceivePageVm( - status: SessionStatus.waiting, - sender: const Device( - ip: '0.0.0.0', - version: '1.0.0', - port: 8080, - https: false, - fingerprint: 'fingerprint', - alias: 'alias', - deviceModel: 'deviceModel', - deviceType: DeviceType.desktop, - download: true, - ), - showSenderInfo: true, - fileCount: 1, - message: 'message', - isLink: false, - showFullIp: false, - onAccept: () {}, - onDecline: () {}, - onClose: () {}, - ); - } - - @override - get initialAction => _WatchStatusAction(); -} - -class _WatchStatusAction extends WatchAction { - @override - ReceivePageVm reduce() { - return state.copyWith( - status: ref.watch(serverProvider.select((state) => state?.session?.status)), - ); - } -} - -class InitReceivePageAction extends ReduxAction { - @override - ReceivePageVm reduce() { - final receiveSession = notifier._server.state?.session; - if (receiveSession == null) { - return state; - } - - return state.copyWith( - sender: receiveSession.sender, - showSenderInfo: true, - fileCount: receiveSession.files.length, - message: receiveSession.message, - isLink: receiveSession.message != null && (receiveSession.message!.isLink), - showFullIp: false, - onAccept: () async { - if (state.message != null) { - // accept nothing - notifier._server.acceptFileRequest({}); - return; - } - - final sessionId = notifier._server.state?.session?.sessionId; - if (sessionId == null) { - return; - } - - final selectedFiles = notifier._selectedReceivingFiles.state; - notifier._server.acceptFileRequest(selectedFiles); - - await Routerino.context.pushAndRemoveUntilImmediately( - removeUntil: ReceivePage, - builder: () => ProgressPage( - showAppBar: false, - closeSessionOnClose: true, - sessionId: sessionId, - ), - ); - }, - onDecline: () { - notifier._server.declineFileRequest(); - }, - onClose: () { - notifier._server.closeSession(); - }, - ); - } -} - -class InitReceivePageFromHistoryMessageAction extends ReduxAction { - final ReceiveHistoryEntry entry; - - InitReceivePageFromHistoryMessageAction({required this.entry}); - - @override - ReceivePageVm reduce() { - return state.copyWith( - sender: Device( - ip: '0.0.0.0', - version: '1.0.0', - port: 8080, - https: false, - fingerprint: 'fingerprint', - alias: entry.senderAlias, - deviceModel: 'deviceModel', - deviceType: DeviceType.web, - download: true, - ), - showSenderInfo: false, - fileCount: 1, - message: entry.fileName, - isLink: entry.fileName.isLink, - showFullIp: false, - onAccept: () {}, - onDecline: () {}, - onClose: () {}, - ); - } -} - -class SetShowFullIpAction extends ReduxAction { - final bool showFullIp; - - SetShowFullIpAction(this.showFullIp); - - @override - ReceivePageVm reduce() { - return state.copyWith( - showFullIp: showFullIp, - ); - } -} - -extension on String { - bool get isLink => Uri.tryParse(this)?.isAbsolute ?? false; -} diff --git a/app/lib/pages/receive_page_controller.mapper.dart b/app/lib/pages/receive_page_controller.mapper.dart deleted file mode 100644 index 73f0f21a..00000000 --- a/app/lib/pages/receive_page_controller.mapper.dart +++ /dev/null @@ -1,182 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'receive_page_controller.dart'; - -class ReceivePageVmMapper extends ClassMapperBase { - ReceivePageVmMapper._(); - - static ReceivePageVmMapper? _instance; - static ReceivePageVmMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ReceivePageVmMapper._()); - DeviceMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'ReceivePageVm'; - - static SessionStatus? _$status(ReceivePageVm v) => v.status; - static const Field _f$status = Field('status', _$status); - static Device _$sender(ReceivePageVm v) => v.sender; - static const Field _f$sender = Field('sender', _$sender); - static bool _$showSenderInfo(ReceivePageVm v) => v.showSenderInfo; - static const Field _f$showSenderInfo = Field('showSenderInfo', _$showSenderInfo); - static int _$fileCount(ReceivePageVm v) => v.fileCount; - static const Field _f$fileCount = Field('fileCount', _$fileCount); - static String? _$message(ReceivePageVm v) => v.message; - static const Field _f$message = Field('message', _$message); - static bool _$isLink(ReceivePageVm v) => v.isLink; - static const Field _f$isLink = Field('isLink', _$isLink); - static bool _$showFullIp(ReceivePageVm v) => v.showFullIp; - static const Field _f$showFullIp = Field('showFullIp', _$showFullIp); - static Function _$onAccept(ReceivePageVm v) => (v as dynamic).onAccept as Function; - static dynamic _arg$onAccept(f) => f(); - static const Field _f$onAccept = Field('onAccept', _$onAccept, arg: _arg$onAccept); - static Function _$onDecline(ReceivePageVm v) => (v as dynamic).onDecline as Function; - static dynamic _arg$onDecline(f) => f(); - static const Field _f$onDecline = Field('onDecline', _$onDecline, arg: _arg$onDecline); - static Function _$onClose(ReceivePageVm v) => (v as dynamic).onClose as Function; - static dynamic _arg$onClose(f) => f(); - static const Field _f$onClose = Field('onClose', _$onClose, arg: _arg$onClose); - - @override - final MappableFields fields = const { - #status: _f$status, - #sender: _f$sender, - #showSenderInfo: _f$showSenderInfo, - #fileCount: _f$fileCount, - #message: _f$message, - #isLink: _f$isLink, - #showFullIp: _f$showFullIp, - #onAccept: _f$onAccept, - #onDecline: _f$onDecline, - #onClose: _f$onClose, - }; - - static ReceivePageVm _instantiate(DecodingData data) { - return ReceivePageVm( - status: data.dec(_f$status), - sender: data.dec(_f$sender), - showSenderInfo: data.dec(_f$showSenderInfo), - fileCount: data.dec(_f$fileCount), - message: data.dec(_f$message), - isLink: data.dec(_f$isLink), - showFullIp: data.dec(_f$showFullIp), - onAccept: data.dec(_f$onAccept), - onDecline: data.dec(_f$onDecline), - onClose: data.dec(_f$onClose)); - } - - @override - final Function instantiate = _instantiate; - - static ReceivePageVm fromJson(Map map) { - return ensureInitialized().decodeMap(map); - } - - static ReceivePageVm deserialize(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin ReceivePageVmMappable { - String serialize() { - return ReceivePageVmMapper.ensureInitialized().encodeJson(this as ReceivePageVm); - } - - Map toJson() { - return ReceivePageVmMapper.ensureInitialized().encodeMap(this as ReceivePageVm); - } - - ReceivePageVmCopyWith get copyWith => - _ReceivePageVmCopyWithImpl(this as ReceivePageVm, $identity, $identity); - @override - String toString() { - return ReceivePageVmMapper.ensureInitialized().stringifyValue(this as ReceivePageVm); - } - - @override - bool operator ==(Object other) { - return ReceivePageVmMapper.ensureInitialized().equalsValue(this as ReceivePageVm, other); - } - - @override - int get hashCode { - return ReceivePageVmMapper.ensureInitialized().hashValue(this as ReceivePageVm); - } -} - -extension ReceivePageVmValueCopy<$R, $Out> on ObjectCopyWith<$R, ReceivePageVm, $Out> { - ReceivePageVmCopyWith<$R, ReceivePageVm, $Out> get $asReceivePageVm => $base.as((v, t, t2) => _ReceivePageVmCopyWithImpl(v, t, t2)); -} - -abstract class ReceivePageVmCopyWith<$R, $In extends ReceivePageVm, $Out> implements ClassCopyWith<$R, $In, $Out> { - DeviceCopyWith<$R, Device, Device> get sender; - $R call( - {SessionStatus? status, - Device? sender, - bool? showSenderInfo, - int? fileCount, - String? message, - bool? isLink, - bool? showFullIp, - void Function()? onAccept, - void Function()? onDecline, - void Function()? onClose}); - ReceivePageVmCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _ReceivePageVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ReceivePageVm, $Out> - implements ReceivePageVmCopyWith<$R, ReceivePageVm, $Out> { - _ReceivePageVmCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = ReceivePageVmMapper.ensureInitialized(); - @override - DeviceCopyWith<$R, Device, Device> get sender => $value.sender.copyWith.$chain((v) => call(sender: v)); - @override - $R call( - {Object? status = $none, - Device? sender, - bool? showSenderInfo, - int? fileCount, - Object? message = $none, - bool? isLink, - bool? showFullIp, - void Function()? onAccept, - void Function()? onDecline, - void Function()? onClose}) => - $apply(FieldCopyWithData({ - if (status != $none) #status: status, - if (sender != null) #sender: sender, - if (showSenderInfo != null) #showSenderInfo: showSenderInfo, - if (fileCount != null) #fileCount: fileCount, - if (message != $none) #message: message, - if (isLink != null) #isLink: isLink, - if (showFullIp != null) #showFullIp: showFullIp, - if (onAccept != null) #onAccept: onAccept, - if (onDecline != null) #onDecline: onDecline, - if (onClose != null) #onClose: onClose - })); - @override - ReceivePageVm $make(CopyWithData data) => ReceivePageVm( - status: data.get(#status, or: $value.status), - sender: data.get(#sender, or: $value.sender), - showSenderInfo: data.get(#showSenderInfo, or: $value.showSenderInfo), - fileCount: data.get(#fileCount, or: $value.fileCount), - message: data.get(#message, or: $value.message), - isLink: data.get(#isLink, or: $value.isLink), - showFullIp: data.get(#showFullIp, or: $value.showFullIp), - onAccept: data.get(#onAccept, or: $value.onAccept), - onDecline: data.get(#onDecline, or: $value.onDecline), - onClose: data.get(#onClose, or: $value.onClose)); - - @override - ReceivePageVmCopyWith<$R2, ReceivePageVm, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _ReceivePageVmCopyWithImpl($value, $cast, t); -} diff --git a/app/lib/pages/tabs/send_tab.dart b/app/lib/pages/tabs/send_tab.dart index fab7d5ab..12ad45de 100644 --- a/app/lib/pages/tabs/send_tab.dart +++ b/app/lib/pages/tabs/send_tab.dart @@ -44,7 +44,7 @@ class SendTab extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder( - provider: sendTabVmProvider, + provider: (ref) => sendTabVmProvider, init: (context) async => context.global.dispatchAsync(SendTabInitAction(context)), // ignore: discarded_futures builder: (context, vm) { final sizingInformation = SizingInformation(MediaQuery.sizeOf(context).width); @@ -546,7 +546,7 @@ class _MultiSendDeviceListTile extends StatelessWidget { progress: progress, isFavorite: isFavorite, nameOverride: nameOverride, - onFavoriteTap: () async => await vm.onToggleFavorite(context, device), + onFavoriteTap: device.ip == null ? null : () async => await vm.onToggleFavorite(context, device), onTap: () async => await vm.onTapDeviceMultiSend(context, device), ); } diff --git a/app/lib/pages/tabs/send_tab_vm.dart b/app/lib/pages/tabs/send_tab_vm.dart index 6eb357cf..80174ed6 100644 --- a/app/lib/pages/tabs/send_tab_vm.dart +++ b/app/lib/pages/tabs/send_tab_vm.dart @@ -56,7 +56,7 @@ final sendTabVmProvider = ViewProvider((ref) { final sendMode = ref.watch(settingsProvider.select((s) => s.sendMode)); final selectedFiles = ref.watch(selectedSendingFilesProvider); final localIps = ref.watch(localIpProvider).localIps; - final nearbyDevices = ref.watch(nearbyDevicesProvider).devices.values; + final nearbyDevices = ref.watch(nearbyDevicesProvider).allDevices.values; final favoriteDevices = ref.watch(favoritesProvider); return SendTabVm( diff --git a/app/lib/pages/tabs/settings_tab.dart b/app/lib/pages/tabs/settings_tab.dart index 96c3805a..f0d87c0e 100644 --- a/app/lib/pages/tabs/settings_tab.dart +++ b/app/lib/pages/tabs/settings_tab.dart @@ -41,7 +41,7 @@ class SettingsTab extends StatelessWidget { @override Widget build(BuildContext context) { return ViewModelBuilder( - provider: settingsTabControllerProvider, + provider: (ref) => settingsTabControllerProvider, builder: (context, vm) { final ref = context.ref; return Stack( diff --git a/app/lib/pages/tabs/settings_tab_vm.mapper.dart b/app/lib/pages/tabs/settings_tab_vm.mapper.dart index 82a31883..0851defa 100644 --- a/app/lib/pages/tabs/settings_tab_vm.mapper.dart +++ b/app/lib/pages/tabs/settings_tab_vm.mapper.dart @@ -23,65 +23,113 @@ class SettingsTabVmMapper extends ClassMapperBase { final String id = 'SettingsTabVm'; static bool _$advanced(SettingsTabVm v) => v.advanced; - static const Field _f$advanced = Field('advanced', _$advanced); - static TextEditingController _$aliasController(SettingsTabVm v) => v.aliasController; - static const Field _f$aliasController = Field('aliasController', _$aliasController); - static TextEditingController _$deviceModelController(SettingsTabVm v) => v.deviceModelController; - static const Field _f$deviceModelController = Field('deviceModelController', _$deviceModelController); - static TextEditingController _$portController(SettingsTabVm v) => v.portController; - static const Field _f$portController = Field('portController', _$portController); - static TextEditingController _$timeoutController(SettingsTabVm v) => v.timeoutController; - static const Field _f$timeoutController = Field('timeoutController', _$timeoutController); - static TextEditingController _$multicastController(SettingsTabVm v) => v.multicastController; - static const Field _f$multicastController = Field('multicastController', _$multicastController); + static const Field _f$advanced = + Field('advanced', _$advanced); + static TextEditingController _$aliasController(SettingsTabVm v) => + v.aliasController; + static const Field _f$aliasController = + Field('aliasController', _$aliasController); + static TextEditingController _$deviceModelController(SettingsTabVm v) => + v.deviceModelController; + static const Field + _f$deviceModelController = + Field('deviceModelController', _$deviceModelController); + static TextEditingController _$portController(SettingsTabVm v) => + v.portController; + static const Field _f$portController = + Field('portController', _$portController); + static TextEditingController _$timeoutController(SettingsTabVm v) => + v.timeoutController; + static const Field + _f$timeoutController = Field('timeoutController', _$timeoutController); + static TextEditingController _$multicastController(SettingsTabVm v) => + v.multicastController; + static const Field + _f$multicastController = + Field('multicastController', _$multicastController); static SettingsState _$settings(SettingsTabVm v) => v.settings; - static const Field _f$settings = Field('settings', _$settings); + static const Field _f$settings = + Field('settings', _$settings); static ServerState? _$serverState(SettingsTabVm v) => v.serverState; - static const Field _f$serverState = Field('serverState', _$serverState); + static const Field _f$serverState = + Field('serverState', _$serverState); static DeviceInfoResult _$deviceInfo(SettingsTabVm v) => v.deviceInfo; - static const Field _f$deviceInfo = Field('deviceInfo', _$deviceInfo); + static const Field _f$deviceInfo = + Field('deviceInfo', _$deviceInfo); static List _$colorModes(SettingsTabVm v) => v.colorModes; - static const Field> _f$colorModes = Field('colorModes', _$colorModes); + static const Field> _f$colorModes = + Field('colorModes', _$colorModes); static bool _$autoStart(SettingsTabVm v) => v.autoStart; - static const Field _f$autoStart = Field('autoStart', _$autoStart); - static bool _$autoStartLaunchHidden(SettingsTabVm v) => v.autoStartLaunchHidden; - static const Field _f$autoStartLaunchHidden = Field('autoStartLaunchHidden', _$autoStartLaunchHidden); + static const Field _f$autoStart = + Field('autoStart', _$autoStart); + static bool _$autoStartLaunchHidden(SettingsTabVm v) => + v.autoStartLaunchHidden; + static const Field _f$autoStartLaunchHidden = + Field('autoStartLaunchHidden', _$autoStartLaunchHidden); static bool _$showInContextMenu(SettingsTabVm v) => v.showInContextMenu; - static const Field _f$showInContextMenu = Field('showInContextMenu', _$showInContextMenu); - static Function _$onChangeTheme(SettingsTabVm v) => (v as dynamic).onChangeTheme as Function; - static dynamic _arg$onChangeTheme(f) => f(); - static const Field _f$onChangeTheme = Field('onChangeTheme', _$onChangeTheme, arg: _arg$onChangeTheme); - static Function _$onChangeColorMode(SettingsTabVm v) => (v as dynamic).onChangeColorMode as Function; + static const Field _f$showInContextMenu = + Field('showInContextMenu', _$showInContextMenu); + static Function _$onChangeTheme(SettingsTabVm v) => + (v as dynamic).onChangeTheme as Function; + static dynamic _arg$onChangeTheme(f) => + f(); + static const Field _f$onChangeTheme = + Field('onChangeTheme', _$onChangeTheme, arg: _arg$onChangeTheme); + static Function _$onChangeColorMode(SettingsTabVm v) => + (v as dynamic).onChangeColorMode as Function; static dynamic _arg$onChangeColorMode(f) => f(); - static const Field _f$onChangeColorMode = Field('onChangeColorMode', _$onChangeColorMode, arg: _arg$onChangeColorMode); - static Function _$onTapLanguage(SettingsTabVm v) => (v as dynamic).onTapLanguage as Function; + static const Field _f$onChangeColorMode = Field( + 'onChangeColorMode', _$onChangeColorMode, + arg: _arg$onChangeColorMode); + static Function _$onTapLanguage(SettingsTabVm v) => + (v as dynamic).onTapLanguage as Function; static dynamic _arg$onTapLanguage(f) => f(); - static const Field _f$onTapLanguage = Field('onTapLanguage', _$onTapLanguage, arg: _arg$onTapLanguage); - static Function _$onToggleAutoStart(SettingsTabVm v) => (v as dynamic).onToggleAutoStart as Function; + static const Field _f$onTapLanguage = + Field('onTapLanguage', _$onTapLanguage, arg: _arg$onTapLanguage); + static Function _$onToggleAutoStart(SettingsTabVm v) => + (v as dynamic).onToggleAutoStart as Function; static dynamic _arg$onToggleAutoStart(f) => f(); - static const Field _f$onToggleAutoStart = Field('onToggleAutoStart', _$onToggleAutoStart, arg: _arg$onToggleAutoStart); - static Function _$onToggleAutoStartLaunchHidden(SettingsTabVm v) => (v as dynamic).onToggleAutoStartLaunchHidden as Function; - static dynamic _arg$onToggleAutoStartLaunchHidden(f) => f(); + static const Field _f$onToggleAutoStart = Field( + 'onToggleAutoStart', _$onToggleAutoStart, + arg: _arg$onToggleAutoStart); + static Function _$onToggleAutoStartLaunchHidden(SettingsTabVm v) => + (v as dynamic).onToggleAutoStartLaunchHidden as Function; + static dynamic _arg$onToggleAutoStartLaunchHidden(f) => + f(); static const Field _f$onToggleAutoStartLaunchHidden = - Field('onToggleAutoStartLaunchHidden', _$onToggleAutoStartLaunchHidden, arg: _arg$onToggleAutoStartLaunchHidden); - static Function _$onToggleShowInContextMenu(SettingsTabVm v) => (v as dynamic).onToggleShowInContextMenu as Function; - static dynamic _arg$onToggleShowInContextMenu(f) => f(); + Field('onToggleAutoStartLaunchHidden', _$onToggleAutoStartLaunchHidden, + arg: _arg$onToggleAutoStartLaunchHidden); + static Function _$onToggleShowInContextMenu(SettingsTabVm v) => + (v as dynamic).onToggleShowInContextMenu as Function; + static dynamic _arg$onToggleShowInContextMenu(f) => + f(); static const Field _f$onToggleShowInContextMenu = - Field('onToggleShowInContextMenu', _$onToggleShowInContextMenu, arg: _arg$onToggleShowInContextMenu); - static Function _$onTapRestartServer(SettingsTabVm v) => (v as dynamic).onTapRestartServer as Function; + Field('onToggleShowInContextMenu', _$onToggleShowInContextMenu, + arg: _arg$onToggleShowInContextMenu); + static Function _$onTapRestartServer(SettingsTabVm v) => + (v as dynamic).onTapRestartServer as Function; static dynamic _arg$onTapRestartServer(f) => f(); - static const Field _f$onTapRestartServer = Field('onTapRestartServer', _$onTapRestartServer, arg: _arg$onTapRestartServer); - static Function _$onTapStartServer(SettingsTabVm v) => (v as dynamic).onTapStartServer as Function; + static const Field _f$onTapRestartServer = Field( + 'onTapRestartServer', _$onTapRestartServer, + arg: _arg$onTapRestartServer); + static Function _$onTapStartServer(SettingsTabVm v) => + (v as dynamic).onTapStartServer as Function; static dynamic _arg$onTapStartServer(f) => f(); - static const Field _f$onTapStartServer = Field('onTapStartServer', _$onTapStartServer, arg: _arg$onTapStartServer); - static Function _$onTapStopServer(SettingsTabVm v) => (v as dynamic).onTapStopServer as Function; + static const Field _f$onTapStartServer = + Field('onTapStartServer', _$onTapStartServer, arg: _arg$onTapStartServer); + static Function _$onTapStopServer(SettingsTabVm v) => + (v as dynamic).onTapStopServer as Function; static dynamic _arg$onTapStopServer(f) => f(); - static const Field _f$onTapStopServer = Field('onTapStopServer', _$onTapStopServer, arg: _arg$onTapStopServer); - static Function _$onTapAdvanced(SettingsTabVm v) => (v as dynamic).onTapAdvanced as Function; + static const Field _f$onTapStopServer = + Field('onTapStopServer', _$onTapStopServer, arg: _arg$onTapStopServer); + static Function _$onTapAdvanced(SettingsTabVm v) => + (v as dynamic).onTapAdvanced as Function; static dynamic _arg$onTapAdvanced(f) => f(); - static const Field _f$onTapAdvanced = Field('onTapAdvanced', _$onTapAdvanced, arg: _arg$onTapAdvanced); + static const Field _f$onTapAdvanced = + Field('onTapAdvanced', _$onTapAdvanced, arg: _arg$onTapAdvanced); static List _$themeModes(SettingsTabVm v) => v.themeModes; - static const Field> _f$themeModes = Field('themeModes', _$themeModes, mode: FieldMode.member); + static const Field> _f$themeModes = + Field('themeModes', _$themeModes, mode: FieldMode.member); @override final MappableFields fields = const { @@ -130,7 +178,8 @@ class SettingsTabVmMapper extends ClassMapperBase { onChangeColorMode: data.dec(_f$onChangeColorMode), onTapLanguage: data.dec(_f$onTapLanguage), onToggleAutoStart: data.dec(_f$onToggleAutoStart), - onToggleAutoStartLaunchHidden: data.dec(_f$onToggleAutoStartLaunchHidden), + onToggleAutoStartLaunchHidden: + data.dec(_f$onToggleAutoStartLaunchHidden), onToggleShowInContextMenu: data.dec(_f$onToggleShowInContextMenu), onTapRestartServer: data.dec(_f$onTapRestartServer), onTapStartServer: data.dec(_f$onTapStartServer), @@ -152,39 +201,49 @@ class SettingsTabVmMapper extends ClassMapperBase { mixin SettingsTabVmMappable { String serialize() { - return SettingsTabVmMapper.ensureInitialized().encodeJson(this as SettingsTabVm); + return SettingsTabVmMapper.ensureInitialized() + .encodeJson(this as SettingsTabVm); } Map toJson() { - return SettingsTabVmMapper.ensureInitialized().encodeMap(this as SettingsTabVm); + return SettingsTabVmMapper.ensureInitialized() + .encodeMap(this as SettingsTabVm); } - SettingsTabVmCopyWith get copyWith => - _SettingsTabVmCopyWithImpl(this as SettingsTabVm, $identity, $identity); + SettingsTabVmCopyWith + get copyWith => _SettingsTabVmCopyWithImpl( + this as SettingsTabVm, $identity, $identity); @override String toString() { - return SettingsTabVmMapper.ensureInitialized().stringifyValue(this as SettingsTabVm); + return SettingsTabVmMapper.ensureInitialized() + .stringifyValue(this as SettingsTabVm); } @override bool operator ==(Object other) { - return SettingsTabVmMapper.ensureInitialized().equalsValue(this as SettingsTabVm, other); + return SettingsTabVmMapper.ensureInitialized() + .equalsValue(this as SettingsTabVm, other); } @override int get hashCode { - return SettingsTabVmMapper.ensureInitialized().hashValue(this as SettingsTabVm); + return SettingsTabVmMapper.ensureInitialized() + .hashValue(this as SettingsTabVm); } } -extension SettingsTabVmValueCopy<$R, $Out> on ObjectCopyWith<$R, SettingsTabVm, $Out> { - SettingsTabVmCopyWith<$R, SettingsTabVm, $Out> get $asSettingsTabVm => $base.as((v, t, t2) => _SettingsTabVmCopyWithImpl(v, t, t2)); +extension SettingsTabVmValueCopy<$R, $Out> + on ObjectCopyWith<$R, SettingsTabVm, $Out> { + SettingsTabVmCopyWith<$R, SettingsTabVm, $Out> get $asSettingsTabVm => + $base.as((v, t, t2) => _SettingsTabVmCopyWithImpl(v, t, t2)); } -abstract class SettingsTabVmCopyWith<$R, $In extends SettingsTabVm, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class SettingsTabVmCopyWith<$R, $In extends SettingsTabVm, $Out> + implements ClassCopyWith<$R, $In, $Out> { SettingsStateCopyWith<$R, SettingsState, SettingsState> get settings; ServerStateCopyWith<$R, ServerState, ServerState>? get serverState; - ListCopyWith<$R, ColorMode, ObjectCopyWith<$R, ColorMode, ColorMode>> get colorModes; + ListCopyWith<$R, ColorMode, ObjectCopyWith<$R, ColorMode, ColorMode>> + get colorModes; $R call( {bool? advanced, TextEditingController? aliasController, @@ -212,19 +271,26 @@ abstract class SettingsTabVmCopyWith<$R, $In extends SettingsTabVm, $Out> implem SettingsTabVmCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SettingsTabVm, $Out> +class _SettingsTabVmCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SettingsTabVm, $Out> implements SettingsTabVmCopyWith<$R, SettingsTabVm, $Out> { _SettingsTabVmCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = SettingsTabVmMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + SettingsTabVmMapper.ensureInitialized(); @override - SettingsStateCopyWith<$R, SettingsState, SettingsState> get settings => $value.settings.copyWith.$chain((v) => call(settings: v)); + SettingsStateCopyWith<$R, SettingsState, SettingsState> get settings => + $value.settings.copyWith.$chain((v) => call(settings: v)); @override - ServerStateCopyWith<$R, ServerState, ServerState>? get serverState => $value.serverState?.copyWith.$chain((v) => call(serverState: v)); + ServerStateCopyWith<$R, ServerState, ServerState>? get serverState => + $value.serverState?.copyWith.$chain((v) => call(serverState: v)); @override - ListCopyWith<$R, ColorMode, ObjectCopyWith<$R, ColorMode, ColorMode>> get colorModes => - ListCopyWith($value.colorModes, (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(colorModes: v)); + ListCopyWith<$R, ColorMode, ObjectCopyWith<$R, ColorMode, ColorMode>> + get colorModes => ListCopyWith( + $value.colorModes, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(colorModes: v)); @override $R call( {bool? advanced, @@ -253,23 +319,28 @@ class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting $apply(FieldCopyWithData({ if (advanced != null) #advanced: advanced, if (aliasController != null) #aliasController: aliasController, - if (deviceModelController != null) #deviceModelController: deviceModelController, + if (deviceModelController != null) + #deviceModelController: deviceModelController, if (portController != null) #portController: portController, if (timeoutController != null) #timeoutController: timeoutController, - if (multicastController != null) #multicastController: multicastController, + if (multicastController != null) + #multicastController: multicastController, if (settings != null) #settings: settings, if (serverState != $none) #serverState: serverState, if (deviceInfo != null) #deviceInfo: deviceInfo, if (colorModes != null) #colorModes: colorModes, if (autoStart != null) #autoStart: autoStart, - if (autoStartLaunchHidden != null) #autoStartLaunchHidden: autoStartLaunchHidden, + if (autoStartLaunchHidden != null) + #autoStartLaunchHidden: autoStartLaunchHidden, if (showInContextMenu != null) #showInContextMenu: showInContextMenu, if (onChangeTheme != null) #onChangeTheme: onChangeTheme, if (onChangeColorMode != null) #onChangeColorMode: onChangeColorMode, if (onTapLanguage != null) #onTapLanguage: onTapLanguage, if (onToggleAutoStart != null) #onToggleAutoStart: onToggleAutoStart, - if (onToggleAutoStartLaunchHidden != null) #onToggleAutoStartLaunchHidden: onToggleAutoStartLaunchHidden, - if (onToggleShowInContextMenu != null) #onToggleShowInContextMenu: onToggleShowInContextMenu, + if (onToggleAutoStartLaunchHidden != null) + #onToggleAutoStartLaunchHidden: onToggleAutoStartLaunchHidden, + if (onToggleShowInContextMenu != null) + #onToggleShowInContextMenu: onToggleShowInContextMenu, if (onTapRestartServer != null) #onTapRestartServer: onTapRestartServer, if (onTapStartServer != null) #onTapStartServer: onTapStartServer, if (onTapStopServer != null) #onTapStopServer: onTapStopServer, @@ -279,28 +350,41 @@ class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting SettingsTabVm $make(CopyWithData data) => SettingsTabVm( advanced: data.get(#advanced, or: $value.advanced), aliasController: data.get(#aliasController, or: $value.aliasController), - deviceModelController: data.get(#deviceModelController, or: $value.deviceModelController), + deviceModelController: + data.get(#deviceModelController, or: $value.deviceModelController), portController: data.get(#portController, or: $value.portController), - timeoutController: data.get(#timeoutController, or: $value.timeoutController), - multicastController: data.get(#multicastController, or: $value.multicastController), + timeoutController: + data.get(#timeoutController, or: $value.timeoutController), + multicastController: + data.get(#multicastController, or: $value.multicastController), settings: data.get(#settings, or: $value.settings), serverState: data.get(#serverState, or: $value.serverState), deviceInfo: data.get(#deviceInfo, or: $value.deviceInfo), colorModes: data.get(#colorModes, or: $value.colorModes), autoStart: data.get(#autoStart, or: $value.autoStart), - autoStartLaunchHidden: data.get(#autoStartLaunchHidden, or: $value.autoStartLaunchHidden), - showInContextMenu: data.get(#showInContextMenu, or: $value.showInContextMenu), + autoStartLaunchHidden: + data.get(#autoStartLaunchHidden, or: $value.autoStartLaunchHidden), + showInContextMenu: + data.get(#showInContextMenu, or: $value.showInContextMenu), onChangeTheme: data.get(#onChangeTheme, or: $value.onChangeTheme), - onChangeColorMode: data.get(#onChangeColorMode, or: $value.onChangeColorMode), + onChangeColorMode: + data.get(#onChangeColorMode, or: $value.onChangeColorMode), onTapLanguage: data.get(#onTapLanguage, or: $value.onTapLanguage), - onToggleAutoStart: data.get(#onToggleAutoStart, or: $value.onToggleAutoStart), - onToggleAutoStartLaunchHidden: data.get(#onToggleAutoStartLaunchHidden, or: $value.onToggleAutoStartLaunchHidden), - onToggleShowInContextMenu: data.get(#onToggleShowInContextMenu, or: $value.onToggleShowInContextMenu), - onTapRestartServer: data.get(#onTapRestartServer, or: $value.onTapRestartServer), - onTapStartServer: data.get(#onTapStartServer, or: $value.onTapStartServer), + onToggleAutoStart: + data.get(#onToggleAutoStart, or: $value.onToggleAutoStart), + onToggleAutoStartLaunchHidden: data.get(#onToggleAutoStartLaunchHidden, + or: $value.onToggleAutoStartLaunchHidden), + onToggleShowInContextMenu: data.get(#onToggleShowInContextMenu, + or: $value.onToggleShowInContextMenu), + onTapRestartServer: + data.get(#onTapRestartServer, or: $value.onTapRestartServer), + onTapStartServer: + data.get(#onTapStartServer, or: $value.onTapStartServer), onTapStopServer: data.get(#onTapStopServer, or: $value.onTapStopServer), onTapAdvanced: data.get(#onTapAdvanced, or: $value.onTapAdvanced)); @override - SettingsTabVmCopyWith<$R2, SettingsTabVm, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _SettingsTabVmCopyWithImpl($value, $cast, t); + SettingsTabVmCopyWith<$R2, SettingsTabVm, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SettingsTabVmCopyWithImpl($value, $cast, t); } diff --git a/app/lib/provider/device_info_provider.dart b/app/lib/provider/device_info_provider.dart index 32b010d3..73db79bd 100644 --- a/app/lib/provider/device_info_provider.dart +++ b/app/lib/provider/device_info_provider.dart @@ -32,6 +32,7 @@ final deviceFullInfoProvider = ViewProvider((ref) { final rawInfo = ref.watch(deviceInfoProvider); final securityContext = ref.read(securityProvider); return Device( + signalingId: null, ip: networkInfo.localIps.firstOrNull ?? '-', version: protocolVersion, port: serverState?.port ?? -1, @@ -41,5 +42,6 @@ final deviceFullInfoProvider = ViewProvider((ref) { deviceModel: rawInfo.deviceModel, deviceType: rawInfo.deviceType, download: serverState?.webSendState != null, + discoveryMethods: const {}, ); }); diff --git a/app/lib/provider/network/nearby_devices_provider.dart b/app/lib/provider/network/nearby_devices_provider.dart index 065b1f51..9f81a02d 100644 --- a/app/lib/provider/network/nearby_devices_provider.dart +++ b/app/lib/provider/network/nearby_devices_provider.dart @@ -40,6 +40,7 @@ class NearbyDevicesService extends ReduxNotifier { runningFavoriteScan: false, runningIps: {}, devices: {}, + signalingDevices: {}, ); } @@ -78,6 +79,8 @@ class RegisterDeviceAction extends AsyncReduxAction reduce() async { + assert(device.ip?.isNotEmpty ?? false, 'IP must not be empty'); + final favoriteDevice = notifier._favoriteService.state.firstWhereOrNull((e) => e.fingerprint == device.fingerprint); if (favoriteDevice != null && !favoriteDevice.customAlias) { // Update existing favorite with new alias @@ -86,7 +89,46 @@ class RegisterDeviceAction extends AsyncReduxAction device, ifAbsent: () => device), + devices: {...state.devices}..update(device.ip!, (_) => device, ifAbsent: () => device), + ); + } +} + +/// Registers a new device found via signaling. +class RegisterSignalingDeviceAction extends ReduxAction { + final Device device; + + RegisterSignalingDeviceAction(this.device); + + @override + NearbyDevicesState reduce() { + final Set existingDevices = state.signalingDevices[device.fingerprint]?.toSet() ?? {}; + final existingDevice = existingDevices.firstWhereOrNull((e) => e.signalingId == device.signalingId); + if (existingDevice != null) { + existingDevices.remove(existingDevice); + } + existingDevices.add(device); + + return state.copyWith( + signalingDevices: { + ...state.signalingDevices, + device.fingerprint: existingDevices, + }, + ); + } +} + +class UnregisterSignalingDeviceAction extends ReduxAction { + final String signalingId; + + UnregisterSignalingDeviceAction(this.signalingId); + + @override + NearbyDevicesState reduce() { + return state.copyWith( + signalingDevices: { + for (final entry in state.signalingDevices.entries) entry.key: entry.value.where((e) => e.signalingId != signalingId).toSet(), + }, ); } } diff --git a/app/lib/provider/network/server/controller/receive_controller.dart b/app/lib/provider/network/server/controller/receive_controller.dart index 5b9dbd7f..610f210b 100644 --- a/app/lib/provider/network/server/controller/receive_controller.dart +++ b/app/lib/provider/network/server/controller/receive_controller.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:common/api_route_builder.dart'; import 'package:common/constants.dart'; +import 'package:common/model/device.dart'; import 'package:common/model/dto/info_dto.dart'; import 'package:common/model/dto/info_register_dto.dart'; import 'package:common/model/dto/prepare_upload_request_dto.dart'; @@ -23,7 +24,6 @@ import 'package:localsend_app/pages/home_page.dart'; import 'package:localsend_app/pages/home_page_controller.dart'; import 'package:localsend_app/pages/progress_page.dart'; import 'package:localsend_app/pages/receive_page.dart'; -import 'package:localsend_app/pages/receive_page_controller.dart'; import 'package:localsend_app/provider/device_info_provider.dart'; import 'package:localsend_app/provider/favorites_provider.dart'; import 'package:localsend_app/provider/http_provider.dart'; @@ -31,6 +31,7 @@ import 'package:localsend_app/provider/logging/discovery_logs_provider.dart'; import 'package:localsend_app/provider/network/nearby_devices_provider.dart'; import 'package:localsend_app/provider/network/send_provider.dart'; import 'package:localsend_app/provider/network/server/controller/common.dart'; +import 'package:localsend_app/provider/network/server/server_provider.dart'; import 'package:localsend_app/provider/network/server/server_utils.dart'; import 'package:localsend_app/provider/progress_provider.dart'; import 'package:localsend_app/provider/receive_history_provider.dart'; @@ -45,6 +46,7 @@ import 'package:localsend_app/util/simple_server.dart'; import 'package:localsend_app/widget/dialogs/open_file_dialog.dart'; import 'package:logging/logging.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:refena_flutter/refena_flutter.dart'; import 'package:routerino/routerino.dart'; import 'package:uuid/uuid.dart'; import 'package:window_manager/window_manager.dart'; @@ -164,7 +166,9 @@ class ReceiveController { } // Save device information - await server.ref.redux(nearbyDevicesProvider).dispatchAsync(RegisterDeviceAction(requestDto.toDevice(request.ip, port, https))); + await server.ref + .redux(nearbyDevicesProvider) + .dispatchAsync(RegisterDeviceAction(requestDto.toDevice(request.ip, port, https, HttpDiscovery(ip: request.ip)))); server.ref.notifier(discoveryLoggerProvider).addLog('[DISCOVER/TCP] Received "/register" HTTP request: ${requestDto.alias} (${request.ip})'); final deviceInfo = server.ref.read(deviceInfoProvider); @@ -229,7 +233,7 @@ class ReceiveController { session: ReceiveSessionState( sessionId: sessionId, status: SessionStatus.waiting, - sender: dto.info.toDevice(request.ip, port, https), + sender: dto.info.toDevice(request.ip, port, https, null), senderAlias: server.ref.read(favoritesProvider).firstWhereOrNull((e) => e.fingerprint == dto.info.fingerprint)?.alias ?? dto.info.alias, files: { for (final file in dto.files.values) @@ -287,14 +291,51 @@ class ReceiveController { senderAlias: server.getState().session!.senderAlias, timestamp: DateTime.now().toUtc(), )); - } else { - server.ref.notifier(selectedReceivingFilesProvider).setFiles(server.getState().session!.files.values.map((f) => f.file).toList()); } - server.ref.redux(receivePageControllerProvider).dispatch(InitReceivePageAction()); + final receiveProvider = ViewProvider((ref) { + final session = ref.watch(serverProvider.select((state) => state?.session)); + return ReceivePageVm( + status: session?.status, + sender: session?.sender ?? Device.empty, + showSenderInfo: true, + files: session?.files.values.map((f) => f.file).toList() ?? [], + message: message, + onAccept: () async { + if (message != null) { + // accept nothing + ref.notifier(serverProvider).acceptFileRequest({}); + return; + } + + final sessionId = ref.read(serverProvider)?.session?.sessionId; + if (sessionId == null) { + return; + } + + final selectedFiles = ref.read(selectedReceivingFilesProvider); + ref.notifier(serverProvider).acceptFileRequest(selectedFiles); + + await Routerino.context.pushAndRemoveUntilImmediately( + removeUntil: ReceivePage, + builder: () => ProgressPage( + showAppBar: false, + closeSessionOnClose: true, + sessionId: sessionId, + ), + ); + }, + onDecline: () { + ref.notifier(serverProvider).declineFileRequest(); + }, + onClose: () { + ref.notifier(serverProvider).closeSession(); + }, + ); + }); // ignore: use_build_context_synchronously, unawaited_futures - Routerino.context.push(() => const ReceivePage()); + Routerino.context.push(() => ReceivePage(receiveProvider)); // Delayed response (waiting for user's decision) selection = await streamController.stream.first; diff --git a/app/lib/provider/network/webrtc/signaling_provider.dart b/app/lib/provider/network/webrtc/signaling_provider.dart new file mode 100644 index 00000000..c2288533 --- /dev/null +++ b/app/lib/provider/network/webrtc/signaling_provider.dart @@ -0,0 +1,231 @@ +import 'dart:async'; + +import 'package:common/constants.dart'; +import 'package:common/model/device.dart'; +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:localsend_app/provider/device_info_provider.dart'; +import 'package:localsend_app/provider/favorites_provider.dart'; +import 'package:localsend_app/provider/network/nearby_devices_provider.dart'; +import 'package:localsend_app/provider/network/webrtc/webrtc_receiver.dart'; +import 'package:localsend_app/provider/persistence_provider.dart'; +import 'package:localsend_app/provider/security_provider.dart'; +import 'package:localsend_app/provider/settings_provider.dart'; +import 'package:localsend_app/rust/api/crypto.dart' as crypto; +import 'package:localsend_app/rust/api/model.dart' as rust; +import 'package:localsend_app/rust/api/webrtc.dart'; +import 'package:refena_flutter/refena_flutter.dart'; + +part 'signaling_provider.mapper.dart'; + +@MappableClass() +class SignalingState with SignalingStateMappable { + final List signalingServers; + final List stunServers; + final Map connections; + + SignalingState({ + required this.signalingServers, + required this.stunServers, + required this.connections, + }); +} + +final signalingProvider = ReduxProvider((ref) { + return SignalingService( + persistence: ref.read(persistenceProvider), + ); +}); + +class SignalingService extends ReduxNotifier { + final PersistenceService _persistence; + + SignalingService({ + required PersistenceService persistence, + }) : _persistence = persistence; + + @override + SignalingState init() { + return SignalingState( + signalingServers: _persistence.getSignalingServers() ?? ['wss://public.localsend.org/v1/ws'], + stunServers: _persistence.getStunServers() ?? ['stun:stun.localsend.org:5349'], + connections: {}, + ); + } +} + +class SetupSignalingConnection extends ReduxAction with GlobalActions { + @override + SignalingState reduce() { + for (final signalingServer in state.signalingServers) { + // ignore: discarded_futures + global.dispatchAsync(_SetupSignalingConnection(signalingServer: signalingServer)); + } + return state; + } +} + +/// Starts an endless running action. +class _SetupSignalingConnection extends AsyncGlobalAction { + final String signalingServer; + + _SetupSignalingConnection({required this.signalingServer}); + + @override + Future reduce() async { + final settings = ref.read(settingsProvider); + final deviceInfo = ref.read(deviceInfoProvider); + final security = ref.read(securityProvider); + + // TODO: Use persistent key + final key = await crypto.generateKeyPair(); + print('private key: ${key.privateKey}'); + + LsSignalingConnection? connection; + final stream = connect( + uri: 'wss://public.localsend.org/v1/ws', + info: ProposingClientInfo( + alias: settings.alias, + version: protocolVersion, + deviceModel: deviceInfo.deviceModel, + deviceType: deviceInfo.deviceType.toRustDeviceType(), + ), + privateKey: key.privateKey, + onConnection: (c) { + connection = c; + + ref.redux(signalingProvider).dispatch(_SetConnectionAction( + signalingServer: signalingServer, + connection: c, + )); + }, + ); + + try { + await for (final message in stream) { + switch (message) { + case WsServerMessage_Hello(): + for (final d in message.peers) { + ref.redux(nearbyDevicesProvider).dispatch(RegisterSignalingDeviceAction( + d.toDevice(signalingServer), + )); + } + break; + case WsServerMessage_Join(peer: final peer): + case WsServerMessage_Update(peer: final peer): + ref.redux(nearbyDevicesProvider).dispatch(RegisterSignalingDeviceAction( + peer.toDevice(signalingServer), + )); + break; + case WsServerMessage_Left(): + ref.redux(nearbyDevicesProvider).dispatch(UnregisterSignalingDeviceAction( + message.peerId.uuid, + )); + break; + case WsServerMessage_Offer(): + final provider = ReduxProvider((ref) { + return WebRTCReceiveService( + signalingServer: signalingServer, + stunServers: ref.read(signalingProvider).stunServers, + connection: connection!, + offer: message.field0, + settings: ref.read(settingsProvider), + favorites: ref.read(favoritesProvider), + key: ref.read(securityProvider), + ); + }); + + await ref.redux(provider).dispatchAsync(AcceptOfferAction()); + break; + case WsServerMessage_Answer(): + case WsServerMessage_Error(): + } + } + } finally { + ref.redux(signalingProvider).dispatch(_RemoveConnectionAction(signalingServer: signalingServer)); + } + + return state; + } +} + +class _SetConnectionAction extends ReduxAction { + final String signalingServer; + final LsSignalingConnection connection; + + _SetConnectionAction({ + required this.signalingServer, + required this.connection, + }); + + @override + SignalingState reduce() { + return state.copyWith( + connections: { + ...state.connections, + signalingServer: connection, + }, + ); + } +} + +class _RemoveConnectionAction extends ReduxAction { + final String signalingServer; + + _RemoveConnectionAction({required this.signalingServer}); + + @override + SignalingState reduce() { + return state.copyWith( + connections: { + for (final entry in state.connections.entries) + if (entry.key != signalingServer) entry.key: entry.value, + }, + ); + } +} + +extension ClientInfoExt on ClientInfo { + Device toDevice(String signalingServer) { + return Device( + signalingId: id.uuid, + ip: null, + version: version, + port: -1, + https: false, + fingerprint: token, + alias: alias, + deviceModel: deviceModel, + deviceType: deviceType?.toDeviceType() ?? DeviceType.desktop, + download: false, + discoveryMethods: { + SignalingDiscovery( + signalingServer: signalingServer, + ), + }, + ); + } +} + +extension on rust.DeviceType { + DeviceType toDeviceType() { + return switch (this) { + rust.DeviceType.mobile => DeviceType.mobile, + rust.DeviceType.desktop => DeviceType.desktop, + rust.DeviceType.web => DeviceType.web, + rust.DeviceType.headless => DeviceType.headless, + rust.DeviceType.server => DeviceType.server, + }; + } +} + +extension on DeviceType { + rust.DeviceType toRustDeviceType() { + return switch (this) { + DeviceType.mobile => rust.DeviceType.mobile, + DeviceType.desktop => rust.DeviceType.desktop, + DeviceType.web => rust.DeviceType.web, + DeviceType.headless => rust.DeviceType.headless, + DeviceType.server => rust.DeviceType.server, + }; + } +} diff --git a/app/lib/provider/network/webrtc/signaling_provider.mapper.dart b/app/lib/provider/network/webrtc/signaling_provider.mapper.dart new file mode 100644 index 00000000..76fe3cd0 --- /dev/null +++ b/app/lib/provider/network/webrtc/signaling_provider.mapper.dart @@ -0,0 +1,164 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'signaling_provider.dart'; + +class SignalingStateMapper extends ClassMapperBase { + SignalingStateMapper._(); + + static SignalingStateMapper? _instance; + static SignalingStateMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SignalingStateMapper._()); + } + return _instance!; + } + + @override + final String id = 'SignalingState'; + + static List _$signalingServers(SignalingState v) => + v.signalingServers; + static const Field> _f$signalingServers = + Field('signalingServers', _$signalingServers); + static List _$stunServers(SignalingState v) => v.stunServers; + static const Field> _f$stunServers = + Field('stunServers', _$stunServers); + static Map _$connections(SignalingState v) => + v.connections; + static const Field> + _f$connections = Field('connections', _$connections); + + @override + final MappableFields fields = const { + #signalingServers: _f$signalingServers, + #stunServers: _f$stunServers, + #connections: _f$connections, + }; + + static SignalingState _instantiate(DecodingData data) { + return SignalingState( + signalingServers: data.dec(_f$signalingServers), + stunServers: data.dec(_f$stunServers), + connections: data.dec(_f$connections)); + } + + @override + final Function instantiate = _instantiate; + + static SignalingState fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SignalingState deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SignalingStateMappable { + String serialize() { + return SignalingStateMapper.ensureInitialized() + .encodeJson(this as SignalingState); + } + + Map toJson() { + return SignalingStateMapper.ensureInitialized() + .encodeMap(this as SignalingState); + } + + SignalingStateCopyWith + get copyWith => _SignalingStateCopyWithImpl( + this as SignalingState, $identity, $identity); + @override + String toString() { + return SignalingStateMapper.ensureInitialized() + .stringifyValue(this as SignalingState); + } + + @override + bool operator ==(Object other) { + return SignalingStateMapper.ensureInitialized() + .equalsValue(this as SignalingState, other); + } + + @override + int get hashCode { + return SignalingStateMapper.ensureInitialized() + .hashValue(this as SignalingState); + } +} + +extension SignalingStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, SignalingState, $Out> { + SignalingStateCopyWith<$R, SignalingState, $Out> get $asSignalingState => + $base.as((v, t, t2) => _SignalingStateCopyWithImpl(v, t, t2)); +} + +abstract class SignalingStateCopyWith<$R, $In extends SignalingState, $Out> + implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> + get signalingServers; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get stunServers; + MapCopyWith<$R, String, LsSignalingConnection, + ObjectCopyWith<$R, LsSignalingConnection, LsSignalingConnection>> + get connections; + $R call( + {List? signalingServers, + List? stunServers, + Map? connections}); + SignalingStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _SignalingStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SignalingState, $Out> + implements SignalingStateCopyWith<$R, SignalingState, $Out> { + _SignalingStateCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + SignalingStateMapper.ensureInitialized(); + @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> + get signalingServers => ListCopyWith( + $value.signalingServers, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(signalingServers: v)); + @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> + get stunServers => ListCopyWith( + $value.stunServers, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(stunServers: v)); + @override + MapCopyWith<$R, String, LsSignalingConnection, + ObjectCopyWith<$R, LsSignalingConnection, LsSignalingConnection>> + get connections => MapCopyWith( + $value.connections, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(connections: v)); + @override + $R call( + {List? signalingServers, + List? stunServers, + Map? connections}) => + $apply(FieldCopyWithData({ + if (signalingServers != null) #signalingServers: signalingServers, + if (stunServers != null) #stunServers: stunServers, + if (connections != null) #connections: connections + })); + @override + SignalingState $make(CopyWithData data) => SignalingState( + signalingServers: + data.get(#signalingServers, or: $value.signalingServers), + stunServers: data.get(#stunServers, or: $value.stunServers), + connections: data.get(#connections, or: $value.connections)); + + @override + SignalingStateCopyWith<$R2, SignalingState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SignalingStateCopyWithImpl($value, $cast, t); +} diff --git a/app/lib/provider/network/webrtc/webrtc_receiver.dart b/app/lib/provider/network/webrtc/webrtc_receiver.dart new file mode 100644 index 00000000..6bf588e7 --- /dev/null +++ b/app/lib/provider/network/webrtc/webrtc_receiver.dart @@ -0,0 +1,218 @@ +import 'package:collection/collection.dart'; +import 'package:common/model/dto/file_dto.dart' as dart_model; +import 'package:common/model/file_status.dart'; +import 'package:common/model/session_status.dart'; +import 'package:common/model/stored_security_context.dart'; +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:localsend_app/model/persistence/favorite_device.dart'; +import 'package:localsend_app/model/state/server/receive_session_state.dart'; +import 'package:localsend_app/model/state/server/receiving_file.dart'; +import 'package:localsend_app/model/state/settings_state.dart'; +import 'package:localsend_app/pages/receive_page.dart'; +import 'package:localsend_app/provider/network/webrtc/signaling_provider.dart'; +import 'package:localsend_app/rust/api/model.dart'; +import 'package:localsend_app/rust/api/webrtc.dart'; +import 'package:refena_flutter/refena_flutter.dart'; +import 'package:routerino/routerino.dart'; + +part 'webrtc_receiver.mapper.dart'; + +@MappableClass() +class WebRTCReceiveState with WebRTCReceiveStateMappable { + final LsSignalingConnection connection; + final WsServerSdpMessage offer; + final RTCStatus? status; + final RtcReceiveController? controller; + final ReceiveSessionState? sessionState; + + const WebRTCReceiveState({ + required this.connection, + required this.offer, + required this.status, + required this.controller, + required this.sessionState, + }); +} + +class WebRTCReceiveService extends ReduxNotifier { + final String _signalingServer; + final List _stunServers; + final LsSignalingConnection _connection; + final WsServerSdpMessage _offer; + final SettingsState _settings; + final List _favorites; + final StoredSecurityContext _key; + + WebRTCReceiveService({ + required String signalingServer, + required List stunServers, + required LsSignalingConnection connection, + required WsServerSdpMessage offer, + required SettingsState settings, + required List favorites, + required StoredSecurityContext key, + }) : _signalingServer = signalingServer, + _stunServers = stunServers, + _connection = connection, + _offer = offer, + _settings = settings, + _favorites = favorites, + _key = key; + + @override + WebRTCReceiveState init() { + return WebRTCReceiveState( + connection: _connection, + offer: _offer, + status: null, + controller: null, + sessionState: null, + ); + } +} + +class AcceptOfferAction extends AsyncReduxAction { + @override + Future reduce() async { + final controller = await state.connection.acceptOffer( + stunServers: notifier._stunServers, + offer: state.offer, + privateKey: notifier._key.privateKey, + ); + + controller.listenStatus().listen((status) { + dispatch(_SetStatusAction(status)); + }); + + return state.copyWith( + controller: controller, + ); + } + + @override + void after() { + // ignore: discarded_futures + dispatchAsync(_AcceptOfferAction()); + } +} + +class _AcceptOfferAction extends AsyncReduxAction { + @override + Future reduce() async { + final controller = state.controller; + if (controller == null) { + return state; + } + + final files = await controller.listenFiles(); + final convertedFiles = files.map((e) => e.toFileDto()).toList(); + dispatch(_InitSessionState(convertedFiles)); + + final vm = ViewProvider((ref) { + final state = ref.watch(notifier.provider as ReduxProvider); + return ReceivePageVm( + status: switch (state.status) { + null => SessionStatus.waiting, + RTCStatus_SdpExchanged() => SessionStatus.waiting, + RTCStatus_Connected() => SessionStatus.waiting, + RTCStatus_PinRequired() => SessionStatus.waiting, + RTCStatus_TooManyAttempts() => SessionStatus.tooManyAttempts, + RTCStatus_Declined() => SessionStatus.declined, + RTCStatus_Sending() => SessionStatus.sending, + RTCStatus_Finished() => SessionStatus.finished, + RTCStatus_Error() => SessionStatus.finishedWithErrors, + }, + sender: state.offer.peer.toDevice(notifier._signalingServer), + showSenderInfo: true, + files: convertedFiles, + message: files.length == 1 && files[0].fileType.startsWith('text/') ? files[0].preview : null, + onAccept: () {}, + onDecline: () {}, + onClose: () {}, + ); + }); + + // ignore: unawaited_futures, use_build_context_synchronously + Routerino.context.push(() => ReceivePage(vm)); + + return state.copyWith( + controller: controller, + ); + } +} + +class _InitSessionState extends ReduxAction { + final List files; + + _InitSessionState(this.files); + + @override + WebRTCReceiveState reduce() { + return state; + // TODO + // return state.copyWith( + // sessionState: ReceiveSessionState( + // sessionId: state.offer.sessionId, + // status: SessionStatus.waiting, + // sender: state.offer.peer.toDevice(notifier._signalingServer), + // senderAlias: notifier._favorites.firstWhereOrNull((e) => e.fingerprint == notifier.info.fingerprint)?.alias ?? dto.info.alias, + // files: { + // for (final file in files) + // file.id: ReceivingFile( + // file: file, + // status: FileStatus.queue, + // token: null, + // desiredName: null, + // path: null, + // savedToGallery: false, + // errorMessage: null, + // ), + // }, + // startTime: null, + // endTime: null, + // destinationDirectory: destinationDir, + // cacheDirectory: cacheDir, + // saveToGallery: checkPlatformWithGallery() && settings.saveToGallery && dto.files.values.every((f) => !f.fileName.contains('/')), + // createdDirectories: {}, + // responseHandler: streamController, + // ), + // ); + } +} + +class _SetStatusAction extends ReduxAction { + final RTCStatus status; + + _SetStatusAction(this.status); + + @override + WebRTCReceiveState reduce() { + return state.copyWith( + status: status, + ); + } +} + +extension on FileDto { + dart_model.FileDto toFileDto() { + return dart_model.FileDto( + id: id, + fileName: fileName, + size: size.toInt(), + fileType: dart_model.decodeFromMime(fileType), + hash: null, + preview: preview, + legacy: false, + metadata: metadata?.toFileMetadata(), + ); + } +} + +extension on FileMetadata { + dart_model.FileMetadata toFileMetadata() { + return dart_model.FileMetadata( + lastModified: DateTime.tryParse(modified ?? ''), + lastAccessed: DateTime.tryParse(accessed ?? ''), + ); + } +} diff --git a/app/lib/provider/network/webrtc/webrtc_receiver.mapper.dart b/app/lib/provider/network/webrtc/webrtc_receiver.mapper.dart new file mode 100644 index 00000000..e93c2ee2 --- /dev/null +++ b/app/lib/provider/network/webrtc/webrtc_receiver.mapper.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'webrtc_receiver.dart'; + +class WebRTCReceiveStateMapper extends ClassMapperBase { + WebRTCReceiveStateMapper._(); + + static WebRTCReceiveStateMapper? _instance; + static WebRTCReceiveStateMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = WebRTCReceiveStateMapper._()); + ReceiveSessionStateMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'WebRTCReceiveState'; + + static LsSignalingConnection _$connection(WebRTCReceiveState v) => + v.connection; + static const Field _f$connection = + Field('connection', _$connection); + static WsServerSdpMessage _$offer(WebRTCReceiveState v) => v.offer; + static const Field _f$offer = + Field('offer', _$offer); + static RTCStatus? _$status(WebRTCReceiveState v) => v.status; + static const Field _f$status = + Field('status', _$status); + static RtcReceiveController? _$controller(WebRTCReceiveState v) => + v.controller; + static const Field _f$controller = + Field('controller', _$controller); + static ReceiveSessionState? _$sessionState(WebRTCReceiveState v) => + v.sessionState; + static const Field _f$sessionState = + Field('sessionState', _$sessionState); + + @override + final MappableFields fields = const { + #connection: _f$connection, + #offer: _f$offer, + #status: _f$status, + #controller: _f$controller, + #sessionState: _f$sessionState, + }; + + static WebRTCReceiveState _instantiate(DecodingData data) { + return WebRTCReceiveState( + connection: data.dec(_f$connection), + offer: data.dec(_f$offer), + status: data.dec(_f$status), + controller: data.dec(_f$controller), + sessionState: data.dec(_f$sessionState)); + } + + @override + final Function instantiate = _instantiate; + + static WebRTCReceiveState fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static WebRTCReceiveState deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WebRTCReceiveStateMappable { + String serialize() { + return WebRTCReceiveStateMapper.ensureInitialized() + .encodeJson(this as WebRTCReceiveState); + } + + Map toJson() { + return WebRTCReceiveStateMapper.ensureInitialized() + .encodeMap(this as WebRTCReceiveState); + } + + WebRTCReceiveStateCopyWith + get copyWith => _WebRTCReceiveStateCopyWithImpl( + this as WebRTCReceiveState, $identity, $identity); + @override + String toString() { + return WebRTCReceiveStateMapper.ensureInitialized() + .stringifyValue(this as WebRTCReceiveState); + } + + @override + bool operator ==(Object other) { + return WebRTCReceiveStateMapper.ensureInitialized() + .equalsValue(this as WebRTCReceiveState, other); + } + + @override + int get hashCode { + return WebRTCReceiveStateMapper.ensureInitialized() + .hashValue(this as WebRTCReceiveState); + } +} + +extension WebRTCReceiveStateValueCopy<$R, $Out> + on ObjectCopyWith<$R, WebRTCReceiveState, $Out> { + WebRTCReceiveStateCopyWith<$R, WebRTCReceiveState, $Out> + get $asWebRTCReceiveState => + $base.as((v, t, t2) => _WebRTCReceiveStateCopyWithImpl(v, t, t2)); +} + +abstract class WebRTCReceiveStateCopyWith<$R, $In extends WebRTCReceiveState, + $Out> implements ClassCopyWith<$R, $In, $Out> { + ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? + get sessionState; + $R call( + {LsSignalingConnection? connection, + WsServerSdpMessage? offer, + RTCStatus? status, + RtcReceiveController? controller, + ReceiveSessionState? sessionState}); + WebRTCReceiveStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _WebRTCReceiveStateCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WebRTCReceiveState, $Out> + implements WebRTCReceiveStateCopyWith<$R, WebRTCReceiveState, $Out> { + _WebRTCReceiveStateCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WebRTCReceiveStateMapper.ensureInitialized(); + @override + ReceiveSessionStateCopyWith<$R, ReceiveSessionState, ReceiveSessionState>? + get sessionState => + $value.sessionState?.copyWith.$chain((v) => call(sessionState: v)); + @override + $R call( + {LsSignalingConnection? connection, + WsServerSdpMessage? offer, + Object? status = $none, + Object? controller = $none, + Object? sessionState = $none}) => + $apply(FieldCopyWithData({ + if (connection != null) #connection: connection, + if (offer != null) #offer: offer, + if (status != $none) #status: status, + if (controller != $none) #controller: controller, + if (sessionState != $none) #sessionState: sessionState + })); + @override + WebRTCReceiveState $make(CopyWithData data) => WebRTCReceiveState( + connection: data.get(#connection, or: $value.connection), + offer: data.get(#offer, or: $value.offer), + status: data.get(#status, or: $value.status), + controller: data.get(#controller, or: $value.controller), + sessionState: data.get(#sessionState, or: $value.sessionState)); + + @override + WebRTCReceiveStateCopyWith<$R2, WebRTCReceiveState, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _WebRTCReceiveStateCopyWithImpl($value, $cast, t); +} diff --git a/app/lib/provider/param/apk_provider_param.mapper.dart b/app/lib/provider/param/apk_provider_param.mapper.dart index e8c9e27a..39329b8c 100644 --- a/app/lib/provider/param/apk_provider_param.mapper.dart +++ b/app/lib/provider/param/apk_provider_param.mapper.dart @@ -21,13 +21,18 @@ class ApkProviderParamMapper extends ClassMapperBase { final String id = 'ApkProviderParam'; static String _$query(ApkProviderParam v) => v.query; - static const Field _f$query = Field('query', _$query); + static const Field _f$query = + Field('query', _$query); static bool _$includeSystemApps(ApkProviderParam v) => v.includeSystemApps; - static const Field _f$includeSystemApps = Field('includeSystemApps', _$includeSystemApps); - static bool _$onlyAppsWithLaunchIntent(ApkProviderParam v) => v.onlyAppsWithLaunchIntent; - static const Field _f$onlyAppsWithLaunchIntent = Field('onlyAppsWithLaunchIntent', _$onlyAppsWithLaunchIntent); + static const Field _f$includeSystemApps = + Field('includeSystemApps', _$includeSystemApps); + static bool _$onlyAppsWithLaunchIntent(ApkProviderParam v) => + v.onlyAppsWithLaunchIntent; + static const Field _f$onlyAppsWithLaunchIntent = + Field('onlyAppsWithLaunchIntent', _$onlyAppsWithLaunchIntent); static bool _$selectMultipleApps(ApkProviderParam v) => v.selectMultipleApps; - static const Field _f$selectMultipleApps = Field('selectMultipleApps', _$selectMultipleApps, opt: true, def: false); + static const Field _f$selectMultipleApps = + Field('selectMultipleApps', _$selectMultipleApps, opt: true, def: false); @override final MappableFields fields = const { @@ -59,60 +64,88 @@ class ApkProviderParamMapper extends ClassMapperBase { mixin ApkProviderParamMappable { String serialize() { - return ApkProviderParamMapper.ensureInitialized().encodeJson(this as ApkProviderParam); + return ApkProviderParamMapper.ensureInitialized() + .encodeJson(this as ApkProviderParam); } Map toJson() { - return ApkProviderParamMapper.ensureInitialized().encodeMap(this as ApkProviderParam); + return ApkProviderParamMapper.ensureInitialized() + .encodeMap(this as ApkProviderParam); } - ApkProviderParamCopyWith get copyWith => - _ApkProviderParamCopyWithImpl(this as ApkProviderParam, $identity, $identity); + ApkProviderParamCopyWith + get copyWith => _ApkProviderParamCopyWithImpl( + this as ApkProviderParam, $identity, $identity); @override String toString() { - return ApkProviderParamMapper.ensureInitialized().stringifyValue(this as ApkProviderParam); + return ApkProviderParamMapper.ensureInitialized() + .stringifyValue(this as ApkProviderParam); } @override bool operator ==(Object other) { - return ApkProviderParamMapper.ensureInitialized().equalsValue(this as ApkProviderParam, other); + return ApkProviderParamMapper.ensureInitialized() + .equalsValue(this as ApkProviderParam, other); } @override int get hashCode { - return ApkProviderParamMapper.ensureInitialized().hashValue(this as ApkProviderParam); + return ApkProviderParamMapper.ensureInitialized() + .hashValue(this as ApkProviderParam); } } -extension ApkProviderParamValueCopy<$R, $Out> on ObjectCopyWith<$R, ApkProviderParam, $Out> { - ApkProviderParamCopyWith<$R, ApkProviderParam, $Out> get $asApkProviderParam => $base.as((v, t, t2) => _ApkProviderParamCopyWithImpl(v, t, t2)); +extension ApkProviderParamValueCopy<$R, $Out> + on ObjectCopyWith<$R, ApkProviderParam, $Out> { + ApkProviderParamCopyWith<$R, ApkProviderParam, $Out> + get $asApkProviderParam => + $base.as((v, t, t2) => _ApkProviderParamCopyWithImpl(v, t, t2)); } -abstract class ApkProviderParamCopyWith<$R, $In extends ApkProviderParam, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call({String? query, bool? includeSystemApps, bool? onlyAppsWithLaunchIntent, bool? selectMultipleApps}); - ApkProviderParamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +abstract class ApkProviderParamCopyWith<$R, $In extends ApkProviderParam, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call( + {String? query, + bool? includeSystemApps, + bool? onlyAppsWithLaunchIntent, + bool? selectMultipleApps}); + ApkProviderParamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _ApkProviderParamCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, ApkProviderParam, $Out> +class _ApkProviderParamCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ApkProviderParam, $Out> implements ApkProviderParamCopyWith<$R, ApkProviderParam, $Out> { _ApkProviderParamCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = ApkProviderParamMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + ApkProviderParamMapper.ensureInitialized(); @override - $R call({String? query, bool? includeSystemApps, bool? onlyAppsWithLaunchIntent, bool? selectMultipleApps}) => $apply(FieldCopyWithData({ + $R call( + {String? query, + bool? includeSystemApps, + bool? onlyAppsWithLaunchIntent, + bool? selectMultipleApps}) => + $apply(FieldCopyWithData({ if (query != null) #query: query, if (includeSystemApps != null) #includeSystemApps: includeSystemApps, - if (onlyAppsWithLaunchIntent != null) #onlyAppsWithLaunchIntent: onlyAppsWithLaunchIntent, + if (onlyAppsWithLaunchIntent != null) + #onlyAppsWithLaunchIntent: onlyAppsWithLaunchIntent, if (selectMultipleApps != null) #selectMultipleApps: selectMultipleApps })); @override ApkProviderParam $make(CopyWithData data) => ApkProviderParam( query: data.get(#query, or: $value.query), - includeSystemApps: data.get(#includeSystemApps, or: $value.includeSystemApps), - onlyAppsWithLaunchIntent: data.get(#onlyAppsWithLaunchIntent, or: $value.onlyAppsWithLaunchIntent), - selectMultipleApps: data.get(#selectMultipleApps, or: $value.selectMultipleApps)); + includeSystemApps: + data.get(#includeSystemApps, or: $value.includeSystemApps), + onlyAppsWithLaunchIntent: data.get(#onlyAppsWithLaunchIntent, + or: $value.onlyAppsWithLaunchIntent), + selectMultipleApps: + data.get(#selectMultipleApps, or: $value.selectMultipleApps)); @override - ApkProviderParamCopyWith<$R2, ApkProviderParam, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _ApkProviderParamCopyWithImpl($value, $cast, t); + ApkProviderParamCopyWith<$R2, ApkProviderParam, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _ApkProviderParamCopyWithImpl($value, $cast, t); } diff --git a/app/lib/provider/param/cached_apk_provider_param.mapper.dart b/app/lib/provider/param/cached_apk_provider_param.mapper.dart index 083a8639..d1c6880d 100644 --- a/app/lib/provider/param/cached_apk_provider_param.mapper.dart +++ b/app/lib/provider/param/cached_apk_provider_param.mapper.dart @@ -6,7 +6,8 @@ part of 'cached_apk_provider_param.dart'; -class CachedApkProviderParamMapper extends ClassMapperBase { +class CachedApkProviderParamMapper + extends ClassMapperBase { CachedApkProviderParamMapper._(); static CachedApkProviderParamMapper? _instance; @@ -20,12 +21,18 @@ class CachedApkProviderParamMapper extends ClassMapperBase v.includeSystemApps; - static const Field _f$includeSystemApps = Field('includeSystemApps', _$includeSystemApps); - static bool _$onlyAppsWithLaunchIntent(CachedApkProviderParam v) => v.onlyAppsWithLaunchIntent; - static const Field _f$onlyAppsWithLaunchIntent = Field('onlyAppsWithLaunchIntent', _$onlyAppsWithLaunchIntent); - static bool _$selectMultipleApps(CachedApkProviderParam v) => v.selectMultipleApps; - static const Field _f$selectMultipleApps = Field('selectMultipleApps', _$selectMultipleApps, opt: true, def: false); + static bool _$includeSystemApps(CachedApkProviderParam v) => + v.includeSystemApps; + static const Field _f$includeSystemApps = + Field('includeSystemApps', _$includeSystemApps); + static bool _$onlyAppsWithLaunchIntent(CachedApkProviderParam v) => + v.onlyAppsWithLaunchIntent; + static const Field _f$onlyAppsWithLaunchIntent = + Field('onlyAppsWithLaunchIntent', _$onlyAppsWithLaunchIntent); + static bool _$selectMultipleApps(CachedApkProviderParam v) => + v.selectMultipleApps; + static const Field _f$selectMultipleApps = + Field('selectMultipleApps', _$selectMultipleApps, opt: true, def: false); @override final MappableFields fields = const { @@ -55,60 +62,88 @@ class CachedApkProviderParamMapper extends ClassMapperBase(this as CachedApkProviderParam); + return CachedApkProviderParamMapper.ensureInitialized() + .encodeJson(this as CachedApkProviderParam); } Map toJson() { - return CachedApkProviderParamMapper.ensureInitialized().encodeMap(this as CachedApkProviderParam); + return CachedApkProviderParamMapper.ensureInitialized() + .encodeMap(this as CachedApkProviderParam); } - CachedApkProviderParamCopyWith get copyWith => - _CachedApkProviderParamCopyWithImpl(this as CachedApkProviderParam, $identity, $identity); + CachedApkProviderParamCopyWith + get copyWith => _CachedApkProviderParamCopyWithImpl( + this as CachedApkProviderParam, $identity, $identity); @override String toString() { - return CachedApkProviderParamMapper.ensureInitialized().stringifyValue(this as CachedApkProviderParam); + return CachedApkProviderParamMapper.ensureInitialized() + .stringifyValue(this as CachedApkProviderParam); } @override bool operator ==(Object other) { - return CachedApkProviderParamMapper.ensureInitialized().equalsValue(this as CachedApkProviderParam, other); + return CachedApkProviderParamMapper.ensureInitialized() + .equalsValue(this as CachedApkProviderParam, other); } @override int get hashCode { - return CachedApkProviderParamMapper.ensureInitialized().hashValue(this as CachedApkProviderParam); + return CachedApkProviderParamMapper.ensureInitialized() + .hashValue(this as CachedApkProviderParam); } } -extension CachedApkProviderParamValueCopy<$R, $Out> on ObjectCopyWith<$R, CachedApkProviderParam, $Out> { - CachedApkProviderParamCopyWith<$R, CachedApkProviderParam, $Out> get $asCachedApkProviderParam => - $base.as((v, t, t2) => _CachedApkProviderParamCopyWithImpl(v, t, t2)); +extension CachedApkProviderParamValueCopy<$R, $Out> + on ObjectCopyWith<$R, CachedApkProviderParam, $Out> { + CachedApkProviderParamCopyWith<$R, CachedApkProviderParam, $Out> + get $asCachedApkProviderParam => + $base.as((v, t, t2) => _CachedApkProviderParamCopyWithImpl(v, t, t2)); } -abstract class CachedApkProviderParamCopyWith<$R, $In extends CachedApkProviderParam, $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call({bool? includeSystemApps, bool? onlyAppsWithLaunchIntent, bool? selectMultipleApps}); - CachedApkProviderParamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +abstract class CachedApkProviderParamCopyWith< + $R, + $In extends CachedApkProviderParam, + $Out> implements ClassCopyWith<$R, $In, $Out> { + $R call( + {bool? includeSystemApps, + bool? onlyAppsWithLaunchIntent, + bool? selectMultipleApps}); + CachedApkProviderParamCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _CachedApkProviderParamCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, CachedApkProviderParam, $Out> - implements CachedApkProviderParamCopyWith<$R, CachedApkProviderParam, $Out> { +class _CachedApkProviderParamCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, CachedApkProviderParam, $Out> + implements + CachedApkProviderParamCopyWith<$R, CachedApkProviderParam, $Out> { _CachedApkProviderParamCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = CachedApkProviderParamMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + CachedApkProviderParamMapper.ensureInitialized(); @override - $R call({bool? includeSystemApps, bool? onlyAppsWithLaunchIntent, bool? selectMultipleApps}) => $apply(FieldCopyWithData({ + $R call( + {bool? includeSystemApps, + bool? onlyAppsWithLaunchIntent, + bool? selectMultipleApps}) => + $apply(FieldCopyWithData({ if (includeSystemApps != null) #includeSystemApps: includeSystemApps, - if (onlyAppsWithLaunchIntent != null) #onlyAppsWithLaunchIntent: onlyAppsWithLaunchIntent, + if (onlyAppsWithLaunchIntent != null) + #onlyAppsWithLaunchIntent: onlyAppsWithLaunchIntent, if (selectMultipleApps != null) #selectMultipleApps: selectMultipleApps })); @override CachedApkProviderParam $make(CopyWithData data) => CachedApkProviderParam( - includeSystemApps: data.get(#includeSystemApps, or: $value.includeSystemApps), - onlyAppsWithLaunchIntent: data.get(#onlyAppsWithLaunchIntent, or: $value.onlyAppsWithLaunchIntent), - selectMultipleApps: data.get(#selectMultipleApps, or: $value.selectMultipleApps)); + includeSystemApps: + data.get(#includeSystemApps, or: $value.includeSystemApps), + onlyAppsWithLaunchIntent: data.get(#onlyAppsWithLaunchIntent, + or: $value.onlyAppsWithLaunchIntent), + selectMultipleApps: + data.get(#selectMultipleApps, or: $value.selectMultipleApps)); @override - CachedApkProviderParamCopyWith<$R2, CachedApkProviderParam, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _CachedApkProviderParamCopyWithImpl($value, $cast, t); + CachedApkProviderParamCopyWith<$R2, CachedApkProviderParam, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _CachedApkProviderParamCopyWithImpl($value, $cast, t); } diff --git a/app/lib/provider/persistence_provider.dart b/app/lib/provider/persistence_provider.dart index 4954ecad..7d776bab 100644 --- a/app/lib/provider/persistence_provider.dart +++ b/app/lib/provider/persistence_provider.dart @@ -47,6 +47,10 @@ const _version = 'ls_version'; // Security keys (generated on first app start) const _securityContext = 'ls_security_context'; +// WebRTC +const _signalingServers = 'ls_signaling_servers'; +const _stunServers = 'ls_stun_servers'; + // Received file history const _receiveHistory = 'ls_receive_history'; @@ -217,6 +221,32 @@ class PersistenceService { await _prefs.setString(_securityContext, jsonEncode(context)); } + List? getSignalingServers() { + final serversRaw = _prefs.getString(_signalingServers); + if (serversRaw == null) { + return null; + } + + return (jsonDecode(serversRaw) as List).cast(); + } + + Future setSignalingServers(List servers) async { + await _prefs.setString(_signalingServers, jsonEncode(servers)); + } + + List? getStunServers() { + final serversRaw = _prefs.getString(_stunServers); + if (serversRaw == null) { + return null; + } + + return (jsonDecode(serversRaw) as List).cast(); + } + + Future setStunServers(List servers) async { + await _prefs.setString(_stunServers, jsonEncode(servers)); + } + List getReceiveHistory() { final historyRaw = _prefs.getStringList(_receiveHistory) ?? []; return historyRaw.map((entry) => ReceiveHistoryEntry.fromJson(jsonDecode(entry))).toList(); diff --git a/app/lib/rust/api/crypto.dart b/app/lib/rust/api/crypto.dart new file mode 100644 index 00000000..e3d7c493 --- /dev/null +++ b/app/lib/rust/api/crypto.dart @@ -0,0 +1,35 @@ +// 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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; + +Future verifyCert({required String cert, required String publicKey}) => + RustLib.instance.api + .crateApiCryptoVerifyCert(cert: cert, publicKey: publicKey); + +Future generateKeyPair() => + RustLib.instance.api.crateApiCryptoGenerateKeyPair(); + +class KeyPair { + final String privateKey; + final String publicKey; + + const KeyPair({ + required this.privateKey, + required this.publicKey, + }); + + @override + int get hashCode => privateKey.hashCode ^ publicKey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is KeyPair && + runtimeType == other.runtimeType && + privateKey == other.privateKey && + publicKey == other.publicKey; +} diff --git a/app/lib/rust/api/logging.dart b/app/lib/rust/api/logging.dart new file mode 100644 index 00000000..fdf2d9ca --- /dev/null +++ b/app/lib/rust/api/logging.dart @@ -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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; + +Future enableDebugLogging() => + RustLib.instance.api.crateApiLoggingEnableDebugLogging(); diff --git a/app/lib/rust/api/model.dart b/app/lib/rust/api/model.dart new file mode 100644 index 00000000..dc051a34 --- /dev/null +++ b/app/lib/rust/api/model.dart @@ -0,0 +1,82 @@ +// 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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; + +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `PrepareUploadRequestDto`, `PrepareUploadResponseDto`, `ProtocolType`, `RegisterDto`, `RegisterResponseDto` + +enum DeviceType { + mobile, + desktop, + web, + headless, + server, + ; +} + +class FileDto { + final String id; + final String fileName; + final BigInt size; + final String fileType; + final String? sha256; + final String? preview; + final FileMetadata? metadata; + + const FileDto({ + required this.id, + required this.fileName, + required this.size, + required this.fileType, + this.sha256, + this.preview, + this.metadata, + }); + + @override + int get hashCode => + id.hashCode ^ + fileName.hashCode ^ + size.hashCode ^ + fileType.hashCode ^ + sha256.hashCode ^ + preview.hashCode ^ + metadata.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FileDto && + runtimeType == other.runtimeType && + id == other.id && + fileName == other.fileName && + size == other.size && + fileType == other.fileType && + sha256 == other.sha256 && + preview == other.preview && + metadata == other.metadata; +} + +class FileMetadata { + final String? modified; + final String? accessed; + + const FileMetadata({ + this.modified, + this.accessed, + }); + + @override + int get hashCode => modified.hashCode ^ accessed.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FileMetadata && + runtimeType == other.runtimeType && + modified == other.modified && + accessed == other.accessed; +} diff --git a/app/lib/rust/api/webrtc.dart b/app/lib/rust/api/webrtc.dart new file mode 100644 index 00000000..83f79561 --- /dev/null +++ b/app/lib/rust/api/webrtc.dart @@ -0,0 +1,352 @@ +// 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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; +import 'package:localsend_app/rust/api/model.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; +import 'package:uuid/uuid.dart'; + +part 'webrtc.freezed.dart'; + +// These functions are ignored because they are not marked as `pub`: `sign` + +Stream connect( + {required String uri, + required ProposingClientInfo info, + required String privateKey, + required FutureOr Function(LsSignalingConnection) + onConnection}) => + RustLib.instance.api.crateApiWebrtcConnect( + uri: uri, + info: info, + privateKey: privateKey, + onConnection: onConnection); + +// Rust type: RustOpaqueMoi> +abstract class LsSignalingConnection implements RustOpaqueInterface { + Future acceptOffer( + {required List stunServers, + required WsServerSdpMessage offer, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin}); + + Future sendOffer( + {required List stunServers, + required UuidValue target, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin, + required List files}); + + Future updateInfo({required ClientInfoWithoutId info}); +} + +// Rust type: RustOpaqueMoi> +abstract class RtcFileReceiver implements RustOpaqueInterface { + Future getFileId(); + + Stream receive(); +} + +// Rust type: RustOpaqueMoi> +abstract class RtcFileSender implements RustOpaqueInterface { + Future send({required List data}); +} + +// Rust type: RustOpaqueMoi> +abstract class RtcReceiveController implements RustOpaqueInterface { + Future decline(); + + Stream listenError(); + + Future> listenFiles(); + + Stream listenReceiving(); + + Stream listenStatus(); + + Future sendFileStatus({required RTCSendFileResponse status}); + + Future sendPin({required String pin}); + + Future sendSelection({required Set selection}); +} + +// Rust type: RustOpaqueMoi> +abstract class RtcSendController implements RustOpaqueInterface { + Stream listenError(); + + Future> listenSelectedFiles(); + + Stream listenStatus(); + + Future sendFile({required String fileId}); + + Future sendPin({required String pin}); +} + +class ClientInfo { + final UuidValue id; + final String alias; + final String version; + final String? deviceModel; + final DeviceType? deviceType; + final String token; + + const ClientInfo({ + required this.id, + required this.alias, + required this.version, + this.deviceModel, + this.deviceType, + required this.token, + }); + + @override + int get hashCode => + id.hashCode ^ + alias.hashCode ^ + version.hashCode ^ + deviceModel.hashCode ^ + deviceType.hashCode ^ + token.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ClientInfo && + runtimeType == other.runtimeType && + id == other.id && + alias == other.alias && + version == other.version && + deviceModel == other.deviceModel && + deviceType == other.deviceType && + token == other.token; +} + +class ClientInfoWithoutId { + final String alias; + final String version; + final String? deviceModel; + final DeviceType? deviceType; + final String token; + + const ClientInfoWithoutId({ + required this.alias, + required this.version, + this.deviceModel, + this.deviceType, + required this.token, + }); + + @override + int get hashCode => + alias.hashCode ^ + version.hashCode ^ + deviceModel.hashCode ^ + deviceType.hashCode ^ + token.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ClientInfoWithoutId && + runtimeType == other.runtimeType && + alias == other.alias && + version == other.version && + deviceModel == other.deviceModel && + deviceType == other.deviceType && + token == other.token; +} + +class ExpectingPublicKey { + final String publicKey; + + /// "ed25519" or "rsa-pss" + final String kind; + + const ExpectingPublicKey({ + required this.publicKey, + required this.kind, + }); + + @override + int get hashCode => publicKey.hashCode ^ kind.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ExpectingPublicKey && + runtimeType == other.runtimeType && + publicKey == other.publicKey && + kind == other.kind; +} + +class PinConfig { + final String pin; + final int maxTries; + + const PinConfig({ + required this.pin, + required this.maxTries, + }); + + @override + int get hashCode => pin.hashCode ^ maxTries.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PinConfig && + runtimeType == other.runtimeType && + pin == other.pin && + maxTries == other.maxTries; +} + +class ProposingClientInfo { + final String alias; + final String version; + final String? deviceModel; + final DeviceType? deviceType; + + const ProposingClientInfo({ + required this.alias, + required this.version, + this.deviceModel, + this.deviceType, + }); + + @override + int get hashCode => + alias.hashCode ^ + version.hashCode ^ + deviceModel.hashCode ^ + deviceType.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProposingClientInfo && + runtimeType == other.runtimeType && + alias == other.alias && + version == other.version && + deviceModel == other.deviceModel && + deviceType == other.deviceType; +} + +class RTCFileError { + final String fileId; + final String error; + + const RTCFileError({ + required this.fileId, + required this.error, + }); + + @override + int get hashCode => fileId.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RTCFileError && + runtimeType == other.runtimeType && + fileId == other.fileId && + error == other.error; +} + +class RTCSendFileResponse { + final String id; + final bool success; + final String? error; + + const RTCSendFileResponse({ + required this.id, + required this.success, + this.error, + }); + + @override + int get hashCode => id.hashCode ^ success.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RTCSendFileResponse && + runtimeType == other.runtimeType && + id == other.id && + success == other.success && + error == other.error; +} + +@freezed +sealed class RTCStatus with _$RTCStatus { + const RTCStatus._(); + + const factory RTCStatus.sdpExchanged() = RTCStatus_SdpExchanged; + const factory RTCStatus.connected() = RTCStatus_Connected; + const factory RTCStatus.pinRequired() = RTCStatus_PinRequired; + const factory RTCStatus.tooManyAttempts() = RTCStatus_TooManyAttempts; + const factory RTCStatus.declined() = RTCStatus_Declined; + const factory RTCStatus.sending() = RTCStatus_Sending; + const factory RTCStatus.finished() = RTCStatus_Finished; + const factory RTCStatus.error( + String field0, + ) = RTCStatus_Error; +} + +@freezed +sealed class WsServerMessage with _$WsServerMessage { + const WsServerMessage._(); + + const factory WsServerMessage.hello({ + required ClientInfo client, + required List peers, + }) = WsServerMessage_Hello; + const factory WsServerMessage.join({ + required ClientInfo peer, + }) = WsServerMessage_Join; + const factory WsServerMessage.update({ + required ClientInfo peer, + }) = WsServerMessage_Update; + const factory WsServerMessage.left({ + required UuidValue peerId, + }) = WsServerMessage_Left; + const factory WsServerMessage.offer( + WsServerSdpMessage field0, + ) = WsServerMessage_Offer; + const factory WsServerMessage.answer( + WsServerSdpMessage field0, + ) = WsServerMessage_Answer; + const factory WsServerMessage.error({ + required int code, + }) = WsServerMessage_Error; +} + +class WsServerSdpMessage { + final ClientInfo peer; + final String sessionId; + final String sdp; + + const WsServerSdpMessage({ + required this.peer, + required this.sessionId, + required this.sdp, + }); + + @override + int get hashCode => peer.hashCode ^ sessionId.hashCode ^ sdp.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WsServerSdpMessage && + runtimeType == other.runtimeType && + peer == other.peer && + sessionId == other.sessionId && + sdp == other.sdp; +} diff --git a/app/lib/rust/api/webrtc.freezed.dart b/app/lib/rust/api/webrtc.freezed.dart new file mode 100644 index 00000000..4fa27549 --- /dev/null +++ b/app/lib/rust/api/webrtc.freezed.dart @@ -0,0 +1,2626 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'webrtc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$RTCStatus { + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RTCStatusCopyWith<$Res> { + factory $RTCStatusCopyWith(RTCStatus value, $Res Function(RTCStatus) then) = + _$RTCStatusCopyWithImpl<$Res, RTCStatus>; +} + +/// @nodoc +class _$RTCStatusCopyWithImpl<$Res, $Val extends RTCStatus> + implements $RTCStatusCopyWith<$Res> { + _$RTCStatusCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$RTCStatus_SdpExchangedImplCopyWith<$Res> { + factory _$$RTCStatus_SdpExchangedImplCopyWith( + _$RTCStatus_SdpExchangedImpl value, + $Res Function(_$RTCStatus_SdpExchangedImpl) then) = + __$$RTCStatus_SdpExchangedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_SdpExchangedImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_SdpExchangedImpl> + implements _$$RTCStatus_SdpExchangedImplCopyWith<$Res> { + __$$RTCStatus_SdpExchangedImplCopyWithImpl( + _$RTCStatus_SdpExchangedImpl _value, + $Res Function(_$RTCStatus_SdpExchangedImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_SdpExchangedImpl extends RTCStatus_SdpExchanged { + const _$RTCStatus_SdpExchangedImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.sdpExchanged()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RTCStatus_SdpExchangedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return sdpExchanged(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return sdpExchanged?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (sdpExchanged != null) { + return sdpExchanged(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return sdpExchanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return sdpExchanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (sdpExchanged != null) { + return sdpExchanged(this); + } + return orElse(); + } +} + +abstract class RTCStatus_SdpExchanged extends RTCStatus { + const factory RTCStatus_SdpExchanged() = _$RTCStatus_SdpExchangedImpl; + const RTCStatus_SdpExchanged._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_ConnectedImplCopyWith<$Res> { + factory _$$RTCStatus_ConnectedImplCopyWith(_$RTCStatus_ConnectedImpl value, + $Res Function(_$RTCStatus_ConnectedImpl) then) = + __$$RTCStatus_ConnectedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_ConnectedImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_ConnectedImpl> + implements _$$RTCStatus_ConnectedImplCopyWith<$Res> { + __$$RTCStatus_ConnectedImplCopyWithImpl(_$RTCStatus_ConnectedImpl _value, + $Res Function(_$RTCStatus_ConnectedImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_ConnectedImpl extends RTCStatus_Connected { + const _$RTCStatus_ConnectedImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.connected()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RTCStatus_ConnectedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return connected(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return connected?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (connected != null) { + return connected(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return connected(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return connected?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (connected != null) { + return connected(this); + } + return orElse(); + } +} + +abstract class RTCStatus_Connected extends RTCStatus { + const factory RTCStatus_Connected() = _$RTCStatus_ConnectedImpl; + const RTCStatus_Connected._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_PinRequiredImplCopyWith<$Res> { + factory _$$RTCStatus_PinRequiredImplCopyWith( + _$RTCStatus_PinRequiredImpl value, + $Res Function(_$RTCStatus_PinRequiredImpl) then) = + __$$RTCStatus_PinRequiredImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_PinRequiredImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_PinRequiredImpl> + implements _$$RTCStatus_PinRequiredImplCopyWith<$Res> { + __$$RTCStatus_PinRequiredImplCopyWithImpl(_$RTCStatus_PinRequiredImpl _value, + $Res Function(_$RTCStatus_PinRequiredImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_PinRequiredImpl extends RTCStatus_PinRequired { + const _$RTCStatus_PinRequiredImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.pinRequired()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RTCStatus_PinRequiredImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return pinRequired(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return pinRequired?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (pinRequired != null) { + return pinRequired(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return pinRequired(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return pinRequired?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (pinRequired != null) { + return pinRequired(this); + } + return orElse(); + } +} + +abstract class RTCStatus_PinRequired extends RTCStatus { + const factory RTCStatus_PinRequired() = _$RTCStatus_PinRequiredImpl; + const RTCStatus_PinRequired._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_TooManyAttemptsImplCopyWith<$Res> { + factory _$$RTCStatus_TooManyAttemptsImplCopyWith( + _$RTCStatus_TooManyAttemptsImpl value, + $Res Function(_$RTCStatus_TooManyAttemptsImpl) then) = + __$$RTCStatus_TooManyAttemptsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_TooManyAttemptsImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_TooManyAttemptsImpl> + implements _$$RTCStatus_TooManyAttemptsImplCopyWith<$Res> { + __$$RTCStatus_TooManyAttemptsImplCopyWithImpl( + _$RTCStatus_TooManyAttemptsImpl _value, + $Res Function(_$RTCStatus_TooManyAttemptsImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_TooManyAttemptsImpl extends RTCStatus_TooManyAttempts { + const _$RTCStatus_TooManyAttemptsImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.tooManyAttempts()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RTCStatus_TooManyAttemptsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return tooManyAttempts(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return tooManyAttempts?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (tooManyAttempts != null) { + return tooManyAttempts(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return tooManyAttempts(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return tooManyAttempts?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (tooManyAttempts != null) { + return tooManyAttempts(this); + } + return orElse(); + } +} + +abstract class RTCStatus_TooManyAttempts extends RTCStatus { + const factory RTCStatus_TooManyAttempts() = _$RTCStatus_TooManyAttemptsImpl; + const RTCStatus_TooManyAttempts._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_DeclinedImplCopyWith<$Res> { + factory _$$RTCStatus_DeclinedImplCopyWith(_$RTCStatus_DeclinedImpl value, + $Res Function(_$RTCStatus_DeclinedImpl) then) = + __$$RTCStatus_DeclinedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_DeclinedImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_DeclinedImpl> + implements _$$RTCStatus_DeclinedImplCopyWith<$Res> { + __$$RTCStatus_DeclinedImplCopyWithImpl(_$RTCStatus_DeclinedImpl _value, + $Res Function(_$RTCStatus_DeclinedImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_DeclinedImpl extends RTCStatus_Declined { + const _$RTCStatus_DeclinedImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.declined()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RTCStatus_DeclinedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return declined(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return declined?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (declined != null) { + return declined(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return declined(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return declined?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (declined != null) { + return declined(this); + } + return orElse(); + } +} + +abstract class RTCStatus_Declined extends RTCStatus { + const factory RTCStatus_Declined() = _$RTCStatus_DeclinedImpl; + const RTCStatus_Declined._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_SendingImplCopyWith<$Res> { + factory _$$RTCStatus_SendingImplCopyWith(_$RTCStatus_SendingImpl value, + $Res Function(_$RTCStatus_SendingImpl) then) = + __$$RTCStatus_SendingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_SendingImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_SendingImpl> + implements _$$RTCStatus_SendingImplCopyWith<$Res> { + __$$RTCStatus_SendingImplCopyWithImpl(_$RTCStatus_SendingImpl _value, + $Res Function(_$RTCStatus_SendingImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_SendingImpl extends RTCStatus_Sending { + const _$RTCStatus_SendingImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.sending()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RTCStatus_SendingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return sending(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return sending?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (sending != null) { + return sending(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return sending(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return sending?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (sending != null) { + return sending(this); + } + return orElse(); + } +} + +abstract class RTCStatus_Sending extends RTCStatus { + const factory RTCStatus_Sending() = _$RTCStatus_SendingImpl; + const RTCStatus_Sending._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_FinishedImplCopyWith<$Res> { + factory _$$RTCStatus_FinishedImplCopyWith(_$RTCStatus_FinishedImpl value, + $Res Function(_$RTCStatus_FinishedImpl) then) = + __$$RTCStatus_FinishedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RTCStatus_FinishedImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_FinishedImpl> + implements _$$RTCStatus_FinishedImplCopyWith<$Res> { + __$$RTCStatus_FinishedImplCopyWithImpl(_$RTCStatus_FinishedImpl _value, + $Res Function(_$RTCStatus_FinishedImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RTCStatus_FinishedImpl extends RTCStatus_Finished { + const _$RTCStatus_FinishedImpl() : super._(); + + @override + String toString() { + return 'RTCStatus.finished()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RTCStatus_FinishedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return finished(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return finished?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (finished != null) { + return finished(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return finished(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return finished?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (finished != null) { + return finished(this); + } + return orElse(); + } +} + +abstract class RTCStatus_Finished extends RTCStatus { + const factory RTCStatus_Finished() = _$RTCStatus_FinishedImpl; + const RTCStatus_Finished._() : super._(); +} + +/// @nodoc +abstract class _$$RTCStatus_ErrorImplCopyWith<$Res> { + factory _$$RTCStatus_ErrorImplCopyWith(_$RTCStatus_ErrorImpl value, + $Res Function(_$RTCStatus_ErrorImpl) then) = + __$$RTCStatus_ErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String field0}); +} + +/// @nodoc +class __$$RTCStatus_ErrorImplCopyWithImpl<$Res> + extends _$RTCStatusCopyWithImpl<$Res, _$RTCStatus_ErrorImpl> + implements _$$RTCStatus_ErrorImplCopyWith<$Res> { + __$$RTCStatus_ErrorImplCopyWithImpl( + _$RTCStatus_ErrorImpl _value, $Res Function(_$RTCStatus_ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? field0 = null, + }) { + return _then(_$RTCStatus_ErrorImpl( + null == field0 + ? _value.field0 + : field0 // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$RTCStatus_ErrorImpl extends RTCStatus_Error { + const _$RTCStatus_ErrorImpl(this.field0) : super._(); + + @override + final String field0; + + @override + String toString() { + return 'RTCStatus.error(field0: $field0)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RTCStatus_ErrorImpl && + (identical(other.field0, field0) || other.field0 == field0)); + } + + @override + int get hashCode => Object.hash(runtimeType, field0); + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RTCStatus_ErrorImplCopyWith<_$RTCStatus_ErrorImpl> get copyWith => + __$$RTCStatus_ErrorImplCopyWithImpl<_$RTCStatus_ErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sdpExchanged, + required TResult Function() connected, + required TResult Function() pinRequired, + required TResult Function() tooManyAttempts, + required TResult Function() declined, + required TResult Function() sending, + required TResult Function() finished, + required TResult Function(String field0) error, + }) { + return error(field0); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sdpExchanged, + TResult? Function()? connected, + TResult? Function()? pinRequired, + TResult? Function()? tooManyAttempts, + TResult? Function()? declined, + TResult? Function()? sending, + TResult? Function()? finished, + TResult? Function(String field0)? error, + }) { + return error?.call(field0); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sdpExchanged, + TResult Function()? connected, + TResult Function()? pinRequired, + TResult Function()? tooManyAttempts, + TResult Function()? declined, + TResult Function()? sending, + TResult Function()? finished, + TResult Function(String field0)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(field0); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(RTCStatus_SdpExchanged value) sdpExchanged, + required TResult Function(RTCStatus_Connected value) connected, + required TResult Function(RTCStatus_PinRequired value) pinRequired, + required TResult Function(RTCStatus_TooManyAttempts value) tooManyAttempts, + required TResult Function(RTCStatus_Declined value) declined, + required TResult Function(RTCStatus_Sending value) sending, + required TResult Function(RTCStatus_Finished value) finished, + required TResult Function(RTCStatus_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult? Function(RTCStatus_Connected value)? connected, + TResult? Function(RTCStatus_PinRequired value)? pinRequired, + TResult? Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult? Function(RTCStatus_Declined value)? declined, + TResult? Function(RTCStatus_Sending value)? sending, + TResult? Function(RTCStatus_Finished value)? finished, + TResult? Function(RTCStatus_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(RTCStatus_SdpExchanged value)? sdpExchanged, + TResult Function(RTCStatus_Connected value)? connected, + TResult Function(RTCStatus_PinRequired value)? pinRequired, + TResult Function(RTCStatus_TooManyAttempts value)? tooManyAttempts, + TResult Function(RTCStatus_Declined value)? declined, + TResult Function(RTCStatus_Sending value)? sending, + TResult Function(RTCStatus_Finished value)? finished, + TResult Function(RTCStatus_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class RTCStatus_Error extends RTCStatus { + const factory RTCStatus_Error(final String field0) = _$RTCStatus_ErrorImpl; + const RTCStatus_Error._() : super._(); + + String get field0; + + /// Create a copy of RTCStatus + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RTCStatus_ErrorImplCopyWith<_$RTCStatus_ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$WsServerMessage { + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WsServerMessageCopyWith<$Res> { + factory $WsServerMessageCopyWith( + WsServerMessage value, $Res Function(WsServerMessage) then) = + _$WsServerMessageCopyWithImpl<$Res, WsServerMessage>; +} + +/// @nodoc +class _$WsServerMessageCopyWithImpl<$Res, $Val extends WsServerMessage> + implements $WsServerMessageCopyWith<$Res> { + _$WsServerMessageCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$WsServerMessage_HelloImplCopyWith<$Res> { + factory _$$WsServerMessage_HelloImplCopyWith( + _$WsServerMessage_HelloImpl value, + $Res Function(_$WsServerMessage_HelloImpl) then) = + __$$WsServerMessage_HelloImplCopyWithImpl<$Res>; + @useResult + $Res call({ClientInfo client, List peers}); +} + +/// @nodoc +class __$$WsServerMessage_HelloImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_HelloImpl> + implements _$$WsServerMessage_HelloImplCopyWith<$Res> { + __$$WsServerMessage_HelloImplCopyWithImpl(_$WsServerMessage_HelloImpl _value, + $Res Function(_$WsServerMessage_HelloImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? client = null, + Object? peers = null, + }) { + return _then(_$WsServerMessage_HelloImpl( + client: null == client + ? _value.client + : client // ignore: cast_nullable_to_non_nullable + as ClientInfo, + peers: null == peers + ? _value._peers + : peers // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_HelloImpl extends WsServerMessage_Hello { + const _$WsServerMessage_HelloImpl( + {required this.client, required final List peers}) + : _peers = peers, + super._(); + + @override + final ClientInfo client; + final List _peers; + @override + List get peers { + if (_peers is EqualUnmodifiableListView) return _peers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_peers); + } + + @override + String toString() { + return 'WsServerMessage.hello(client: $client, peers: $peers)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_HelloImpl && + (identical(other.client, client) || other.client == client) && + const DeepCollectionEquality().equals(other._peers, _peers)); + } + + @override + int get hashCode => Object.hash( + runtimeType, client, const DeepCollectionEquality().hash(_peers)); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_HelloImplCopyWith<_$WsServerMessage_HelloImpl> + get copyWith => __$$WsServerMessage_HelloImplCopyWithImpl< + _$WsServerMessage_HelloImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return hello(client, peers); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return hello?.call(client, peers); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (hello != null) { + return hello(client, peers); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return hello(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return hello?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (hello != null) { + return hello(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Hello extends WsServerMessage { + const factory WsServerMessage_Hello( + {required final ClientInfo client, + required final List peers}) = _$WsServerMessage_HelloImpl; + const WsServerMessage_Hello._() : super._(); + + ClientInfo get client; + List get peers; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_HelloImplCopyWith<_$WsServerMessage_HelloImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_JoinImplCopyWith<$Res> { + factory _$$WsServerMessage_JoinImplCopyWith(_$WsServerMessage_JoinImpl value, + $Res Function(_$WsServerMessage_JoinImpl) then) = + __$$WsServerMessage_JoinImplCopyWithImpl<$Res>; + @useResult + $Res call({ClientInfo peer}); +} + +/// @nodoc +class __$$WsServerMessage_JoinImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_JoinImpl> + implements _$$WsServerMessage_JoinImplCopyWith<$Res> { + __$$WsServerMessage_JoinImplCopyWithImpl(_$WsServerMessage_JoinImpl _value, + $Res Function(_$WsServerMessage_JoinImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? peer = null, + }) { + return _then(_$WsServerMessage_JoinImpl( + peer: null == peer + ? _value.peer + : peer // ignore: cast_nullable_to_non_nullable + as ClientInfo, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_JoinImpl extends WsServerMessage_Join { + const _$WsServerMessage_JoinImpl({required this.peer}) : super._(); + + @override + final ClientInfo peer; + + @override + String toString() { + return 'WsServerMessage.join(peer: $peer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_JoinImpl && + (identical(other.peer, peer) || other.peer == peer)); + } + + @override + int get hashCode => Object.hash(runtimeType, peer); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_JoinImplCopyWith<_$WsServerMessage_JoinImpl> + get copyWith => + __$$WsServerMessage_JoinImplCopyWithImpl<_$WsServerMessage_JoinImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return join(peer); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return join?.call(peer); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (join != null) { + return join(peer); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return join(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return join?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (join != null) { + return join(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Join extends WsServerMessage { + const factory WsServerMessage_Join({required final ClientInfo peer}) = + _$WsServerMessage_JoinImpl; + const WsServerMessage_Join._() : super._(); + + ClientInfo get peer; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_JoinImplCopyWith<_$WsServerMessage_JoinImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_UpdateImplCopyWith<$Res> { + factory _$$WsServerMessage_UpdateImplCopyWith( + _$WsServerMessage_UpdateImpl value, + $Res Function(_$WsServerMessage_UpdateImpl) then) = + __$$WsServerMessage_UpdateImplCopyWithImpl<$Res>; + @useResult + $Res call({ClientInfo peer}); +} + +/// @nodoc +class __$$WsServerMessage_UpdateImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_UpdateImpl> + implements _$$WsServerMessage_UpdateImplCopyWith<$Res> { + __$$WsServerMessage_UpdateImplCopyWithImpl( + _$WsServerMessage_UpdateImpl _value, + $Res Function(_$WsServerMessage_UpdateImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? peer = null, + }) { + return _then(_$WsServerMessage_UpdateImpl( + peer: null == peer + ? _value.peer + : peer // ignore: cast_nullable_to_non_nullable + as ClientInfo, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_UpdateImpl extends WsServerMessage_Update { + const _$WsServerMessage_UpdateImpl({required this.peer}) : super._(); + + @override + final ClientInfo peer; + + @override + String toString() { + return 'WsServerMessage.update(peer: $peer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_UpdateImpl && + (identical(other.peer, peer) || other.peer == peer)); + } + + @override + int get hashCode => Object.hash(runtimeType, peer); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_UpdateImplCopyWith<_$WsServerMessage_UpdateImpl> + get copyWith => __$$WsServerMessage_UpdateImplCopyWithImpl< + _$WsServerMessage_UpdateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return update(peer); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return update?.call(peer); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (update != null) { + return update(peer); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return update(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return update?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (update != null) { + return update(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Update extends WsServerMessage { + const factory WsServerMessage_Update({required final ClientInfo peer}) = + _$WsServerMessage_UpdateImpl; + const WsServerMessage_Update._() : super._(); + + ClientInfo get peer; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_UpdateImplCopyWith<_$WsServerMessage_UpdateImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_LeftImplCopyWith<$Res> { + factory _$$WsServerMessage_LeftImplCopyWith(_$WsServerMessage_LeftImpl value, + $Res Function(_$WsServerMessage_LeftImpl) then) = + __$$WsServerMessage_LeftImplCopyWithImpl<$Res>; + @useResult + $Res call({UuidValue peerId}); +} + +/// @nodoc +class __$$WsServerMessage_LeftImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_LeftImpl> + implements _$$WsServerMessage_LeftImplCopyWith<$Res> { + __$$WsServerMessage_LeftImplCopyWithImpl(_$WsServerMessage_LeftImpl _value, + $Res Function(_$WsServerMessage_LeftImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? peerId = null, + }) { + return _then(_$WsServerMessage_LeftImpl( + peerId: null == peerId + ? _value.peerId + : peerId // ignore: cast_nullable_to_non_nullable + as UuidValue, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_LeftImpl extends WsServerMessage_Left { + const _$WsServerMessage_LeftImpl({required this.peerId}) : super._(); + + @override + final UuidValue peerId; + + @override + String toString() { + return 'WsServerMessage.left(peerId: $peerId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_LeftImpl && + (identical(other.peerId, peerId) || other.peerId == peerId)); + } + + @override + int get hashCode => Object.hash(runtimeType, peerId); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_LeftImplCopyWith<_$WsServerMessage_LeftImpl> + get copyWith => + __$$WsServerMessage_LeftImplCopyWithImpl<_$WsServerMessage_LeftImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return left(peerId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return left?.call(peerId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (left != null) { + return left(peerId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return left(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return left?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (left != null) { + return left(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Left extends WsServerMessage { + const factory WsServerMessage_Left({required final UuidValue peerId}) = + _$WsServerMessage_LeftImpl; + const WsServerMessage_Left._() : super._(); + + UuidValue get peerId; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_LeftImplCopyWith<_$WsServerMessage_LeftImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_OfferImplCopyWith<$Res> { + factory _$$WsServerMessage_OfferImplCopyWith( + _$WsServerMessage_OfferImpl value, + $Res Function(_$WsServerMessage_OfferImpl) then) = + __$$WsServerMessage_OfferImplCopyWithImpl<$Res>; + @useResult + $Res call({WsServerSdpMessage field0}); +} + +/// @nodoc +class __$$WsServerMessage_OfferImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_OfferImpl> + implements _$$WsServerMessage_OfferImplCopyWith<$Res> { + __$$WsServerMessage_OfferImplCopyWithImpl(_$WsServerMessage_OfferImpl _value, + $Res Function(_$WsServerMessage_OfferImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? field0 = null, + }) { + return _then(_$WsServerMessage_OfferImpl( + null == field0 + ? _value.field0 + : field0 // ignore: cast_nullable_to_non_nullable + as WsServerSdpMessage, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_OfferImpl extends WsServerMessage_Offer { + const _$WsServerMessage_OfferImpl(this.field0) : super._(); + + @override + final WsServerSdpMessage field0; + + @override + String toString() { + return 'WsServerMessage.offer(field0: $field0)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_OfferImpl && + (identical(other.field0, field0) || other.field0 == field0)); + } + + @override + int get hashCode => Object.hash(runtimeType, field0); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_OfferImplCopyWith<_$WsServerMessage_OfferImpl> + get copyWith => __$$WsServerMessage_OfferImplCopyWithImpl< + _$WsServerMessage_OfferImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return offer(field0); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return offer?.call(field0); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (offer != null) { + return offer(field0); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return offer(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return offer?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (offer != null) { + return offer(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Offer extends WsServerMessage { + const factory WsServerMessage_Offer(final WsServerSdpMessage field0) = + _$WsServerMessage_OfferImpl; + const WsServerMessage_Offer._() : super._(); + + WsServerSdpMessage get field0; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_OfferImplCopyWith<_$WsServerMessage_OfferImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_AnswerImplCopyWith<$Res> { + factory _$$WsServerMessage_AnswerImplCopyWith( + _$WsServerMessage_AnswerImpl value, + $Res Function(_$WsServerMessage_AnswerImpl) then) = + __$$WsServerMessage_AnswerImplCopyWithImpl<$Res>; + @useResult + $Res call({WsServerSdpMessage field0}); +} + +/// @nodoc +class __$$WsServerMessage_AnswerImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_AnswerImpl> + implements _$$WsServerMessage_AnswerImplCopyWith<$Res> { + __$$WsServerMessage_AnswerImplCopyWithImpl( + _$WsServerMessage_AnswerImpl _value, + $Res Function(_$WsServerMessage_AnswerImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? field0 = null, + }) { + return _then(_$WsServerMessage_AnswerImpl( + null == field0 + ? _value.field0 + : field0 // ignore: cast_nullable_to_non_nullable + as WsServerSdpMessage, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_AnswerImpl extends WsServerMessage_Answer { + const _$WsServerMessage_AnswerImpl(this.field0) : super._(); + + @override + final WsServerSdpMessage field0; + + @override + String toString() { + return 'WsServerMessage.answer(field0: $field0)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_AnswerImpl && + (identical(other.field0, field0) || other.field0 == field0)); + } + + @override + int get hashCode => Object.hash(runtimeType, field0); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_AnswerImplCopyWith<_$WsServerMessage_AnswerImpl> + get copyWith => __$$WsServerMessage_AnswerImplCopyWithImpl< + _$WsServerMessage_AnswerImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return answer(field0); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return answer?.call(field0); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (answer != null) { + return answer(field0); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return answer(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return answer?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (answer != null) { + return answer(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Answer extends WsServerMessage { + const factory WsServerMessage_Answer(final WsServerSdpMessage field0) = + _$WsServerMessage_AnswerImpl; + const WsServerMessage_Answer._() : super._(); + + WsServerSdpMessage get field0; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_AnswerImplCopyWith<_$WsServerMessage_AnswerImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$WsServerMessage_ErrorImplCopyWith<$Res> { + factory _$$WsServerMessage_ErrorImplCopyWith( + _$WsServerMessage_ErrorImpl value, + $Res Function(_$WsServerMessage_ErrorImpl) then) = + __$$WsServerMessage_ErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({int code}); +} + +/// @nodoc +class __$$WsServerMessage_ErrorImplCopyWithImpl<$Res> + extends _$WsServerMessageCopyWithImpl<$Res, _$WsServerMessage_ErrorImpl> + implements _$$WsServerMessage_ErrorImplCopyWith<$Res> { + __$$WsServerMessage_ErrorImplCopyWithImpl(_$WsServerMessage_ErrorImpl _value, + $Res Function(_$WsServerMessage_ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? code = null, + }) { + return _then(_$WsServerMessage_ErrorImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$WsServerMessage_ErrorImpl extends WsServerMessage_Error { + const _$WsServerMessage_ErrorImpl({required this.code}) : super._(); + + @override + final int code; + + @override + String toString() { + return 'WsServerMessage.error(code: $code)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WsServerMessage_ErrorImpl && + (identical(other.code, code) || other.code == code)); + } + + @override + int get hashCode => Object.hash(runtimeType, code); + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WsServerMessage_ErrorImplCopyWith<_$WsServerMessage_ErrorImpl> + get copyWith => __$$WsServerMessage_ErrorImplCopyWithImpl< + _$WsServerMessage_ErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ClientInfo client, List peers) hello, + required TResult Function(ClientInfo peer) join, + required TResult Function(ClientInfo peer) update, + required TResult Function(UuidValue peerId) left, + required TResult Function(WsServerSdpMessage field0) offer, + required TResult Function(WsServerSdpMessage field0) answer, + required TResult Function(int code) error, + }) { + return error(code); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ClientInfo client, List peers)? hello, + TResult? Function(ClientInfo peer)? join, + TResult? Function(ClientInfo peer)? update, + TResult? Function(UuidValue peerId)? left, + TResult? Function(WsServerSdpMessage field0)? offer, + TResult? Function(WsServerSdpMessage field0)? answer, + TResult? Function(int code)? error, + }) { + return error?.call(code); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ClientInfo client, List peers)? hello, + TResult Function(ClientInfo peer)? join, + TResult Function(ClientInfo peer)? update, + TResult Function(UuidValue peerId)? left, + TResult Function(WsServerSdpMessage field0)? offer, + TResult Function(WsServerSdpMessage field0)? answer, + TResult Function(int code)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(code); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(WsServerMessage_Hello value) hello, + required TResult Function(WsServerMessage_Join value) join, + required TResult Function(WsServerMessage_Update value) update, + required TResult Function(WsServerMessage_Left value) left, + required TResult Function(WsServerMessage_Offer value) offer, + required TResult Function(WsServerMessage_Answer value) answer, + required TResult Function(WsServerMessage_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(WsServerMessage_Hello value)? hello, + TResult? Function(WsServerMessage_Join value)? join, + TResult? Function(WsServerMessage_Update value)? update, + TResult? Function(WsServerMessage_Left value)? left, + TResult? Function(WsServerMessage_Offer value)? offer, + TResult? Function(WsServerMessage_Answer value)? answer, + TResult? Function(WsServerMessage_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(WsServerMessage_Hello value)? hello, + TResult Function(WsServerMessage_Join value)? join, + TResult Function(WsServerMessage_Update value)? update, + TResult Function(WsServerMessage_Left value)? left, + TResult Function(WsServerMessage_Offer value)? offer, + TResult Function(WsServerMessage_Answer value)? answer, + TResult Function(WsServerMessage_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class WsServerMessage_Error extends WsServerMessage { + const factory WsServerMessage_Error({required final int code}) = + _$WsServerMessage_ErrorImpl; + const WsServerMessage_Error._() : super._(); + + int get code; + + /// Create a copy of WsServerMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WsServerMessage_ErrorImplCopyWith<_$WsServerMessage_ErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/app/lib/rust/frb_generated.dart b/app/lib/rust/frb_generated.dart new file mode 100644 index 00000000..e397c5a6 --- /dev/null +++ b/app/lib/rust/frb_generated.dart @@ -0,0 +1,3079 @@ +// 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 'dart:async'; +import 'dart:convert'; + +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:localsend_app/rust/api/crypto.dart'; +import 'package:localsend_app/rust/api/logging.dart'; +import 'package:localsend_app/rust/api/model.dart'; +import 'package:localsend_app/rust/api/webrtc.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; +import 'package:localsend_app/rust/frb_generated.io.dart' + if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'package:uuid/uuid.dart'; + +/// Main entrypoint of the Rust API +class RustLib extends BaseEntrypoint { + @internal + static final instance = RustLib._(); + + RustLib._(); + + /// Initialize flutter_rust_bridge + static Future init({ + RustLibApi? api, + BaseHandler? handler, + ExternalLibrary? externalLibrary, + bool forceSameCodegenVersion = true, + }) async { + await instance.initImpl( + api: api, + handler: handler, + externalLibrary: externalLibrary, + forceSameCodegenVersion: forceSameCodegenVersion, + ); + } + + /// Initialize flutter_rust_bridge in mock mode. + /// No libraries for FFI are loaded. + static void initMock({ + required RustLibApi api, + }) { + instance.initMockImpl( + api: api, + ); + } + + /// Dispose flutter_rust_bridge + /// + /// The call to this function is optional, since flutter_rust_bridge (and everything else) + /// is automatically disposed when the app stops. + static void dispose() => instance.disposeImpl(); + + @override + ApiImplConstructor get apiImplConstructor => + RustLibApiImpl.new; + + @override + WireConstructor get wireConstructor => + RustLibWire.fromExternalLibrary; + + @override + Future executeRustInitializers() async {} + + @override + ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => + kDefaultExternalLibraryLoaderConfig; + + @override + String get codegenVersion => '2.11.1'; + + @override + int get rustContentHash => 1108156133; + + static const kDefaultExternalLibraryLoaderConfig = + ExternalLibraryLoaderConfig( + stem: 'rust_lib_localsend_app', + ioDirectory: 'rust/target/release/', + webPrefix: 'pkg/', + ); +} + +abstract class RustLibApi extends BaseApi { + Future crateApiWebrtcLsSignalingConnectionAcceptOffer( + {required LsSignalingConnection that, + required List stunServers, + required WsServerSdpMessage offer, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin}); + + Future crateApiWebrtcLsSignalingConnectionSendOffer( + {required LsSignalingConnection that, + required List stunServers, + required UuidValue target, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin, + required List files}); + + Future crateApiWebrtcLsSignalingConnectionUpdateInfo( + {required LsSignalingConnection that, required ClientInfoWithoutId info}); + + Future crateApiWebrtcRtcFileReceiverGetFileId( + {required RtcFileReceiver that}); + + Stream crateApiWebrtcRtcFileReceiverReceive( + {required RtcFileReceiver that}); + + Future crateApiWebrtcRtcFileSenderSend( + {required RtcFileSender that, required List data}); + + Future crateApiWebrtcRtcReceiveControllerDecline( + {required RtcReceiveController that}); + + Stream crateApiWebrtcRtcReceiveControllerListenError( + {required RtcReceiveController that}); + + Future> crateApiWebrtcRtcReceiveControllerListenFiles( + {required RtcReceiveController that}); + + Stream crateApiWebrtcRtcReceiveControllerListenReceiving( + {required RtcReceiveController that}); + + Stream crateApiWebrtcRtcReceiveControllerListenStatus( + {required RtcReceiveController that}); + + Future crateApiWebrtcRtcReceiveControllerSendFileStatus( + {required RtcReceiveController that, + required RTCSendFileResponse status}); + + Future crateApiWebrtcRtcReceiveControllerSendPin( + {required RtcReceiveController that, required String pin}); + + Future crateApiWebrtcRtcReceiveControllerSendSelection( + {required RtcReceiveController that, required Set selection}); + + Stream crateApiWebrtcRtcSendControllerListenError( + {required RtcSendController that}); + + Future> crateApiWebrtcRtcSendControllerListenSelectedFiles( + {required RtcSendController that}); + + Stream crateApiWebrtcRtcSendControllerListenStatus( + {required RtcSendController that}); + + Future crateApiWebrtcRtcSendControllerSendFile( + {required RtcSendController that, required String fileId}); + + Future crateApiWebrtcRtcSendControllerSendPin( + {required RtcSendController that, required String pin}); + + Stream crateApiWebrtcConnect( + {required String uri, + required ProposingClientInfo info, + required String privateKey, + required FutureOr Function(LsSignalingConnection) onConnection}); + + Future crateApiLoggingEnableDebugLogging(); + + Future crateApiCryptoGenerateKeyPair(); + + Future crateApiCryptoVerifyCert( + {required String cert, required String publicKey}); + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_LsSignalingConnection; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_LsSignalingConnection; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_LsSignalingConnectionPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcFileReceiver; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcFileReceiver; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileReceiverPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcFileSender; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcFileSender; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileSenderPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcReceiveController; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcReceiveController; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcReceiveControllerPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcSendController; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcSendController; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcSendControllerPtr; +} + +class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { + RustLibApiImpl({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @override + Future crateApiWebrtcLsSignalingConnectionAcceptOffer( + {required LsSignalingConnection that, + required List stunServers, + required WsServerSdpMessage offer, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + that, serializer); + sse_encode_list_String(stunServers, serializer); + sse_encode_box_autoadd_ws_server_sdp_message(offer, serializer); + sse_encode_String(privateKey, serializer); + sse_encode_opt_box_autoadd_expecting_public_key( + expectingPublicKey, serializer); + sse_encode_opt_box_autoadd_pin_config(pin, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 1, port: port_); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcLsSignalingConnectionAcceptOfferConstMeta, + argValues: [ + that, + stunServers, + offer, + privateKey, + expectingPublicKey, + pin + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcLsSignalingConnectionAcceptOfferConstMeta => + const TaskConstMeta( + debugName: 'LsSignalingConnection_accept_offer', + argNames: [ + 'that', + 'stunServers', + 'offer', + 'privateKey', + 'expectingPublicKey', + 'pin' + ], + ); + + @override + Future crateApiWebrtcLsSignalingConnectionSendOffer( + {required LsSignalingConnection that, + required List stunServers, + required UuidValue target, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin, + required List files}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + that, serializer); + sse_encode_list_String(stunServers, serializer); + sse_encode_Uuid(target, serializer); + sse_encode_String(privateKey, serializer); + sse_encode_opt_box_autoadd_expecting_public_key( + expectingPublicKey, serializer); + sse_encode_opt_box_autoadd_pin_config(pin, serializer); + sse_encode_list_file_dto(files, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 2, port: port_); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcLsSignalingConnectionSendOfferConstMeta, + argValues: [ + that, + stunServers, + target, + privateKey, + expectingPublicKey, + pin, + files + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcLsSignalingConnectionSendOfferConstMeta => + const TaskConstMeta( + debugName: 'LsSignalingConnection_send_offer', + argNames: [ + 'that', + 'stunServers', + 'target', + 'privateKey', + 'expectingPublicKey', + 'pin', + 'files' + ], + ); + + @override + Future crateApiWebrtcLsSignalingConnectionUpdateInfo( + {required LsSignalingConnection that, + required ClientInfoWithoutId info}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + that, serializer); + sse_encode_box_autoadd_client_info_without_id(info, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 3, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcLsSignalingConnectionUpdateInfoConstMeta, + argValues: [that, info], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcLsSignalingConnectionUpdateInfoConstMeta => + const TaskConstMeta( + debugName: 'LsSignalingConnection_update_info', + argNames: ['that', 'info'], + ); + + @override + Future crateApiWebrtcRtcFileReceiverGetFileId( + {required RtcFileReceiver that}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + that, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 4, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcFileReceiverGetFileIdConstMeta, + argValues: [that], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcFileReceiverGetFileIdConstMeta => + const TaskConstMeta( + debugName: 'RtcFileReceiver_get_file_id', + argNames: ['that'], + ); + + @override + Stream crateApiWebrtcRtcFileReceiverReceive( + {required RtcFileReceiver that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + that, serializer); + sse_encode_StreamSink_list_prim_u_8_strict_Sse(sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 5, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcFileReceiverReceiveConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcRtcFileReceiverReceiveConstMeta => + const TaskConstMeta( + debugName: 'RtcFileReceiver_receive', + argNames: ['that', 'sink'], + ); + + @override + Future crateApiWebrtcRtcFileSenderSend( + {required RtcFileSender that, required List data}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + that, serializer); + sse_encode_list_prim_u_8_loose(data, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 6, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcFileSenderSendConstMeta, + argValues: [that, data], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcFileSenderSendConstMeta => + const TaskConstMeta( + debugName: 'RtcFileSender_send', + argNames: ['that', 'data'], + ); + + @override + Future crateApiWebrtcRtcReceiveControllerDecline( + {required RtcReceiveController that}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 7, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerDeclineConstMeta, + argValues: [that], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerDeclineConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_decline', + argNames: ['that'], + ); + + @override + Stream crateApiWebrtcRtcReceiveControllerListenError( + {required RtcReceiveController that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_StreamSink_rtc_file_error_Sse(sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 8, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerListenErrorConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerListenErrorConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_listen_error', + argNames: ['that', 'sink'], + ); + + @override + Future> crateApiWebrtcRtcReceiveControllerListenFiles( + {required RtcReceiveController that}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 9, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_file_dto, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerListenFilesConstMeta, + argValues: [that], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerListenFilesConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_listen_files', + argNames: ['that'], + ); + + @override + Stream crateApiWebrtcRtcReceiveControllerListenReceiving( + {required RtcReceiveController that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 10, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerListenReceivingConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta + get kCrateApiWebrtcRtcReceiveControllerListenReceivingConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_listen_receiving', + argNames: ['that', 'sink'], + ); + + @override + Stream crateApiWebrtcRtcReceiveControllerListenStatus( + {required RtcReceiveController that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_StreamSink_rtc_status_Sse(sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 11, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerListenStatusConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerListenStatusConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_listen_status', + argNames: ['that', 'sink'], + ); + + @override + Future crateApiWebrtcRtcReceiveControllerSendFileStatus( + {required RtcReceiveController that, + required RTCSendFileResponse status}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_box_autoadd_rtc_send_file_response(status, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 12, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerSendFileStatusConstMeta, + argValues: [that, status], + apiImpl: this, + )); + } + + TaskConstMeta + get kCrateApiWebrtcRtcReceiveControllerSendFileStatusConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_send_file_status', + argNames: ['that', 'status'], + ); + + @override + Future crateApiWebrtcRtcReceiveControllerSendPin( + {required RtcReceiveController that, required String pin}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_String(pin, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 13, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerSendPinConstMeta, + argValues: [that, pin], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerSendPinConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_send_pin', + argNames: ['that', 'pin'], + ); + + @override + Future crateApiWebrtcRtcReceiveControllerSendSelection( + {required RtcReceiveController that, required Set selection}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + that, serializer); + sse_encode_Set_String_None(selection, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 14, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcReceiveControllerSendSelectionConstMeta, + argValues: [that, selection], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcReceiveControllerSendSelectionConstMeta => + const TaskConstMeta( + debugName: 'RtcReceiveController_send_selection', + argNames: ['that', 'selection'], + ); + + @override + Stream crateApiWebrtcRtcSendControllerListenError( + {required RtcSendController that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + that, serializer); + sse_encode_StreamSink_rtc_file_error_Sse(sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 15, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcSendControllerListenErrorConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcRtcSendControllerListenErrorConstMeta => + const TaskConstMeta( + debugName: 'RtcSendController_listen_error', + argNames: ['that', 'sink'], + ); + + @override + Future> crateApiWebrtcRtcSendControllerListenSelectedFiles( + {required RtcSendController that}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + that, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 16, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_Set_String_None, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcSendControllerListenSelectedFilesConstMeta, + argValues: [that], + apiImpl: this, + )); + } + + TaskConstMeta + get kCrateApiWebrtcRtcSendControllerListenSelectedFilesConstMeta => + const TaskConstMeta( + debugName: 'RtcSendController_listen_selected_files', + argNames: ['that'], + ); + + @override + Stream crateApiWebrtcRtcSendControllerListenStatus( + {required RtcSendController that}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + that, serializer); + sse_encode_StreamSink_rtc_status_Sse(sink, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 17, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcRtcSendControllerListenStatusConstMeta, + argValues: [that, sink], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcRtcSendControllerListenStatusConstMeta => + const TaskConstMeta( + debugName: 'RtcSendController_listen_status', + argNames: ['that', 'sink'], + ); + + @override + Future crateApiWebrtcRtcSendControllerSendFile( + {required RtcSendController that, required String fileId}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + that, serializer); + sse_encode_String(fileId, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 18, port: port_); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcSendControllerSendFileConstMeta, + argValues: [that, fileId], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcSendControllerSendFileConstMeta => + const TaskConstMeta( + debugName: 'RtcSendController_send_file', + argNames: ['that', 'fileId'], + ); + + @override + Future crateApiWebrtcRtcSendControllerSendPin( + {required RtcSendController that, required String pin}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + that, serializer); + sse_encode_String(pin, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 19, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiWebrtcRtcSendControllerSendPinConstMeta, + argValues: [that, pin], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiWebrtcRtcSendControllerSendPinConstMeta => + const TaskConstMeta( + debugName: 'RtcSendController_send_pin', + argNames: ['that', 'pin'], + ); + + @override + Stream crateApiWebrtcConnect( + {required String uri, + required ProposingClientInfo info, + required String privateKey, + required FutureOr Function(LsSignalingConnection) onConnection}) { + final sink = RustStreamSink(); + unawaited(handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_StreamSink_ws_server_message_Sse(sink, serializer); + sse_encode_String(uri, serializer); + sse_encode_box_autoadd_proposing_client_info(info, serializer); + sse_encode_String(privateKey, serializer); + sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + onConnection, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 20, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiWebrtcConnectConstMeta, + argValues: [sink, uri, info, privateKey, onConnection], + apiImpl: this, + ))); + return sink.stream; + } + + TaskConstMeta get kCrateApiWebrtcConnectConstMeta => const TaskConstMeta( + debugName: 'connect', + argNames: ['sink', 'uri', 'info', 'privateKey', 'onConnection'], + ); + + @override + Future crateApiLoggingEnableDebugLogging() { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 21, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiLoggingEnableDebugLoggingConstMeta, + argValues: [], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiLoggingEnableDebugLoggingConstMeta => + const TaskConstMeta( + debugName: 'enable_debug_logging', + argNames: [], + ); + + @override + Future crateApiCryptoGenerateKeyPair() { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 22, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_key_pair, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiCryptoGenerateKeyPairConstMeta, + argValues: [], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiCryptoGenerateKeyPairConstMeta => + const TaskConstMeta( + debugName: 'generate_key_pair', + argNames: [], + ); + + @override + Future crateApiCryptoVerifyCert( + {required String cert, required String publicKey}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(cert, serializer); + sse_encode_String(publicKey, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 23, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiCryptoVerifyCertConstMeta, + argValues: [cert, publicKey], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiCryptoVerifyCertConstMeta => const TaskConstMeta( + debugName: 'verify_cert', + argNames: ['cert', 'publicKey'], + ); + + Future Function(int, dynamic) + encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + FutureOr Function(LsSignalingConnection) raw) { + return (callId, rawArg0) async { + final arg0 = + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException('$e\n\n$s')); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen); + }; + } + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_LsSignalingConnection => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_LsSignalingConnection => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcFileReceiver => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcFileReceiver => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcFileSender => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcFileSender => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcReceiveController => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcReceiveController => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_RtcSendController => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_RtcSendController => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnyhowException(raw as String); + } + + @protected + LsSignalingConnection + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileReceiver + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileSender + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcReceiveController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcSendController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcSendController + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + LsSignalingConnection + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileReceiver + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileSender + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcReceiveController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcSendController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + FutureOr Function(LsSignalingConnection) + dco_decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + Object dco_decode_DartOpaque(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return decodeDartOpaque(raw, generalizedFrbRustBinding); + } + + @protected + LsSignalingConnection + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileReceiver + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcFileSender + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcReceiveController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + RtcSendController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalDcoDecode(raw as List); + } + + @protected + Set dco_decode_Set_String_None(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return Set.from(dco_decode_list_String(raw)); + } + + @protected + RustStreamSink + dco_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + RustStreamSink dco_decode_StreamSink_list_prim_u_8_strict_Sse( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + RustStreamSink dco_decode_StreamSink_rtc_file_error_Sse( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + RustStreamSink dco_decode_StreamSink_rtc_status_Sse(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + RustStreamSink dco_decode_StreamSink_ws_server_message_Sse( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + String dco_decode_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as String; + } + + @protected + UuidValue dco_decode_Uuid(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return UuidValue.fromByteList(dco_decode_list_prim_u_8_strict(raw)); + } + + @protected + bool dco_decode_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + ClientInfo dco_decode_box_autoadd_client_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_client_info(raw); + } + + @protected + ClientInfoWithoutId dco_decode_box_autoadd_client_info_without_id( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_client_info_without_id(raw); + } + + @protected + DeviceType dco_decode_box_autoadd_device_type(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_device_type(raw); + } + + @protected + ExpectingPublicKey dco_decode_box_autoadd_expecting_public_key(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_expecting_public_key(raw); + } + + @protected + FileMetadata dco_decode_box_autoadd_file_metadata(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_file_metadata(raw); + } + + @protected + PinConfig dco_decode_box_autoadd_pin_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_pin_config(raw); + } + + @protected + ProposingClientInfo dco_decode_box_autoadd_proposing_client_info( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_proposing_client_info(raw); + } + + @protected + RTCSendFileResponse dco_decode_box_autoadd_rtc_send_file_response( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_rtc_send_file_response(raw); + } + + @protected + WsServerSdpMessage dco_decode_box_autoadd_ws_server_sdp_message(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_ws_server_sdp_message(raw); + } + + @protected + ClientInfo dco_decode_client_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 6) + throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + return ClientInfo( + id: dco_decode_Uuid(arr[0]), + alias: dco_decode_String(arr[1]), + version: dco_decode_String(arr[2]), + deviceModel: dco_decode_opt_String(arr[3]), + deviceType: dco_decode_opt_box_autoadd_device_type(arr[4]), + token: dco_decode_String(arr[5]), + ); + } + + @protected + ClientInfoWithoutId dco_decode_client_info_without_id(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 5) + throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return ClientInfoWithoutId( + alias: dco_decode_String(arr[0]), + version: dco_decode_String(arr[1]), + deviceModel: dco_decode_opt_String(arr[2]), + deviceType: dco_decode_opt_box_autoadd_device_type(arr[3]), + token: dco_decode_String(arr[4]), + ); + } + + @protected + DeviceType dco_decode_device_type(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return DeviceType.values[raw as int]; + } + + @protected + ExpectingPublicKey dco_decode_expecting_public_key(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return ExpectingPublicKey( + publicKey: dco_decode_String(arr[0]), + kind: dco_decode_String(arr[1]), + ); + } + + @protected + FileDto dco_decode_file_dto(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 7) + throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + return FileDto( + id: dco_decode_String(arr[0]), + fileName: dco_decode_String(arr[1]), + size: dco_decode_u_64(arr[2]), + fileType: dco_decode_String(arr[3]), + sha256: dco_decode_opt_String(arr[4]), + preview: dco_decode_opt_String(arr[5]), + metadata: dco_decode_opt_box_autoadd_file_metadata(arr[6]), + ); + } + + @protected + FileMetadata dco_decode_file_metadata(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return FileMetadata( + modified: dco_decode_opt_String(arr[0]), + accessed: dco_decode_opt_String(arr[1]), + ); + } + + @protected + int dco_decode_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + PlatformInt64 dco_decode_isize(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + + @protected + KeyPair dco_decode_key_pair(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return KeyPair( + privateKey: dco_decode_String(arr[0]), + publicKey: dco_decode_String(arr[1]), + ); + } + + @protected + List dco_decode_list_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_String).toList(); + } + + @protected + List dco_decode_list_client_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_client_info).toList(); + } + + @protected + List dco_decode_list_file_dto(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_file_dto).toList(); + } + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as List; + } + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as Uint8List; + } + + @protected + String? dco_decode_opt_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_String(raw); + } + + @protected + DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_device_type(raw); + } + + @protected + ExpectingPublicKey? dco_decode_opt_box_autoadd_expecting_public_key( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_expecting_public_key(raw); + } + + @protected + FileMetadata? dco_decode_opt_box_autoadd_file_metadata(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_file_metadata(raw); + } + + @protected + PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_pin_config(raw); + } + + @protected + PinConfig dco_decode_pin_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return PinConfig( + pin: dco_decode_String(arr[0]), + maxTries: dco_decode_u_8(arr[1]), + ); + } + + @protected + ProposingClientInfo dco_decode_proposing_client_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 4) + throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); + return ProposingClientInfo( + alias: dco_decode_String(arr[0]), + version: dco_decode_String(arr[1]), + deviceModel: dco_decode_opt_String(arr[2]), + deviceType: dco_decode_opt_box_autoadd_device_type(arr[3]), + ); + } + + @protected + RTCFileError dco_decode_rtc_file_error(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return RTCFileError( + fileId: dco_decode_String(arr[0]), + error: dco_decode_String(arr[1]), + ); + } + + @protected + RTCSendFileResponse dco_decode_rtc_send_file_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return RTCSendFileResponse( + id: dco_decode_String(arr[0]), + success: dco_decode_bool(arr[1]), + error: dco_decode_opt_String(arr[2]), + ); + } + + @protected + RTCStatus dco_decode_rtc_status(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return RTCStatus_SdpExchanged(); + case 1: + return RTCStatus_Connected(); + case 2: + return RTCStatus_PinRequired(); + case 3: + return RTCStatus_TooManyAttempts(); + case 4: + return RTCStatus_Declined(); + case 5: + return RTCStatus_Sending(); + case 6: + return RTCStatus_Finished(); + case 7: + return RTCStatus_Error( + dco_decode_String(raw[1]), + ); + default: + throw Exception('unreachable'); + } + } + + @protected + int dco_decode_u_16(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + BigInt dco_decode_u_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + + @protected + int dco_decode_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + void dco_decode_unit(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return; + } + + @protected + BigInt dco_decode_usize(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + + @protected + WsServerMessage dco_decode_ws_server_message(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return WsServerMessage_Hello( + client: dco_decode_box_autoadd_client_info(raw[1]), + peers: dco_decode_list_client_info(raw[2]), + ); + case 1: + return WsServerMessage_Join( + peer: dco_decode_box_autoadd_client_info(raw[1]), + ); + case 2: + return WsServerMessage_Update( + peer: dco_decode_box_autoadd_client_info(raw[1]), + ); + case 3: + return WsServerMessage_Left( + peerId: dco_decode_Uuid(raw[1]), + ); + case 4: + return WsServerMessage_Offer( + dco_decode_box_autoadd_ws_server_sdp_message(raw[1]), + ); + case 5: + return WsServerMessage_Answer( + dco_decode_box_autoadd_ws_server_sdp_message(raw[1]), + ); + case 6: + return WsServerMessage_Error( + code: dco_decode_u_16(raw[1]), + ); + default: + throw Exception('unreachable'); + } + } + + @protected + WsServerSdpMessage dco_decode_ws_server_sdp_message(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return WsServerSdpMessage( + peer: dco_decode_client_info(arr[0]), + sessionId: dco_decode_String(arr[1]), + sdp: dco_decode_String(arr[2]), + ); + } + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_String(deserializer); + return AnyhowException(inner); + } + + @protected + LsSignalingConnection + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileReceiver + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileSender + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcReceiveController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcSendController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcSendController + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + LsSignalingConnection + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileReceiver + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileSender + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcReceiveController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcSendController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_isize(deserializer); + return decodeDartOpaque(inner, generalizedFrbRustBinding); + } + + @protected + LsSignalingConnection + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return LsSignalingConnectionImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileReceiver + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileReceiverImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcFileSender + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcFileSenderImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcReceiveController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcReceiveControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + RtcSendController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RtcSendControllerImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); + } + + @protected + Set sse_decode_Set_String_None(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_String(deserializer); + return Set.from(inner); + } + + @protected + RustStreamSink + sse_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + + @protected + RustStreamSink sse_decode_StreamSink_list_prim_u_8_strict_Sse( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + + @protected + RustStreamSink sse_decode_StreamSink_rtc_file_error_Sse( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + + @protected + RustStreamSink sse_decode_StreamSink_rtc_status_Sse( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + + @protected + RustStreamSink sse_decode_StreamSink_ws_server_message_Sse( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + + @protected + String sse_decode_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return utf8.decoder.convert(inner); + } + + @protected + UuidValue sse_decode_Uuid(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return UuidValue.fromByteList(inner); + } + + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_client_info(deserializer)); + } + + @protected + ClientInfoWithoutId sse_decode_box_autoadd_client_info_without_id( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_client_info_without_id(deserializer)); + } + + @protected + DeviceType sse_decode_box_autoadd_device_type(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_device_type(deserializer)); + } + + @protected + ExpectingPublicKey sse_decode_box_autoadd_expecting_public_key( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_expecting_public_key(deserializer)); + } + + @protected + FileMetadata sse_decode_box_autoadd_file_metadata( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_file_metadata(deserializer)); + } + + @protected + PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_pin_config(deserializer)); + } + + @protected + ProposingClientInfo sse_decode_box_autoadd_proposing_client_info( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_proposing_client_info(deserializer)); + } + + @protected + RTCSendFileResponse sse_decode_box_autoadd_rtc_send_file_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_rtc_send_file_response(deserializer)); + } + + @protected + WsServerSdpMessage sse_decode_box_autoadd_ws_server_sdp_message( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_ws_server_sdp_message(deserializer)); + } + + @protected + ClientInfo sse_decode_client_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_Uuid(deserializer); + var var_alias = sse_decode_String(deserializer); + var var_version = sse_decode_String(deserializer); + var var_deviceModel = sse_decode_opt_String(deserializer); + var var_deviceType = sse_decode_opt_box_autoadd_device_type(deserializer); + var var_token = sse_decode_String(deserializer); + return ClientInfo( + id: var_id, + alias: var_alias, + version: var_version, + deviceModel: var_deviceModel, + deviceType: var_deviceType, + token: var_token); + } + + @protected + ClientInfoWithoutId sse_decode_client_info_without_id( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_alias = sse_decode_String(deserializer); + var var_version = sse_decode_String(deserializer); + var var_deviceModel = sse_decode_opt_String(deserializer); + var var_deviceType = sse_decode_opt_box_autoadd_device_type(deserializer); + var var_token = sse_decode_String(deserializer); + return ClientInfoWithoutId( + alias: var_alias, + version: var_version, + deviceModel: var_deviceModel, + deviceType: var_deviceType, + token: var_token); + } + + @protected + DeviceType sse_decode_device_type(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_i_32(deserializer); + return DeviceType.values[inner]; + } + + @protected + ExpectingPublicKey sse_decode_expecting_public_key( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_publicKey = sse_decode_String(deserializer); + var var_kind = sse_decode_String(deserializer); + return ExpectingPublicKey(publicKey: var_publicKey, kind: var_kind); + } + + @protected + FileDto sse_decode_file_dto(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_String(deserializer); + var var_fileName = sse_decode_String(deserializer); + var var_size = sse_decode_u_64(deserializer); + var var_fileType = sse_decode_String(deserializer); + var var_sha256 = sse_decode_opt_String(deserializer); + var var_preview = sse_decode_opt_String(deserializer); + var var_metadata = sse_decode_opt_box_autoadd_file_metadata(deserializer); + return FileDto( + id: var_id, + fileName: var_fileName, + size: var_size, + fileType: var_fileType, + sha256: var_sha256, + preview: var_preview, + metadata: var_metadata); + } + + @protected + FileMetadata sse_decode_file_metadata(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_modified = sse_decode_opt_String(deserializer); + var var_accessed = sse_decode_opt_String(deserializer); + return FileMetadata(modified: var_modified, accessed: var_accessed); + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + + @protected + KeyPair sse_decode_key_pair(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_privateKey = sse_decode_String(deserializer); + var var_publicKey = sse_decode_String(deserializer); + return KeyPair(privateKey: var_privateKey, publicKey: var_publicKey); + } + + @protected + List sse_decode_list_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_String(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_client_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_client_info(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_file_dto(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_file_dto(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_String(deserializer)); + } else { + return null; + } + } + + @protected + DeviceType? sse_decode_opt_box_autoadd_device_type( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_device_type(deserializer)); + } else { + return null; + } + } + + @protected + ExpectingPublicKey? sse_decode_opt_box_autoadd_expecting_public_key( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_expecting_public_key(deserializer)); + } else { + return null; + } + } + + @protected + FileMetadata? sse_decode_opt_box_autoadd_file_metadata( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_file_metadata(deserializer)); + } else { + return null; + } + } + + @protected + PinConfig? sse_decode_opt_box_autoadd_pin_config( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_pin_config(deserializer)); + } else { + return null; + } + } + + @protected + PinConfig sse_decode_pin_config(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_pin = sse_decode_String(deserializer); + var var_maxTries = sse_decode_u_8(deserializer); + return PinConfig(pin: var_pin, maxTries: var_maxTries); + } + + @protected + ProposingClientInfo sse_decode_proposing_client_info( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_alias = sse_decode_String(deserializer); + var var_version = sse_decode_String(deserializer); + var var_deviceModel = sse_decode_opt_String(deserializer); + var var_deviceType = sse_decode_opt_box_autoadd_device_type(deserializer); + return ProposingClientInfo( + alias: var_alias, + version: var_version, + deviceModel: var_deviceModel, + deviceType: var_deviceType); + } + + @protected + RTCFileError sse_decode_rtc_file_error(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_fileId = sse_decode_String(deserializer); + var var_error = sse_decode_String(deserializer); + return RTCFileError(fileId: var_fileId, error: var_error); + } + + @protected + RTCSendFileResponse sse_decode_rtc_send_file_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_String(deserializer); + var var_success = sse_decode_bool(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return RTCSendFileResponse( + id: var_id, success: var_success, error: var_error); + } + + @protected + RTCStatus sse_decode_rtc_status(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + return RTCStatus_SdpExchanged(); + case 1: + return RTCStatus_Connected(); + case 2: + return RTCStatus_PinRequired(); + case 3: + return RTCStatus_TooManyAttempts(); + case 4: + return RTCStatus_Declined(); + case 5: + return RTCStatus_Sending(); + case 6: + return RTCStatus_Finished(); + case 7: + var var_field0 = sse_decode_String(deserializer); + return RTCStatus_Error(var_field0); + default: + throw UnimplementedError(''); + } + } + + @protected + int sse_decode_u_16(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint16(); + } + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + + @protected + int sse_decode_u_8(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8(); + } + + @protected + void sse_decode_unit(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + + @protected + WsServerMessage sse_decode_ws_server_message(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_client = sse_decode_box_autoadd_client_info(deserializer); + var var_peers = sse_decode_list_client_info(deserializer); + return WsServerMessage_Hello(client: var_client, peers: var_peers); + case 1: + var var_peer = sse_decode_box_autoadd_client_info(deserializer); + return WsServerMessage_Join(peer: var_peer); + case 2: + var var_peer = sse_decode_box_autoadd_client_info(deserializer); + return WsServerMessage_Update(peer: var_peer); + case 3: + var var_peerId = sse_decode_Uuid(deserializer); + return WsServerMessage_Left(peerId: var_peerId); + case 4: + var var_field0 = + sse_decode_box_autoadd_ws_server_sdp_message(deserializer); + return WsServerMessage_Offer(var_field0); + case 5: + var var_field0 = + sse_decode_box_autoadd_ws_server_sdp_message(deserializer); + return WsServerMessage_Answer(var_field0); + case 6: + var var_code = sse_decode_u_16(deserializer); + return WsServerMessage_Error(code: var_code); + default: + throw UnimplementedError(''); + } + } + + @protected + WsServerSdpMessage sse_decode_ws_server_sdp_message( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_peer = sse_decode_client_info(deserializer); + var var_sessionId = sse_decode_String(deserializer); + var var_sdp = sse_decode_String(deserializer); + return WsServerSdpMessage( + peer: var_peer, sessionId: var_sessionId, sdp: var_sdp); + } + + @protected + void sse_encode_AnyhowException( + AnyhowException self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.message, serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as LsSignalingConnectionImpl).frbInternalSseEncode(move: true), + serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileReceiverImpl).frbInternalSseEncode(move: true), + serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileSenderImpl).frbInternalSseEncode(move: true), + serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcReceiveControllerImpl).frbInternalSseEncode(move: true), + serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcSendControllerImpl).frbInternalSseEncode(move: true), + serializer); + } + + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcSendControllerImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as LsSignalingConnectionImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileReceiverImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileSenderImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcReceiveControllerImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcSendControllerImpl).frbInternalSseEncode(move: false), + serializer); + } + + @protected + void + sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + FutureOr Function(LsSignalingConnection) self, + SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + self), + serializer); + } + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_isize( + PlatformPointerUtil.ptrToPlatformInt64(encodeDartOpaque( + self, portManager.dartHandlerPort, generalizedFrbRustBinding)), + serializer); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as LsSignalingConnectionImpl).frbInternalSseEncode(move: null), + serializer); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileReceiverImpl).frbInternalSseEncode(move: null), + serializer); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcFileSenderImpl).frbInternalSseEncode(move: null), + serializer); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcReceiveControllerImpl).frbInternalSseEncode(move: null), + serializer); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as RtcSendControllerImpl).frbInternalSseEncode(move: null), + serializer); + } + + @protected + void sse_encode_Set_String_None(Set self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_String(self.toList(), serializer); + } + + @protected + void + sse_encode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + RustStreamSink self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver, + decodeErrorData: sse_decode_AnyhowException, + )), + serializer); + } + + @protected + void sse_encode_StreamSink_list_prim_u_8_strict_Sse( + RustStreamSink self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + )), + serializer); + } + + @protected + void sse_encode_StreamSink_rtc_file_error_Sse( + RustStreamSink self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_rtc_file_error, + decodeErrorData: sse_decode_AnyhowException, + )), + serializer); + } + + @protected + void sse_encode_StreamSink_rtc_status_Sse( + RustStreamSink self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_rtc_status, + decodeErrorData: sse_decode_AnyhowException, + )), + serializer); + } + + @protected + void sse_encode_StreamSink_ws_server_message_Sse( + RustStreamSink self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_ws_server_message, + decodeErrorData: sse_decode_AnyhowException, + )), + serializer); + } + + @protected + void sse_encode_String(String self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); + } + + @protected + void sse_encode_Uuid(UuidValue self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(self.toBytes(), serializer); + } + + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } + + @protected + void sse_encode_box_autoadd_client_info( + ClientInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_client_info(self, serializer); + } + + @protected + void sse_encode_box_autoadd_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_client_info_without_id(self, serializer); + } + + @protected + void sse_encode_box_autoadd_device_type( + DeviceType self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_device_type(self, serializer); + } + + @protected + void sse_encode_box_autoadd_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_expecting_public_key(self, serializer); + } + + @protected + void sse_encode_box_autoadd_file_metadata( + FileMetadata self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_file_metadata(self, serializer); + } + + @protected + void sse_encode_box_autoadd_pin_config( + PinConfig self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_pin_config(self, serializer); + } + + @protected + void sse_encode_box_autoadd_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_proposing_client_info(self, serializer); + } + + @protected + void sse_encode_box_autoadd_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_rtc_send_file_response(self, serializer); + } + + @protected + void sse_encode_box_autoadd_ws_server_sdp_message( + WsServerSdpMessage self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_ws_server_sdp_message(self, serializer); + } + + @protected + void sse_encode_client_info(ClientInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_Uuid(self.id, serializer); + sse_encode_String(self.alias, serializer); + sse_encode_String(self.version, serializer); + sse_encode_opt_String(self.deviceModel, serializer); + sse_encode_opt_box_autoadd_device_type(self.deviceType, serializer); + sse_encode_String(self.token, serializer); + } + + @protected + void sse_encode_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.alias, serializer); + sse_encode_String(self.version, serializer); + sse_encode_opt_String(self.deviceModel, serializer); + sse_encode_opt_box_autoadd_device_type(self.deviceType, serializer); + sse_encode_String(self.token, serializer); + } + + @protected + void sse_encode_device_type(DeviceType self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.index, serializer); + } + + @protected + void sse_encode_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.publicKey, serializer); + sse_encode_String(self.kind, serializer); + } + + @protected + void sse_encode_file_dto(FileDto self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.id, serializer); + sse_encode_String(self.fileName, serializer); + sse_encode_u_64(self.size, serializer); + sse_encode_String(self.fileType, serializer); + sse_encode_opt_String(self.sha256, serializer); + sse_encode_opt_String(self.preview, serializer); + sse_encode_opt_box_autoadd_file_metadata(self.metadata, serializer); + } + + @protected + void sse_encode_file_metadata(FileMetadata self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_String(self.modified, serializer); + sse_encode_opt_String(self.accessed, serializer); + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + + @protected + void sse_encode_key_pair(KeyPair self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.privateKey, serializer); + sse_encode_String(self.publicKey, serializer); + } + + @protected + void sse_encode_list_String(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_String(item, serializer); + } + } + + @protected + void sse_encode_list_client_info( + List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_client_info(item, serializer); + } + } + + @protected + void sse_encode_list_file_dto(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_file_dto(item, serializer); + } + } + + @protected + void sse_encode_list_prim_u_8_loose( + List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer + .putUint8List(self is Uint8List ? self : Uint8List.fromList(self)); + } + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List(self); + } + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_String(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_device_type( + DeviceType? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_device_type(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_expecting_public_key( + ExpectingPublicKey? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_expecting_public_key(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_file_metadata( + FileMetadata? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_file_metadata(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_pin_config( + PinConfig? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_pin_config(self, serializer); + } + } + + @protected + void sse_encode_pin_config(PinConfig self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.pin, serializer); + sse_encode_u_8(self.maxTries, serializer); + } + + @protected + void sse_encode_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.alias, serializer); + sse_encode_String(self.version, serializer); + sse_encode_opt_String(self.deviceModel, serializer); + sse_encode_opt_box_autoadd_device_type(self.deviceType, serializer); + } + + @protected + void sse_encode_rtc_file_error(RTCFileError self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.fileId, serializer); + sse_encode_String(self.error, serializer); + } + + @protected + void sse_encode_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.id, serializer); + sse_encode_bool(self.success, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_rtc_status(RTCStatus self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case RTCStatus_SdpExchanged(): + sse_encode_i_32(0, serializer); + case RTCStatus_Connected(): + sse_encode_i_32(1, serializer); + case RTCStatus_PinRequired(): + sse_encode_i_32(2, serializer); + case RTCStatus_TooManyAttempts(): + sse_encode_i_32(3, serializer); + case RTCStatus_Declined(): + sse_encode_i_32(4, serializer); + case RTCStatus_Sending(): + sse_encode_i_32(5, serializer); + case RTCStatus_Finished(): + sse_encode_i_32(6, serializer); + case RTCStatus_Error(field0: final field0): + sse_encode_i_32(7, serializer); + sse_encode_String(field0, serializer); + } + } + + @protected + void sse_encode_u_16(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint16(self); + } + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + + @protected + void sse_encode_u_8(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self); + } + + @protected + void sse_encode_unit(void self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + + @protected + void sse_encode_ws_server_message( + WsServerMessage self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case WsServerMessage_Hello(client: final client, peers: final peers): + sse_encode_i_32(0, serializer); + sse_encode_box_autoadd_client_info(client, serializer); + sse_encode_list_client_info(peers, serializer); + case WsServerMessage_Join(peer: final peer): + sse_encode_i_32(1, serializer); + sse_encode_box_autoadd_client_info(peer, serializer); + case WsServerMessage_Update(peer: final peer): + sse_encode_i_32(2, serializer); + sse_encode_box_autoadd_client_info(peer, serializer); + case WsServerMessage_Left(peerId: final peerId): + sse_encode_i_32(3, serializer); + sse_encode_Uuid(peerId, serializer); + case WsServerMessage_Offer(field0: final field0): + sse_encode_i_32(4, serializer); + sse_encode_box_autoadd_ws_server_sdp_message(field0, serializer); + case WsServerMessage_Answer(field0: final field0): + sse_encode_i_32(5, serializer); + sse_encode_box_autoadd_ws_server_sdp_message(field0, serializer); + case WsServerMessage_Error(code: final code): + sse_encode_i_32(6, serializer); + sse_encode_u_16(code, serializer); + } + } + + @protected + void sse_encode_ws_server_sdp_message( + WsServerSdpMessage self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_client_info(self.peer, serializer); + sse_encode_String(self.sessionId, serializer); + sse_encode_String(self.sdp, serializer); + } +} + +@sealed +class LsSignalingConnectionImpl extends RustOpaque + implements LsSignalingConnection { + // Not to be used by end users + LsSignalingConnectionImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + LsSignalingConnectionImpl.frbInternalSseDecode( + BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: RustLib + .instance.api.rust_arc_increment_strong_count_LsSignalingConnection, + rustArcDecrementStrongCount: RustLib + .instance.api.rust_arc_decrement_strong_count_LsSignalingConnection, + rustArcDecrementStrongCountPtr: RustLib + .instance.api.rust_arc_decrement_strong_count_LsSignalingConnectionPtr, + ); + + Future acceptOffer( + {required List stunServers, + required WsServerSdpMessage offer, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin}) => + RustLib.instance.api.crateApiWebrtcLsSignalingConnectionAcceptOffer( + that: this, + stunServers: stunServers, + offer: offer, + privateKey: privateKey, + expectingPublicKey: expectingPublicKey, + pin: pin); + + Future sendOffer( + {required List stunServers, + required UuidValue target, + required String privateKey, + ExpectingPublicKey? expectingPublicKey, + PinConfig? pin, + required List files}) => + RustLib.instance.api.crateApiWebrtcLsSignalingConnectionSendOffer( + that: this, + stunServers: stunServers, + target: target, + privateKey: privateKey, + expectingPublicKey: expectingPublicKey, + pin: pin, + files: files); + + Future updateInfo({required ClientInfoWithoutId info}) => RustLib + .instance.api + .crateApiWebrtcLsSignalingConnectionUpdateInfo(that: this, info: info); +} + +@sealed +class RtcFileReceiverImpl extends RustOpaque implements RtcFileReceiver { + // Not to be used by end users + RtcFileReceiverImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + RtcFileReceiverImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_RtcFileReceiver, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_RtcFileReceiver, + rustArcDecrementStrongCountPtr: + RustLib.instance.api.rust_arc_decrement_strong_count_RtcFileReceiverPtr, + ); + + Future getFileId() => + RustLib.instance.api.crateApiWebrtcRtcFileReceiverGetFileId( + that: this, + ); + + Stream receive() => + RustLib.instance.api.crateApiWebrtcRtcFileReceiverReceive( + that: this, + ); +} + +@sealed +class RtcFileSenderImpl extends RustOpaque implements RtcFileSender { + // Not to be used by end users + RtcFileSenderImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + RtcFileSenderImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_RtcFileSender, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_RtcFileSender, + rustArcDecrementStrongCountPtr: + RustLib.instance.api.rust_arc_decrement_strong_count_RtcFileSenderPtr, + ); + + Future send({required List data}) => RustLib.instance.api + .crateApiWebrtcRtcFileSenderSend(that: this, data: data); +} + +@sealed +class RtcReceiveControllerImpl extends RustOpaque + implements RtcReceiveController { + // Not to be used by end users + RtcReceiveControllerImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + RtcReceiveControllerImpl.frbInternalSseDecode( + BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: RustLib + .instance.api.rust_arc_increment_strong_count_RtcReceiveController, + rustArcDecrementStrongCount: RustLib + .instance.api.rust_arc_decrement_strong_count_RtcReceiveController, + rustArcDecrementStrongCountPtr: RustLib + .instance.api.rust_arc_decrement_strong_count_RtcReceiveControllerPtr, + ); + + Future decline() => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerDecline( + that: this, + ); + + Stream listenError() => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerListenError( + that: this, + ); + + Future> listenFiles() => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerListenFiles( + that: this, + ); + + Stream listenReceiving() => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerListenReceiving( + that: this, + ); + + Stream listenStatus() => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerListenStatus( + that: this, + ); + + Future sendFileStatus({required RTCSendFileResponse status}) => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerSendFileStatus( + that: this, status: status); + + Future sendPin({required String pin}) => RustLib.instance.api + .crateApiWebrtcRtcReceiveControllerSendPin(that: this, pin: pin); + + Future sendSelection({required Set selection}) => + RustLib.instance.api.crateApiWebrtcRtcReceiveControllerSendSelection( + that: this, selection: selection); +} + +@sealed +class RtcSendControllerImpl extends RustOpaque implements RtcSendController { + // Not to be used by end users + RtcSendControllerImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + RtcSendControllerImpl.frbInternalSseDecode( + BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_RtcSendController, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_RtcSendController, + rustArcDecrementStrongCountPtr: RustLib + .instance.api.rust_arc_decrement_strong_count_RtcSendControllerPtr, + ); + + Stream listenError() => + RustLib.instance.api.crateApiWebrtcRtcSendControllerListenError( + that: this, + ); + + Future> listenSelectedFiles() => + RustLib.instance.api.crateApiWebrtcRtcSendControllerListenSelectedFiles( + that: this, + ); + + Stream listenStatus() => + RustLib.instance.api.crateApiWebrtcRtcSendControllerListenStatus( + that: this, + ); + + Future sendFile({required String fileId}) => + RustLib.instance.api + .crateApiWebrtcRtcSendControllerSendFile(that: this, fileId: fileId); + + Future sendPin({required String pin}) => RustLib.instance.api + .crateApiWebrtcRtcSendControllerSendPin(that: this, pin: pin); +} diff --git a/app/lib/rust/frb_generated.io.dart b/app/lib/rust/frb_generated.io.dart new file mode 100644 index 00000000..c18bb3d6 --- /dev/null +++ b/app/lib/rust/frb_generated.io.dart @@ -0,0 +1,987 @@ +// 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 'dart:async'; +import 'dart:convert'; +import 'dart:ffi' as ffi; + +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; +import 'package:localsend_app/rust/api/crypto.dart'; +import 'package:localsend_app/rust/api/logging.dart'; +import 'package:localsend_app/rust/api/model.dart'; +import 'package:localsend_app/rust/api/webrtc.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; +import 'package:uuid/uuid.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_LsSignalingConnectionPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnectionPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileReceiverPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiverPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileSenderPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSenderPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcReceiveControllerPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveControllerPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcSendControllerPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendControllerPtr; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + LsSignalingConnection + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + LsSignalingConnection + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + FutureOr Function(LsSignalingConnection) + dco_decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + dynamic raw); + + @protected + Object dco_decode_DartOpaque(dynamic raw); + + @protected + LsSignalingConnection + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + Set dco_decode_Set_String_None(dynamic raw); + + @protected + RustStreamSink + dco_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_list_prim_u_8_strict_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_rtc_file_error_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_rtc_status_Sse(dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_ws_server_message_Sse( + dynamic raw); + + @protected + String dco_decode_String(dynamic raw); + + @protected + UuidValue dco_decode_Uuid(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + ClientInfo dco_decode_box_autoadd_client_info(dynamic raw); + + @protected + ClientInfoWithoutId dco_decode_box_autoadd_client_info_without_id( + dynamic raw); + + @protected + DeviceType dco_decode_box_autoadd_device_type(dynamic raw); + + @protected + ExpectingPublicKey dco_decode_box_autoadd_expecting_public_key(dynamic raw); + + @protected + FileMetadata dco_decode_box_autoadd_file_metadata(dynamic raw); + + @protected + PinConfig dco_decode_box_autoadd_pin_config(dynamic raw); + + @protected + ProposingClientInfo dco_decode_box_autoadd_proposing_client_info(dynamic raw); + + @protected + RTCSendFileResponse dco_decode_box_autoadd_rtc_send_file_response( + dynamic raw); + + @protected + WsServerSdpMessage dco_decode_box_autoadd_ws_server_sdp_message(dynamic raw); + + @protected + ClientInfo dco_decode_client_info(dynamic raw); + + @protected + ClientInfoWithoutId dco_decode_client_info_without_id(dynamic raw); + + @protected + DeviceType dco_decode_device_type(dynamic raw); + + @protected + ExpectingPublicKey dco_decode_expecting_public_key(dynamic raw); + + @protected + FileDto dco_decode_file_dto(dynamic raw); + + @protected + FileMetadata dco_decode_file_metadata(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_isize(dynamic raw); + + @protected + KeyPair dco_decode_key_pair(dynamic raw); + + @protected + List dco_decode_list_String(dynamic raw); + + @protected + List dco_decode_list_client_info(dynamic raw); + + @protected + List dco_decode_list_file_dto(dynamic raw); + + @protected + List 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 + DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw); + + @protected + ExpectingPublicKey? dco_decode_opt_box_autoadd_expecting_public_key( + dynamic raw); + + @protected + FileMetadata? dco_decode_opt_box_autoadd_file_metadata(dynamic raw); + + @protected + PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw); + + @protected + PinConfig dco_decode_pin_config(dynamic raw); + + @protected + ProposingClientInfo dco_decode_proposing_client_info(dynamic raw); + + @protected + RTCFileError dco_decode_rtc_file_error(dynamic raw); + + @protected + RTCSendFileResponse dco_decode_rtc_send_file_response(dynamic raw); + + @protected + RTCStatus dco_decode_rtc_status(dynamic raw); + + @protected + int dco_decode_u_16(dynamic raw); + + @protected + BigInt dco_decode_u_64(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + WsServerMessage dco_decode_ws_server_message(dynamic raw); + + @protected + WsServerSdpMessage dco_decode_ws_server_sdp_message(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + Set sse_decode_Set_String_None(SseDeserializer deserializer); + + @protected + RustStreamSink + sse_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_list_prim_u_8_strict_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_rtc_file_error_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_rtc_status_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_ws_server_message_Sse( + SseDeserializer deserializer); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + UuidValue sse_decode_Uuid(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer); + + @protected + ClientInfoWithoutId sse_decode_box_autoadd_client_info_without_id( + SseDeserializer deserializer); + + @protected + DeviceType sse_decode_box_autoadd_device_type(SseDeserializer deserializer); + + @protected + ExpectingPublicKey sse_decode_box_autoadd_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileMetadata sse_decode_box_autoadd_file_metadata( + SseDeserializer deserializer); + + @protected + PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer); + + @protected + ProposingClientInfo sse_decode_box_autoadd_proposing_client_info( + SseDeserializer deserializer); + + @protected + RTCSendFileResponse sse_decode_box_autoadd_rtc_send_file_response( + SseDeserializer deserializer); + + @protected + WsServerSdpMessage sse_decode_box_autoadd_ws_server_sdp_message( + SseDeserializer deserializer); + + @protected + ClientInfo sse_decode_client_info(SseDeserializer deserializer); + + @protected + ClientInfoWithoutId sse_decode_client_info_without_id( + SseDeserializer deserializer); + + @protected + DeviceType sse_decode_device_type(SseDeserializer deserializer); + + @protected + ExpectingPublicKey sse_decode_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileDto sse_decode_file_dto(SseDeserializer deserializer); + + @protected + FileMetadata sse_decode_file_metadata(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer); + + @protected + KeyPair sse_decode_key_pair(SseDeserializer deserializer); + + @protected + List sse_decode_list_String(SseDeserializer deserializer); + + @protected + List sse_decode_list_client_info(SseDeserializer deserializer); + + @protected + List sse_decode_list_file_dto(SseDeserializer deserializer); + + @protected + List 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 + DeviceType? sse_decode_opt_box_autoadd_device_type( + SseDeserializer deserializer); + + @protected + ExpectingPublicKey? sse_decode_opt_box_autoadd_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileMetadata? sse_decode_opt_box_autoadd_file_metadata( + SseDeserializer deserializer); + + @protected + PinConfig? sse_decode_opt_box_autoadd_pin_config( + SseDeserializer deserializer); + + @protected + PinConfig sse_decode_pin_config(SseDeserializer deserializer); + + @protected + ProposingClientInfo sse_decode_proposing_client_info( + SseDeserializer deserializer); + + @protected + RTCFileError sse_decode_rtc_file_error(SseDeserializer deserializer); + + @protected + RTCSendFileResponse sse_decode_rtc_send_file_response( + SseDeserializer deserializer); + + @protected + RTCStatus sse_decode_rtc_status(SseDeserializer deserializer); + + @protected + int sse_decode_u_16(SseDeserializer deserializer); + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + WsServerMessage sse_decode_ws_server_message(SseDeserializer deserializer); + + @protected + WsServerSdpMessage sse_decode_ws_server_sdp_message( + SseDeserializer deserializer); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + FutureOr Function(LsSignalingConnection) self, + SseSerializer serializer); + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void sse_encode_Set_String_None(Set self, SseSerializer serializer); + + @protected + void + sse_encode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_list_prim_u_8_strict_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_rtc_file_error_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_rtc_status_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_ws_server_message_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_Uuid(UuidValue self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_client_info( + ClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_device_type( + DeviceType self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_file_metadata( + FileMetadata self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_pin_config( + PinConfig self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_ws_server_sdp_message( + WsServerSdpMessage self, SseSerializer serializer); + + @protected + void sse_encode_client_info(ClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer); + + @protected + void sse_encode_device_type(DeviceType self, SseSerializer serializer); + + @protected + void sse_encode_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer); + + @protected + void sse_encode_file_dto(FileDto self, SseSerializer serializer); + + @protected + void sse_encode_file_metadata(FileMetadata self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_key_pair(KeyPair self, SseSerializer serializer); + + @protected + void sse_encode_list_String(List self, SseSerializer serializer); + + @protected + void sse_encode_list_client_info( + List self, SseSerializer serializer); + + @protected + void sse_encode_list_file_dto(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List 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_opt_box_autoadd_device_type( + DeviceType? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_expecting_public_key( + ExpectingPublicKey? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_file_metadata( + FileMetadata? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_pin_config( + PinConfig? self, SseSerializer serializer); + + @protected + void sse_encode_pin_config(PinConfig self, SseSerializer serializer); + + @protected + void sse_encode_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_rtc_file_error(RTCFileError self, SseSerializer serializer); + + @protected + void sse_encode_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer); + + @protected + void sse_encode_rtc_status(RTCStatus self, SseSerializer serializer); + + @protected + void sse_encode_u_16(int self, SseSerializer serializer); + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_ws_server_message( + WsServerMessage self, SseSerializer serializer); + + @protected + void sse_encode_ws_server_sdp_message( + WsServerSdpMessage 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 Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + RustLibWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnectionPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection'); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnectionPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnectionPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection'); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnectionPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiverPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver'); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiverPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiverPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver'); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiverPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSenderPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender'); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSenderPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSenderPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender'); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSenderPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveControllerPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController'); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveControllerPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveControllerPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController'); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveControllerPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendControllerPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController'); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendControllerPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendControllerPtr = + _lookup)>>( + 'frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController'); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendControllerPtr + .asFunction)>(); +} diff --git a/app/lib/rust/frb_generated.web.dart b/app/lib/rust/frb_generated.web.dart new file mode 100644 index 00000000..eb99cd1f --- /dev/null +++ b/app/lib/rust/frb_generated.web.dart @@ -0,0 +1,927 @@ +// 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 'dart:async'; +import 'dart:convert'; + +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; +import 'package:localsend_app/rust/api/crypto.dart'; +import 'package:localsend_app/rust/api/logging.dart'; +import 'package:localsend_app/rust/api/model.dart'; +import 'package:localsend_app/rust/api/webrtc.dart'; +import 'package:localsend_app/rust/frb_generated.dart'; +import 'package:uuid/uuid.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_LsSignalingConnectionPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileReceiverPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcFileSenderPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcReceiveControllerPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_RtcSendControllerPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + LsSignalingConnection + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + LsSignalingConnection + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + FutureOr Function(LsSignalingConnection) + dco_decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + dynamic raw); + + @protected + Object dco_decode_DartOpaque(dynamic raw); + + @protected + LsSignalingConnection + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + dynamic raw); + + @protected + RtcFileReceiver + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + dynamic raw); + + @protected + RtcFileSender + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + dynamic raw); + + @protected + RtcReceiveController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + dynamic raw); + + @protected + RtcSendController + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + dynamic raw); + + @protected + Set dco_decode_Set_String_None(dynamic raw); + + @protected + RustStreamSink + dco_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_list_prim_u_8_strict_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_rtc_file_error_Sse( + dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_rtc_status_Sse(dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_ws_server_message_Sse( + dynamic raw); + + @protected + String dco_decode_String(dynamic raw); + + @protected + UuidValue dco_decode_Uuid(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + ClientInfo dco_decode_box_autoadd_client_info(dynamic raw); + + @protected + ClientInfoWithoutId dco_decode_box_autoadd_client_info_without_id( + dynamic raw); + + @protected + DeviceType dco_decode_box_autoadd_device_type(dynamic raw); + + @protected + ExpectingPublicKey dco_decode_box_autoadd_expecting_public_key(dynamic raw); + + @protected + FileMetadata dco_decode_box_autoadd_file_metadata(dynamic raw); + + @protected + PinConfig dco_decode_box_autoadd_pin_config(dynamic raw); + + @protected + ProposingClientInfo dco_decode_box_autoadd_proposing_client_info(dynamic raw); + + @protected + RTCSendFileResponse dco_decode_box_autoadd_rtc_send_file_response( + dynamic raw); + + @protected + WsServerSdpMessage dco_decode_box_autoadd_ws_server_sdp_message(dynamic raw); + + @protected + ClientInfo dco_decode_client_info(dynamic raw); + + @protected + ClientInfoWithoutId dco_decode_client_info_without_id(dynamic raw); + + @protected + DeviceType dco_decode_device_type(dynamic raw); + + @protected + ExpectingPublicKey dco_decode_expecting_public_key(dynamic raw); + + @protected + FileDto dco_decode_file_dto(dynamic raw); + + @protected + FileMetadata dco_decode_file_metadata(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_isize(dynamic raw); + + @protected + KeyPair dco_decode_key_pair(dynamic raw); + + @protected + List dco_decode_list_String(dynamic raw); + + @protected + List dco_decode_list_client_info(dynamic raw); + + @protected + List dco_decode_list_file_dto(dynamic raw); + + @protected + List 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 + DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw); + + @protected + ExpectingPublicKey? dco_decode_opt_box_autoadd_expecting_public_key( + dynamic raw); + + @protected + FileMetadata? dco_decode_opt_box_autoadd_file_metadata(dynamic raw); + + @protected + PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw); + + @protected + PinConfig dco_decode_pin_config(dynamic raw); + + @protected + ProposingClientInfo dco_decode_proposing_client_info(dynamic raw); + + @protected + RTCFileError dco_decode_rtc_file_error(dynamic raw); + + @protected + RTCSendFileResponse dco_decode_rtc_send_file_response(dynamic raw); + + @protected + RTCStatus dco_decode_rtc_status(dynamic raw); + + @protected + int dco_decode_u_16(dynamic raw); + + @protected + BigInt dco_decode_u_64(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + WsServerMessage dco_decode_ws_server_message(dynamic raw); + + @protected + WsServerSdpMessage dco_decode_ws_server_sdp_message(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer); + + @protected + LsSignalingConnection + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + SseDeserializer deserializer); + + @protected + RtcFileReceiver + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + SseDeserializer deserializer); + + @protected + RtcFileSender + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + SseDeserializer deserializer); + + @protected + RtcReceiveController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + SseDeserializer deserializer); + + @protected + RtcSendController + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + SseDeserializer deserializer); + + @protected + Set sse_decode_Set_String_None(SseDeserializer deserializer); + + @protected + RustStreamSink + sse_decode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_list_prim_u_8_strict_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_rtc_file_error_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_rtc_status_Sse( + SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_ws_server_message_Sse( + SseDeserializer deserializer); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + UuidValue sse_decode_Uuid(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer); + + @protected + ClientInfoWithoutId sse_decode_box_autoadd_client_info_without_id( + SseDeserializer deserializer); + + @protected + DeviceType sse_decode_box_autoadd_device_type(SseDeserializer deserializer); + + @protected + ExpectingPublicKey sse_decode_box_autoadd_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileMetadata sse_decode_box_autoadd_file_metadata( + SseDeserializer deserializer); + + @protected + PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer); + + @protected + ProposingClientInfo sse_decode_box_autoadd_proposing_client_info( + SseDeserializer deserializer); + + @protected + RTCSendFileResponse sse_decode_box_autoadd_rtc_send_file_response( + SseDeserializer deserializer); + + @protected + WsServerSdpMessage sse_decode_box_autoadd_ws_server_sdp_message( + SseDeserializer deserializer); + + @protected + ClientInfo sse_decode_client_info(SseDeserializer deserializer); + + @protected + ClientInfoWithoutId sse_decode_client_info_without_id( + SseDeserializer deserializer); + + @protected + DeviceType sse_decode_device_type(SseDeserializer deserializer); + + @protected + ExpectingPublicKey sse_decode_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileDto sse_decode_file_dto(SseDeserializer deserializer); + + @protected + FileMetadata sse_decode_file_metadata(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer); + + @protected + KeyPair sse_decode_key_pair(SseDeserializer deserializer); + + @protected + List sse_decode_list_String(SseDeserializer deserializer); + + @protected + List sse_decode_list_client_info(SseDeserializer deserializer); + + @protected + List sse_decode_list_file_dto(SseDeserializer deserializer); + + @protected + List 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 + DeviceType? sse_decode_opt_box_autoadd_device_type( + SseDeserializer deserializer); + + @protected + ExpectingPublicKey? sse_decode_opt_box_autoadd_expecting_public_key( + SseDeserializer deserializer); + + @protected + FileMetadata? sse_decode_opt_box_autoadd_file_metadata( + SseDeserializer deserializer); + + @protected + PinConfig? sse_decode_opt_box_autoadd_pin_config( + SseDeserializer deserializer); + + @protected + PinConfig sse_decode_pin_config(SseDeserializer deserializer); + + @protected + ProposingClientInfo sse_decode_proposing_client_info( + SseDeserializer deserializer); + + @protected + RTCFileError sse_decode_rtc_file_error(SseDeserializer deserializer); + + @protected + RTCSendFileResponse sse_decode_rtc_send_file_response( + SseDeserializer deserializer); + + @protected + RTCStatus sse_decode_rtc_status(SseDeserializer deserializer); + + @protected + int sse_decode_u_16(SseDeserializer deserializer); + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + WsServerMessage sse_decode_ws_server_message(SseDeserializer deserializer); + + @protected + WsServerSdpMessage sse_decode_ws_server_sdp_message( + SseDeserializer deserializer); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void + sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + FutureOr Function(LsSignalingConnection) self, + SseSerializer serializer); + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + LsSignalingConnection self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + RtcFileReceiver self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + RtcFileSender self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + RtcReceiveController self, SseSerializer serializer); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + RtcSendController self, SseSerializer serializer); + + @protected + void sse_encode_Set_String_None(Set self, SseSerializer serializer); + + @protected + void + sse_encode_StreamSink_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_list_prim_u_8_strict_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_rtc_file_error_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_rtc_status_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_StreamSink_ws_server_message_Sse( + RustStreamSink self, SseSerializer serializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_Uuid(UuidValue self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_client_info( + ClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_device_type( + DeviceType self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_file_metadata( + FileMetadata self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_pin_config( + PinConfig self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_ws_server_sdp_message( + WsServerSdpMessage self, SseSerializer serializer); + + @protected + void sse_encode_client_info(ClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_client_info_without_id( + ClientInfoWithoutId self, SseSerializer serializer); + + @protected + void sse_encode_device_type(DeviceType self, SseSerializer serializer); + + @protected + void sse_encode_expecting_public_key( + ExpectingPublicKey self, SseSerializer serializer); + + @protected + void sse_encode_file_dto(FileDto self, SseSerializer serializer); + + @protected + void sse_encode_file_metadata(FileMetadata self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_key_pair(KeyPair self, SseSerializer serializer); + + @protected + void sse_encode_list_String(List self, SseSerializer serializer); + + @protected + void sse_encode_list_client_info( + List self, SseSerializer serializer); + + @protected + void sse_encode_list_file_dto(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List 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_opt_box_autoadd_device_type( + DeviceType? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_expecting_public_key( + ExpectingPublicKey? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_file_metadata( + FileMetadata? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_pin_config( + PinConfig? self, SseSerializer serializer); + + @protected + void sse_encode_pin_config(PinConfig self, SseSerializer serializer); + + @protected + void sse_encode_proposing_client_info( + ProposingClientInfo self, SseSerializer serializer); + + @protected + void sse_encode_rtc_file_error(RTCFileError self, SseSerializer serializer); + + @protected + void sse_encode_rtc_send_file_response( + RTCSendFileResponse self, SseSerializer serializer); + + @protected + void sse_encode_rtc_status(RTCStatus self, SseSerializer serializer); + + @protected + void sse_encode_u_16(int self, SseSerializer serializer); + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_ws_server_message( + WsServerMessage self, SseSerializer serializer); + + @protected + void sse_encode_ws_server_sdp_message( + WsServerSdpMessage self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + RustLibWire.fromExternalLibrary(ExternalLibrary lib); + + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + int ptr) => + wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr); + + void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + int ptr) => + wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr); + + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + int ptr) => + wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr); + + void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + int ptr) => + wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr); + + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + int ptr) => + wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr); + + void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + int ptr) => + wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr); + + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + int ptr) => + wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr); + + void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + int ptr) => + wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr); + + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + int ptr) => + wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr); + + void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + int ptr) => + wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr); +} + +@JS('wasm_bindgen') +external RustLibWasmModule get wasmModule; + +@JS() +@anonymous +extension type RustLibWasmModule._(JSObject _) implements JSObject { + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + int ptr); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + int ptr); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + int ptr); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + int ptr); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + int ptr); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + int ptr); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + int ptr); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + int ptr); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + int ptr); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + int ptr); +} diff --git a/app/lib/util/native/channel/android_channel.mapper.dart b/app/lib/util/native/channel/android_channel.mapper.dart index 1d57a34f..e1a0d6db 100644 --- a/app/lib/util/native/channel/android_channel.mapper.dart +++ b/app/lib/util/native/channel/android_channel.mapper.dart @@ -22,9 +22,11 @@ class PickDirectoryResultMapper extends ClassMapperBase { final String id = 'PickDirectoryResult'; static String _$directoryUri(PickDirectoryResult v) => v.directoryUri; - static const Field _f$directoryUri = Field('directoryUri', _$directoryUri); + static const Field _f$directoryUri = + Field('directoryUri', _$directoryUri); static List _$files(PickDirectoryResult v) => v.files; - static const Field> _f$files = Field('files', _$files); + static const Field> _f$files = + Field('files', _$files); @override final MappableFields fields = const { @@ -33,7 +35,8 @@ class PickDirectoryResultMapper extends ClassMapperBase { }; static PickDirectoryResult _instantiate(DecodingData data) { - return PickDirectoryResult(directoryUri: data.dec(_f$directoryUri), files: data.dec(_f$files)); + return PickDirectoryResult( + directoryUri: data.dec(_f$directoryUri), files: data.dec(_f$files)); } @override @@ -50,61 +53,81 @@ class PickDirectoryResultMapper extends ClassMapperBase { mixin PickDirectoryResultMappable { String serialize() { - return PickDirectoryResultMapper.ensureInitialized().encodeJson(this as PickDirectoryResult); + return PickDirectoryResultMapper.ensureInitialized() + .encodeJson(this as PickDirectoryResult); } Map toJson() { - return PickDirectoryResultMapper.ensureInitialized().encodeMap(this as PickDirectoryResult); + return PickDirectoryResultMapper.ensureInitialized() + .encodeMap(this as PickDirectoryResult); } - PickDirectoryResultCopyWith get copyWith => - _PickDirectoryResultCopyWithImpl(this as PickDirectoryResult, $identity, $identity); + PickDirectoryResultCopyWith + get copyWith => _PickDirectoryResultCopyWithImpl( + this as PickDirectoryResult, $identity, $identity); @override String toString() { - return PickDirectoryResultMapper.ensureInitialized().stringifyValue(this as PickDirectoryResult); + return PickDirectoryResultMapper.ensureInitialized() + .stringifyValue(this as PickDirectoryResult); } @override bool operator ==(Object other) { - return PickDirectoryResultMapper.ensureInitialized().equalsValue(this as PickDirectoryResult, other); + return PickDirectoryResultMapper.ensureInitialized() + .equalsValue(this as PickDirectoryResult, other); } @override int get hashCode { - return PickDirectoryResultMapper.ensureInitialized().hashValue(this as PickDirectoryResult); + return PickDirectoryResultMapper.ensureInitialized() + .hashValue(this as PickDirectoryResult); } } -extension PickDirectoryResultValueCopy<$R, $Out> on ObjectCopyWith<$R, PickDirectoryResult, $Out> { - PickDirectoryResultCopyWith<$R, PickDirectoryResult, $Out> get $asPickDirectoryResult => - $base.as((v, t, t2) => _PickDirectoryResultCopyWithImpl(v, t, t2)); +extension PickDirectoryResultValueCopy<$R, $Out> + on ObjectCopyWith<$R, PickDirectoryResult, $Out> { + PickDirectoryResultCopyWith<$R, PickDirectoryResult, $Out> + get $asPickDirectoryResult => + $base.as((v, t, t2) => _PickDirectoryResultCopyWithImpl(v, t, t2)); } -abstract class PickDirectoryResultCopyWith<$R, $In extends PickDirectoryResult, $Out> implements ClassCopyWith<$R, $In, $Out> { - ListCopyWith<$R, FileInfo, FileInfoCopyWith<$R, FileInfo, FileInfo>> get files; +abstract class PickDirectoryResultCopyWith<$R, $In extends PickDirectoryResult, + $Out> implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, FileInfo, FileInfoCopyWith<$R, FileInfo, FileInfo>> + get files; $R call({String? directoryUri, List? files}); - PickDirectoryResultCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); + PickDirectoryResultCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); } -class _PickDirectoryResultCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, PickDirectoryResult, $Out> +class _PickDirectoryResultCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, PickDirectoryResult, $Out> implements PickDirectoryResultCopyWith<$R, PickDirectoryResult, $Out> { _PickDirectoryResultCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = PickDirectoryResultMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + PickDirectoryResultMapper.ensureInitialized(); @override - ListCopyWith<$R, FileInfo, FileInfoCopyWith<$R, FileInfo, FileInfo>> get files => - ListCopyWith($value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); + ListCopyWith<$R, FileInfo, FileInfoCopyWith<$R, FileInfo, FileInfo>> + get files => ListCopyWith( + $value.files, (v, t) => v.copyWith.$chain(t), (v) => call(files: v)); @override $R call({String? directoryUri, List? files}) => - $apply(FieldCopyWithData({if (directoryUri != null) #directoryUri: directoryUri, if (files != null) #files: files})); + $apply(FieldCopyWithData({ + if (directoryUri != null) #directoryUri: directoryUri, + if (files != null) #files: files + })); @override - PickDirectoryResult $make(CopyWithData data) => - PickDirectoryResult(directoryUri: data.get(#directoryUri, or: $value.directoryUri), files: data.get(#files, or: $value.files)); + PickDirectoryResult $make(CopyWithData data) => PickDirectoryResult( + directoryUri: data.get(#directoryUri, or: $value.directoryUri), + files: data.get(#files, or: $value.files)); @override - PickDirectoryResultCopyWith<$R2, PickDirectoryResult, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _PickDirectoryResultCopyWithImpl($value, $cast, t); + PickDirectoryResultCopyWith<$R2, PickDirectoryResult, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _PickDirectoryResultCopyWithImpl($value, $cast, t); } class FileInfoMapper extends ClassMapperBase { @@ -128,7 +151,8 @@ class FileInfoMapper extends ClassMapperBase { static String _$uri(FileInfo v) => v.uri; static const Field _f$uri = Field('uri', _$uri); static int _$lastModified(FileInfo v) => v.lastModified; - static const Field _f$lastModified = Field('lastModified', _$lastModified); + static const Field _f$lastModified = + Field('lastModified', _$lastModified); @override final MappableFields fields = const { @@ -139,7 +163,11 @@ class FileInfoMapper extends ClassMapperBase { }; static FileInfo _instantiate(DecodingData data) { - return FileInfo(name: data.dec(_f$name), size: data.dec(_f$size), uri: data.dec(_f$uri), lastModified: data.dec(_f$lastModified)); + return FileInfo( + name: data.dec(_f$name), + size: data.dec(_f$size), + uri: data.dec(_f$uri), + lastModified: data.dec(_f$lastModified)); } @override @@ -156,14 +184,17 @@ class FileInfoMapper extends ClassMapperBase { mixin FileInfoMappable { String serialize() { - return FileInfoMapper.ensureInitialized().encodeJson(this as FileInfo); + return FileInfoMapper.ensureInitialized() + .encodeJson(this as FileInfo); } Map toJson() { - return FileInfoMapper.ensureInitialized().encodeMap(this as FileInfo); + return FileInfoMapper.ensureInitialized() + .encodeMap(this as FileInfo); } - FileInfoCopyWith get copyWith => _FileInfoCopyWithImpl(this as FileInfo, $identity, $identity); + FileInfoCopyWith get copyWith => + _FileInfoCopyWithImpl(this as FileInfo, $identity, $identity); @override String toString() { return FileInfoMapper.ensureInitialized().stringifyValue(this as FileInfo); @@ -171,7 +202,8 @@ mixin FileInfoMappable { @override bool operator ==(Object other) { - return FileInfoMapper.ensureInitialized().equalsValue(this as FileInfo, other); + return FileInfoMapper.ensureInitialized() + .equalsValue(this as FileInfo, other); } @override @@ -181,21 +213,27 @@ mixin FileInfoMappable { } extension FileInfoValueCopy<$R, $Out> on ObjectCopyWith<$R, FileInfo, $Out> { - FileInfoCopyWith<$R, FileInfo, $Out> get $asFileInfo => $base.as((v, t, t2) => _FileInfoCopyWithImpl(v, t, t2)); + FileInfoCopyWith<$R, FileInfo, $Out> get $asFileInfo => + $base.as((v, t, t2) => _FileInfoCopyWithImpl(v, t, t2)); } -abstract class FileInfoCopyWith<$R, $In extends FileInfo, $Out> implements ClassCopyWith<$R, $In, $Out> { +abstract class FileInfoCopyWith<$R, $In extends FileInfo, $Out> + implements ClassCopyWith<$R, $In, $Out> { $R call({String? name, int? size, String? uri, int? lastModified}); FileInfoCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } -class _FileInfoCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, FileInfo, $Out> implements FileInfoCopyWith<$R, FileInfo, $Out> { +class _FileInfoCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, FileInfo, $Out> + implements FileInfoCopyWith<$R, FileInfo, $Out> { _FileInfoCopyWithImpl(super.value, super.then, super.then2); @override - late final ClassMapperBase $mapper = FileInfoMapper.ensureInitialized(); + late final ClassMapperBase $mapper = + FileInfoMapper.ensureInitialized(); @override - $R call({String? name, int? size, String? uri, int? lastModified}) => $apply(FieldCopyWithData({ + $R call({String? name, int? size, String? uri, int? lastModified}) => + $apply(FieldCopyWithData({ if (name != null) #name: name, if (size != null) #size: size, if (uri != null) #uri: uri, @@ -209,5 +247,7 @@ class _FileInfoCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, FileInfo, $O lastModified: data.get(#lastModified, or: $value.lastModified)); @override - FileInfoCopyWith<$R2, FileInfo, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _FileInfoCopyWithImpl($value, $cast, t); + FileInfoCopyWith<$R2, FileInfo, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _FileInfoCopyWithImpl($value, $cast, t); } diff --git a/app/lib/util/security_helper.dart b/app/lib/util/security_helper.dart index 95f2fb99..5699897e 100644 --- a/app/lib/util/security_helper.dart +++ b/app/lib/util/security_helper.dart @@ -3,6 +3,8 @@ import 'dart:typed_data'; import 'package:basic_utils/basic_utils.dart'; import 'package:common/model/stored_security_context.dart'; +import 'package:convert/convert.dart'; +import 'package:localsend_app/rust/api/crypto.dart' as rust; /// Generates a random [SecurityContextResult]. StoredSecurityContext generateSecurityContext([AsymmetricKeyPair? keyPair]) { @@ -19,11 +21,13 @@ StoredSecurityContext generateSecurityContext([AsymmetricKeyPair? keyPair]) { }; final csr = X509Utils.generateRsaCsrPem(dn, privateKey, publicKey); final certificate = X509Utils.generateSelfSignedCertificate(keyPair.privateKey, csr, 365 * 10); + final hash = calculateHashOfCertificate(certificate); + final spki = extractPublicKeyFromCertificate(certificate); return StoredSecurityContext( privateKey: CryptoUtils.encodeRSAPrivateKeyToPemPkcs1(privateKey), - publicKey: CryptoUtils.encodeRSAPublicKeyToPemPkcs1(publicKey), + publicKey: spki, certificate: certificate, certificateHash: hash, ); @@ -41,3 +45,27 @@ String calculateHashOfCertificate(String certificate) { algorithmName: 'SHA-256', ); } + +String extractPublicKeyFromCertificate(String certificate) { + final cert = X509Utils.x509CertificateFromPem(certificate); + final publicHex = cert.tbsCertificate!.subjectPublicKeyInfo.bytes!; + return _hexToSpkiPem(publicHex); +} + +String _hexToSpkiPem(String hexBytes) { + final publicBytes = hex.decode(hexBytes); + final publicBase64 = base64Encode(publicBytes); + final temp = '''-----BEGIN PUBLIC KEY----- +$publicBase64 +-----END PUBLIC KEY-----'''; + return X509Utils.fixPem(temp); +} + +/// Verifies a certificate with a public key. +/// Throws an exception if the certificate is invalid. +Future verifyCertificate({ + required String cert, + required String publicKey, +}) async { + await rust.verifyCert(cert: cert, publicKey: publicKey); +} diff --git a/app/lib/widget/list_tile/device_list_tile.dart b/app/lib/widget/list_tile/device_list_tile.dart index 255eb373..8d8c279f 100644 --- a/app/lib/widget/list_tile/device_list_tile.dart +++ b/app/lib/widget/list_tile/device_list_tile.dart @@ -53,11 +53,18 @@ class DeviceListTile extends StatelessWidget { child: CustomProgressBar(progress: progress!), ) else ...[ - DeviceBadge( - backgroundColor: badgeColor, - foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer, - label: '#${device.ip.visualId}', - ), + if (device.ip != null) + DeviceBadge( + backgroundColor: badgeColor, + foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer, + label: 'LAN • HTTP', + ) + else + DeviceBadge( + backgroundColor: badgeColor, + foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer, + label: 'WebRTC', + ), if (device.deviceModel != null) DeviceBadge( backgroundColor: badgeColor, diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index 0c29172d..edc88372 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -20,6 +20,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST rhttp + rust_lib_localsend_app ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/app/macos/Podfile.lock b/app/macos/Podfile.lock index 813623bf..ecaddfd8 100644 --- a/app/macos/Podfile.lock +++ b/app/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - bitsdojo_window_macos (0.0.1): + - FlutterMacOS - connectivity_plus (0.0.1): - Flutter - FlutterMacOS @@ -34,6 +36,8 @@ PODS: - FlutterMacOS - rhttp (0.0.1): - FlutterMacOS + - rust_lib_localsend_app (0.0.1): + - FlutterMacOS - screen_retriever_macos (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -54,6 +58,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`) - Defaults (~> 4.2) - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) @@ -70,6 +75,7 @@ DEPENDENCIES: - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - photo_manager (from `Flutter/ephemeral/.symlinks/plugins/photo_manager/macos`) - rhttp (from `Flutter/ephemeral/.symlinks/plugins/rhttp/macos`) + - rust_lib_localsend_app (from `Flutter/ephemeral/.symlinks/plugins/rust_lib_localsend_app/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) @@ -84,6 +90,8 @@ SPEC REPOS: - Defaults EXTERNAL SOURCES: + bitsdojo_window_macos: + :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos connectivity_plus: :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin desktop_drop: @@ -114,6 +122,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/photo_manager/macos rhttp: :path: Flutter/ephemeral/.symlinks/plugins/rhttp/macos + rust_lib_localsend_app: + :path: Flutter/ephemeral/.symlinks/plugins/rust_lib_localsend_app/macos screen_retriever_macos: :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos shared_preferences_foundation: @@ -132,30 +142,32 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563 + bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9 + connectivity_plus: b21496ab28d1324eb59885d888a4d83b98531f01 Defaults: d785e56c0fb8890dc40351603f05c8e1df1bdd45 - desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 - device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215 - dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f - file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d + desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43 + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 + dynamic_color: b820c000cc68df65e7ba7ff177cb98404ce56651 + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 - in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 - network_info_plus: 2cb02d8435635eae13b3b79279681985121cf30c - open_dir_macos: 9649018f88e71eb9fb8aa3d95caec30a73c08c5b - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a - rhttp: 337afda4c3e4df31087160719ffca6452c225cc2 - screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 - uri_content: bfedc2e58f0ee4de88a63e5bcb826398389b22cb - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 - wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + gal: 44e5b10dbd347c8247a2851acee6c1fbe282c1d3 + in_app_purchase_storekit: e126ef1b89e4a9fdf07e28f005f82632b4609437 + network_info_plus: 21d1cd6a015ccb2fdff06a1fbfa88d54b4e92f61 + open_dir_macos: 79810d7921d777f8732bf6358b3a0839a8fad9b5 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413 + rhttp: 08d791d6373089de796318f4cfb172cbcf34e78b + rust_lib_localsend_app: 6b270a4b507b1599a1d06c9ebf86d01adfa0e875 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 + uri_content: a2350e4567f5fba3f48bb0f144f277eaddf0aa2c + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c PODFILE CHECKSUM: 5c6550f5101fcba381ddb97e2135e4cacca4f31f diff --git a/app/pubspec.lock b/app/pubspec.lock index e39bda1f..504122c5 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -278,7 +278,7 @@ packages: source: hosted version: "2.0.1" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 @@ -584,13 +584,13 @@ packages: source: hosted version: "2.0.23" flutter_rust_bridge: - dependency: transitive + dependency: "direct main" description: name: flutter_rust_bridge - sha256: "3292ad6085552987b8b3b9a7e5805567f4013372d302736b702801acb001ee00" + sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.11.1" flutter_test: dependency: transitive description: flutter @@ -601,8 +601,16 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + url: "https://pub.dev" + source: hosted + version: "2.5.7" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 @@ -1351,42 +1359,42 @@ packages: dependency: transitive description: name: refena - sha256: "1446e9622451e0cffef2af5b3fd57a52f0688d7c9eb848e969c16ccc2d14e833" + sha256: "69b2744196f7c752b66a20a360d6c188f52cccb7b4d6f5bcf61bede3a7764a35" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.1.0" refena_flutter: dependency: "direct main" description: name: refena_flutter - sha256: "2e57bab72667f37b55f26675d46c0a362e6eb115a70605c90163736b85e97a42" + sha256: "5f090b0ecc7079161d6fff4bad3644df3984bc23b42aabc171d638caf5d6a264" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.1.0" refena_inspector: dependency: "direct dev" description: name: refena_inspector - sha256: "717a19f70d9667e2459fd8dfd6a72b1306bd2c04bfc255bada4f2eb429ca4757" + sha256: "2b2b8dc85c908c4c1ce733f53b4d505496c67944a98fb3586eb2eb7a4e62d7a7" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.1.0" refena_inspector_client: dependency: "direct main" description: name: refena_inspector_client - sha256: "8bcc1e169bfc0e5ba448f4920067a0579c0b4a42fff39dbdfd4a743bf2b235a6" + sha256: "88f41a0b55aae12a88ad40c33b0b1593581ead0c32b0bcfed1115daf6d54e6ce" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" rhttp: dependency: "direct main" description: name: rhttp - sha256: "3deabc6c3384b4efa252dfb4a5059acc6530117fdc1b10f5f67ff9768c9af75a" + sha256: e8f0a8756a931598b7d9c4107a4afe93c2b81cbf8c45e532ba9dc52e12bbaa35 url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.13.0" routerino: dependency: "direct main" description: @@ -1395,6 +1403,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.0" + rust_lib_localsend_app: + dependency: "direct main" + description: + path: rust_builder + relative: true + source: path + version: "0.0.1" saf_stream: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 1f3a147a..e6d411bc 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: common: path: ../common connectivity_plus: 6.1.0 + convert: 3.1.2 dart_mappable: 4.3.0 desktop_drop: 0.5.0 device_apps: 2.2.0 @@ -29,6 +30,8 @@ dependencies: flutter_localizations: sdk: flutter flutter_markdown: 0.7.4+2 + flutter_rust_bridge: 2.11.1 + freezed_annotation: 2.4.4 gal: 2.3.0 glob: ^2.1.2 image: 4.3.0 @@ -53,10 +56,12 @@ dependencies: path_provider_foundation: 2.4.0 permission_handler: 11.3.1 pretty_qr_code: 3.3.0 - refena_flutter: 2.1.1 - refena_inspector_client: 2.0.1 - rhttp: 0.10.0 + refena_flutter: 3.1.0 + refena_inspector_client: 2.1.0 + rhttp: 0.13.0 routerino: 0.8.0 + rust_lib_localsend_app: + path: rust_builder saf_stream: 0.10.0 screen_retriever: 0.2.0 share_handler: 0.0.22 @@ -82,8 +87,9 @@ dev_dependencies: dart_mappable_builder: 4.3.0 flutter_gen_runner: 5.8.0 flutter_lints: 5.0.0 + freezed: 2.5.7 mockito: 5.4.4 - refena_inspector: 2.0.3 + refena_inspector: 2.1.0 slang_build_runner: 4.5.0 test: ^1.24.8 diff --git a/app/rust/.gitignore b/app/rust/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/app/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock new file mode 100644 index 00000000..89712c41 --- /dev/null +++ b/app/rust/Cargo.lock @@ -0,0 +1,3520 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allo-isolate" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f67642eb6773fb42a95dd3b348c305ee18dee6642274c6b412d67e985e3befc" +dependencies = [ + "anyhow", + "atomic", + "backtrace", + "uuid", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.11", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.1", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.0", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.3", +] + +[[package]] +name = "flutter_rust_bridge" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725" +dependencies = [ + "allo-isolate", + "android_logger", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interceptor" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ab04c530fd82e414e40394cabe5f0ebfe30d119f10fe29d6e3561926af412e" +dependencies = [ + "async-trait", + "bytes", + "log", + "portable-atomic", + "rand", + "rtcp", + "rtp", + "thiserror 1.0.69", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "localsend" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "bytes", + "ed25519-dalek", + "flate2", + "futures-util", + "http-body-util", + "hyper", + "hyper-util", + "pem", + "rand", + "reqwest", + "rsa", + "rustls", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.11", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", + "tungstenite", + "uuid", + "webrtc", + "x509-parser 0.17.0", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264c56d1492c13e769662197fb6b94e0a52abe52d27efac374615799a4bf453d" +dependencies = [ + "asn1-rs 0.7.0", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +dependencies = [ + "bitflags 2.7.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.16.0", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rtcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8306430fb118b7834bbee50e744dc34826eca1da2158657a3d6cbc70e24c2096" +dependencies = [ + "bytes", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68baca5b6cb4980678713f0d06ef3a432aa642baefcbfd0f4dd2ef9eb5ab550" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand", + "serde", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rust_lib_localsend_app" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "flutter_rust_bridge", + "localsend", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.7.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a526161f474ae94b966ba622379d939a8fe46c930eebbadb73e339622599d5" +dependencies = [ + "rand", + "substring", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.7.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stun" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea256fb46a13f9204e9dee9982997b2c3097db175a9fddaa8350310d03c4d5a3" +dependencies = [ + "base64", + "crc", + "lazy_static", + "md-5", + "rand", + "ring", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.7.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.11", + "utf-8", +] + +[[package]] +name = "turn" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0044fdae001dd8a1e247ea6289abf12f4fcea1331a2364da512f9cd680bbd8cb" +dependencies = [ + "async-trait", + "base64", + "futures", + "log", + "md-5", + "portable-atomic", + "rand", + "ring", + "stun", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "webrtc-util", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webrtc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30367074d9f18231d28a74fab0120856b2b665da108d71a12beab7185a36f97b" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror 1.0.69", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec93b991efcd01b73c5b3503fa8adba159d069abe5785c988ebe14fcf8f05d1" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.69", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c9b89fc909f9da0499283b1112cd98f72fec28e55a54a9e352525ca65cd95c" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser 9.0.0", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "portable-atomic", + "rand", + "rand_core", + "rcgen", + "ring", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser 0.16.0", +] + +[[package]] +name = "webrtc-ice" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348b28b593f7709ac98d872beb58c0009523df652c78e01b950ab9c537ff17d" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand", + "serde", + "serde_json", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dfe9686c6c9c51428da4de415cb6ca2dc0591ce2b63212e23fd9cccf0e316b" +dependencies = [ + "log", + "socket2", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e153be16b8650021ad3e9e49ab6e5fa9fb7f6d1c23c213fd8bbd1a1135a4c704" +dependencies = [ + "byteorder", + "bytes", + "rand", + "rtp", + "thiserror 1.0.69", +] + +[[package]] +name = "webrtc-sctp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5faf3846ec4b7e64b56338d62cbafe084aa79806b0379dff5cc74a8b7a2b3063" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771db9993712a8fb3886d5be4613ebf27250ef422bd4071988bf55f1ed1a64fa" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1438a8fd0d69c5775afb4a71470af92242dbd04059c61895163aa3c1ef933375" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "portable-atomic", + "rand", + "thiserror 1.0.69", + "tokio", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry 0.7.1", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.0", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.0", + "ring", + "rusticata-macros", + "thiserror 2.0.11", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml new file mode 100644 index 00000000..c79e0872 --- /dev/null +++ b/app/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust_lib_localsend_app" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +anyhow = "1.0.95" +bytes = "1.9.0" +flutter_rust_bridge = { version = "=2.11.1", features = ["uuid"] } +localsend = { path = "../../core", features = ["full"] } +tokio = { version = "1.43.0", features = ["full"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19" } +uuid = { version = "1.11.1", features = ["v4"] } diff --git a/app/rust/src/api/crypto.rs b/app/rust/src/api/crypto.rs new file mode 100644 index 00000000..e2257528 --- /dev/null +++ b/app/rust/src/api/crypto.rs @@ -0,0 +1,21 @@ +pub fn verify_cert(cert: String, public_key: String) -> anyhow::Result<()> { + localsend::crypto::cert::verify_cert_from_pem(cert, Some(public_key)) +} + +pub fn generate_key_pair() -> anyhow::Result { + let signing_key = localsend::crypto::token::generate_key(); + let private_key = localsend::crypto::token::export_private_key(&signing_key)?; + let public_key = localsend::crypto::token::export_public_key(&signing_key)?; + + Ok( + KeyPair { + private_key: private_key.to_string(), + public_key, + } + ) +} + +pub struct KeyPair { + pub private_key: String, + pub public_key: String, +} diff --git a/app/rust/src/api/logging.rs b/app/rust/src/api/logging.rs new file mode 100644 index 00000000..124507fb --- /dev/null +++ b/app/rust/src/api/logging.rs @@ -0,0 +1,11 @@ +use anyhow::Result; +use tracing::Level; + +pub fn enable_debug_logging() -> Result<()> { + tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .try_init() + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + + Ok(()) +} diff --git a/app/rust/src/api/mod.rs b/app/rust/src/api/mod.rs new file mode 100644 index 00000000..22dc981e --- /dev/null +++ b/app/rust/src/api/mod.rs @@ -0,0 +1,4 @@ +pub mod crypto; +pub mod logging; +pub mod model; +pub mod webrtc; diff --git a/app/rust/src/api/model.rs b/app/rust/src/api/model.rs new file mode 100644 index 00000000..47764cc7 --- /dev/null +++ b/app/rust/src/api/model.rs @@ -0,0 +1,72 @@ +use flutter_rust_bridge::frb; +pub use localsend::model::discovery::{DeviceType, ProtocolType, RegisterDto, RegisterResponseDto}; +pub use localsend::model::transfer::{ + FileDto, FileMetadata, PrepareUploadRequestDto, PrepareUploadResponseDto, +}; +use std::collections::HashMap; + +#[frb(mirror(RegisterDto))] +pub struct _RegisterDto { + pub alias: String, + pub version: String, + pub device_model: Option, + pub device_type: Option, + pub fingerprint: String, + pub port: u16, + pub protocol: ProtocolType, + pub download: bool, +} + +#[frb(mirror(RegisterResponseDto))] +pub struct _RegisterResponseDto { + pub alias: String, + pub version: String, + pub device_model: Option, + pub device_type: Option, + pub fingerprint: String, + pub download: bool, +} + +#[frb(mirror(DeviceType))] +pub enum _DeviceType { + Mobile, + Desktop, + Web, + Headless, + Server, +} + +#[frb(mirror(ProtocolType))] +pub enum _ProtocolType { + Http, + Https, +} + +#[frb(mirror(FileDto))] +pub struct _FileDto { + pub id: String, + pub file_name: String, + pub size: u64, + pub file_type: String, + pub sha256: Option, + pub preview: Option, + pub metadata: Option, +} + +#[frb(mirror(FileMetadata))] +pub struct _FileMetadata { + pub modified: Option, + pub accessed: Option, +} + +#[frb(mirror(PrepareUploadRequestDto))] +pub struct _PrepareUploadRequestDto { + pub info: RegisterDto, + pub files: HashMap, +} + +#[frb(mirror(PrepareUploadResponseDto))] +pub struct _PrepareUploadResponseDto { + pub session_id: String, + pub files: HashMap, +} diff --git a/app/rust/src/api/webrtc.rs b/app/rust/src/api/webrtc.rs new file mode 100644 index 00000000..1f8b08ac --- /dev/null +++ b/app/rust/src/api/webrtc.rs @@ -0,0 +1,514 @@ +use crate::frb_generated::StreamSink; +use bytes::Bytes; +use flutter_rust_bridge::{frb, DartFnFuture}; +use localsend::crypto::token::SigningTokenKey; +use localsend::model::discovery::DeviceType; +use localsend::model::transfer::FileDto; +pub use localsend::webrtc::signaling::{ + ClientInfo, ClientInfoWithoutId, ManagedSignalingConnection, SignalingConnection, + WsServerMessage, WsServerSdpMessage, +}; +pub use localsend::webrtc::webrtc::{ + PinConfig, RTCFile, RTCFileError, RTCSendFileResponse, RTCStatus, +}; +use std::collections::HashSet; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{mpsc, oneshot, Mutex}; +use tokio::time; +use uuid::Uuid; + +pub struct ProposingClientInfo { + pub alias: String, + pub version: String, + pub device_model: Option, + pub device_type: Option, +} + +impl ProposingClientInfo { + fn sign(&self, signing_key: &SigningTokenKey) -> anyhow::Result { + Ok(ClientInfoWithoutId { + alias: self.alias.clone(), + version: self.version.clone(), + device_model: self.device_model.clone(), + device_type: self.device_type.clone(), + token: localsend::crypto::token::generate_token_timestamp(&signing_key)?, + }) + } +} + +pub async fn connect( + sink: StreamSink, + uri: String, + info: ProposingClientInfo, + private_key: String, + on_connection: impl Fn(LsSignalingConnection) -> DartFnFuture<()>, +) { + let Ok(signing_key) = localsend::crypto::token::parse_private_key(&private_key) else { + let _ = sink.add_error(anyhow::anyhow!("Invalid private key")); + return; + }; + + let Ok(client_info) = info.sign(&signing_key) else { + let _ = sink.add_error(anyhow::anyhow!("Invalid client info")); + return; + }; + + let connection = match SignalingConnection::connect(uri, &client_info).await { + Ok(connection) => connection, + Err(e) => { + let _ = sink.add_error(e.to_string()); + return; + } + }; + + let (managed_connection, mut rx) = connection.start_listener(); + on_connection(LsSignalingConnection { + inner: Arc::new(managed_connection), + }) + .await; + + while let Some(message) = rx.recv().await { + let _ = sink.add(message.into()); + } +} + +pub struct LsSignalingConnection { + inner: Arc, +} + +impl LsSignalingConnection { + pub async fn update_info(&self, info: ClientInfoWithoutId) -> anyhow::Result<()> { + self.inner.send_update(info).await?; + Ok(()) + } + + pub async fn send_offer( + &self, + stun_servers: Vec, + target: Uuid, + private_key: &str, + expecting_public_key: Option, + pin: Option, + files: Vec, + ) -> anyhow::Result { + let (status_tx, status_rx) = mpsc::channel::(1); + let (selected_tx, selected_rx) = oneshot::channel::>(); + let (error_tx, error_rx) = mpsc::channel::(1); + let (pin_tx, mut pin_rx) = mpsc::channel::>(1); + let (pair_tx, pair_rx) = oneshot::channel::>(); + let (send_tx, send_rx) = mpsc::channel::(1); + + let managed_connection = self.inner.clone(); + + let signing_key = localsend::crypto::token::parse_private_key(private_key)?; + let expecting_public_key = match expecting_public_key { + Some(key) => Some(localsend::crypto::token::parse_public_key( + &key.public_key, + &key.kind, + )?), + None => None, + }; + + tokio::spawn(async move { + let result = localsend::webrtc::webrtc::send_offer( + &managed_connection, + stun_servers, + target, + signing_key, + expecting_public_key, + pin, + files, + status_tx.clone(), + selected_tx, + error_tx, + pin_tx, + pair_tx, + send_rx, + ) + .await; + + if let Err(e) = result { + let _ = status_tx.send(RTCStatus::Error(e.to_string())).await; + } + }); + + tokio::spawn(async move { + // TODO: support pairing + let Ok(pair_tx) = pair_rx.await else { + return; + }; + + let _ = pair_tx.send(false); + }); + + let pin_sender = Arc::new(Mutex::new(None)); + + tokio::spawn({ + let pin_sender = Arc::clone(&pin_sender); + async move { + while let Some(pin_tx) = pin_rx.recv().await { + *pin_sender.lock().await = Some(pin_tx); + } + } + }); + + Ok(RTCSendController { + status_rx, + selected_rx: Arc::new(Mutex::new(Some(selected_rx))), + error_rx, + pin_tx: pin_sender, + send_tx, + }) + } + + pub async fn accept_offer( + &self, + stun_servers: Vec, + offer: WsServerSdpMessage, + private_key: &str, + expecting_public_key: Option, + pin: Option, + ) -> anyhow::Result { + let (status_tx, status_rx) = mpsc::channel::(1); + let (files_tx, files_rx) = oneshot::channel::>(); + let (selected_tx, selected_rx) = oneshot::channel::>>(); + let (error_tx, error_rx) = mpsc::channel::(1); + let (receiving_tx, receiving_rx) = mpsc::channel::(1); + let (pin_tx, mut pin_rx) = mpsc::channel::>(1); + let (file_status_tx, file_status_rx) = mpsc::channel::(1); + + let managed_connection = self.inner.clone(); + + let signing_key = localsend::crypto::token::parse_private_key(private_key)?; + let expecting_public_key = match expecting_public_key { + Some(key) => Some(localsend::crypto::token::parse_public_key( + &key.public_key, + &key.kind, + )?), + None => None, + }; + + tokio::spawn(async move { + let result = localsend::webrtc::webrtc::accept_offer( + &managed_connection, + stun_servers, + &offer, + signing_key, + expecting_public_key, + pin, + status_tx.clone(), + files_tx, + selected_rx, + error_tx, + pin_tx, + receiving_tx, + file_status_rx, + ) + .await; + + if let Err(e) = result { + let _ = status_tx.send(RTCStatus::Error(e.to_string())).await; + } + }); + + let pin_sender = Arc::new(Mutex::new(None)); + + tokio::spawn({ + let pin_sender = Arc::clone(&pin_sender); + async move { + while let Some(pin_tx) = pin_rx.recv().await { + *pin_sender.lock().await = Some(pin_tx); + } + } + }); + + Ok(RTCReceiveController { + status_rx: Arc::new(Mutex::new(Some(status_rx))), + files_rx: Arc::new(Mutex::new(Some(files_rx))), + selected_tx: Arc::new(Mutex::new(Some(selected_tx))), + error_rx: Arc::new(Mutex::new(Some(error_rx))), + pin_tx: pin_sender, + receiving_rx: Arc::new(Mutex::new(Some(receiving_rx))), + file_status_tx, + }) + } +} + +pub struct ExpectingPublicKey { + pub public_key: String, + + /// "ed25519" or "rsa-pss" + pub kind: String, +} + +pub struct RTCSendController { + status_rx: mpsc::Receiver, + selected_rx: Arc>>>>, + error_rx: mpsc::Receiver, + pin_tx: Arc>>>, + send_tx: mpsc::Sender, +} + +impl RTCSendController { + pub async fn listen_status(&mut self, sink: StreamSink) { + while let Some(status) = self.status_rx.recv().await { + let _ = sink.add(status); + } + } + + pub async fn listen_selected_files(&self) -> anyhow::Result> { + let Some(selected_rx) = self.selected_rx.lock().await.take() else { + return Err(anyhow::anyhow!("Selected files already received")); + }; + + let Ok(selected) = selected_rx.await else { + return Err(anyhow::anyhow!("Selected files channel closed")); + }; + + Ok(selected) + } + + pub async fn listen_error(&mut self, sink: StreamSink) { + while let Some(error) = self.error_rx.recv().await { + let _ = sink.add(error); + } + } + + pub async fn send_pin(&self, pin: String) -> anyhow::Result<()> { + let Some(pin_tx) = self.pin_tx.lock().await.take() else { + return Err(anyhow::anyhow!("Pin already sent")); + }; + + pin_tx + .send(pin) + .map_err(|_| anyhow::anyhow!("Pin channel closed"))?; + + Ok(()) + } + + pub async fn send_file(&self, file_id: String) -> anyhow::Result { + let (tx, rx) = mpsc::channel::(1); + self.send_tx + .send(RTCFile { + file_id, + binary_rx: rx, + }) + .await?; + + Ok(RTCFileSender { binary_tx: tx }) + } +} + +pub struct RTCFileSender { + binary_tx: mpsc::Sender, +} + +impl RTCFileSender { + pub async fn send(&self, data: Vec) -> anyhow::Result<()> { + self.binary_tx.send(Bytes::from(data)).await?; + Ok(()) + } +} + +pub struct RTCReceiveController { + status_rx: Arc>>>, + files_rx: Arc>>>>, + selected_tx: Arc>>>>>, + error_rx: Arc>>>, + pin_tx: Arc>>>, + receiving_rx: Arc>>>, + file_status_tx: mpsc::Sender, +} + +impl RTCReceiveController { + pub async fn listen_status(&self, sink: StreamSink) { + let Some(mut status_rx) = self.status_rx.lock().await.take() else { + let _ = sink.add_error(anyhow::anyhow!("Status stream already listened to")); + return; + }; + while let Some(status) = status_rx.recv().await { + let _ = sink.add(status); + } + } + + pub async fn listen_files(&self) -> anyhow::Result> { + let Some(files_rx) = self.files_rx.lock().await.take() else { + return Err(anyhow::anyhow!("Files already received")); + }; + + let Ok(files) = files_rx.await else { + return Err(anyhow::anyhow!("Files channel closed")); + }; + + Ok(files) + } + + pub async fn send_pin(&self, pin: String) -> anyhow::Result<()> { + let Some(pin_tx) = self.pin_tx.lock().await.take() else { + return Err(anyhow::anyhow!("Pin already sent")); + }; + + pin_tx + .send(pin) + .map_err(|_| anyhow::anyhow!("Pin channel closed"))?; + + Ok(()) + } + + pub async fn send_selection(&self, selection: HashSet) -> anyhow::Result<()> { + let Some(selected_tx) = self.selected_tx.lock().await.take() else { + return Err(anyhow::anyhow!("Selected files already sent")); + }; + + selected_tx + .send(Some(selection)) + .map_err(|_| anyhow::anyhow!("Selected files channel closed"))?; + + Ok(()) + } + + pub async fn decline(&self) -> anyhow::Result<()> { + let Some(selected_tx) = self.selected_tx.lock().await.take() else { + return Err(anyhow::anyhow!("Selected files already sent")); + }; + + selected_tx + .send(None) + .map_err(|_| anyhow::anyhow!("Selected files channel closed"))?; + + Ok(()) + } + + pub async fn listen_error(&self, sink: StreamSink) { + let Some(mut error_rx) = self.error_rx.lock().await.take() else { + let _ = sink.add_error(anyhow::anyhow!("Error stream already listened to")); + return; + }; + while let Some(error) = error_rx.recv().await { + let _ = sink.add(error); + } + } + + pub async fn listen_receiving(&self, sink: StreamSink) { + let Some(mut receiving_rx) = self.receiving_rx.lock().await.take() else { + let _ = sink.add_error(anyhow::anyhow!("Receiving stream already listened to")); + return; + }; + while let Some(file) = receiving_rx.recv().await { + let _ = sink.add(RTCFileReceiver { + file_id: file.file_id, + binary_rx: Arc::new(Mutex::new(Some(file.binary_rx))), + }); + } + } + + pub async fn send_file_status(&self, status: RTCSendFileResponse) -> anyhow::Result<()> { + self.file_status_tx.send(status).await?; + Ok(()) + } +} + +pub struct RTCFileReceiver { + file_id: String, + binary_rx: Arc>>>, +} + +impl RTCFileReceiver { + pub fn get_file_id(&self) -> String { + self.file_id.to_owned() + } + + pub async fn receive(&self, sink: StreamSink>) -> anyhow::Result<()> { + let Some(rx) = self.binary_rx.lock().await.take() else { + return Err(anyhow::anyhow!("File receiver listened to")); + }; + + let mut rx = crate::util::bytes::buffer_receiver(rx).await; + + while let Some(data) = rx.recv().await { + let _ = sink.add(data); + } + + Ok(()) + } +} + +#[frb(mirror(PinConfig))] +pub struct _PinConfig { + pub pin: String, + pub max_tries: u8, +} + +#[frb(mirror(WsServerMessage))] +pub enum _WsServerMessage { + Hello { + client: ClientInfo, + peers: Vec, + }, + Join { + peer: ClientInfo, + }, + Update { + peer: ClientInfo, + }, + Left { + peer_id: Uuid, + }, + Offer(WsServerSdpMessage), + Answer(WsServerSdpMessage), + Error { + code: u16, + }, +} + +#[frb(mirror(ClientInfo))] +pub struct _ClientInfo { + pub id: Uuid, + pub alias: String, + pub version: String, + pub device_model: Option, + pub device_type: Option, + pub token: String, +} + +#[frb(mirror(ClientInfoWithoutId))] +pub struct _ClientInfoWithoutId { + pub alias: String, + pub version: String, + pub device_model: Option, + pub device_type: Option, + pub token: String, +} + +#[frb(mirror(WsServerSdpMessage))] +pub struct _WsServerSdpMessage { + pub peer: ClientInfo, + pub session_id: String, + pub sdp: String, +} + +#[frb(mirror(RTCStatus))] +pub enum _RTCStatus { + SdpExchanged, + Connected, + PinRequired, + TooManyAttempts, + Declined, + Sending, + Finished, + Error(String), +} + +#[frb(mirror(RTCFileError))] +pub struct _RTCFileError { + pub file_id: String, + pub error: String, +} + +#[frb(mirror(RTCSendFileResponse))] +pub struct _RTCSendFileResponse { + pub id: String, + pub success: bool, + pub error: Option, +} diff --git a/app/rust/src/frb_generated.rs b/app/rust/src/frb_generated.rs new file mode 100644 index 00000000..c2e0ceb1 --- /dev/null +++ b/app/rust/src/frb_generated.rs @@ -0,0 +1,3392 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +#![allow( + non_camel_case_types, + unused, + non_snake_case, + clippy::needless_return, + clippy::redundant_closure_call, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::unused_unit, + clippy::double_parens, + clippy::let_and_return, + clippy::too_many_arguments, + clippy::match_single_binding, + clippy::clone_on_copy, + clippy::let_unit_value, + clippy::deref_addrof, + clippy::explicit_auto_deref, + clippy::borrow_deref_ref, + clippy::needless_borrow +)] + +// Section: imports + +use crate::api::webrtc::*; +use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; +use flutter_rust_bridge::{Handler, IntoIntoDart}; + +// Section: boilerplate + +flutter_rust_bridge::frb_generated_boilerplate!( + default_stream_sink_codec = SseCodec, + default_rust_opaque = RustOpaqueMoi, + default_rust_auto_opaque = RustAutoOpaqueMoi, +); +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1108156133; + +// Section: executor + +flutter_rust_bridge::frb_generated_default_handler!(); + +// Section: wire_funcs + +fn wire__crate__api__webrtc__LsSignalingConnection_accept_offer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "LsSignalingConnection_accept_offer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_stun_servers = >::sse_decode(&mut deserializer); + let api_offer = ::sse_decode(&mut deserializer); + let api_private_key = ::sse_decode(&mut deserializer); + let api_expecting_public_key = + >::sse_decode(&mut deserializer); + let api_pin = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::LsSignalingConnection::accept_offer( + &*api_that_guard, + api_stun_servers, + api_offer, + &api_private_key, + api_expecting_public_key, + api_pin, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__LsSignalingConnection_send_offer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "LsSignalingConnection_send_offer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_stun_servers = >::sse_decode(&mut deserializer); + let api_target = ::sse_decode(&mut deserializer); + let api_private_key = ::sse_decode(&mut deserializer); + let api_expecting_public_key = + >::sse_decode(&mut deserializer); + let api_pin = >::sse_decode(&mut deserializer); + let api_files = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::LsSignalingConnection::send_offer( + &*api_that_guard, + api_stun_servers, + api_target, + &api_private_key, + api_expecting_public_key, + api_pin, + api_files, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__LsSignalingConnection_update_info_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "LsSignalingConnection_update_info", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_info = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::LsSignalingConnection::update_info( + &*api_that_guard, + api_info, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcFileReceiver_get_file_id_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcFileReceiver_get_file_id", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok( + crate::api::webrtc::RTCFileReceiver::get_file_id(&*api_that_guard), + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcFileReceiver_receive_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcFileReceiver_receive", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = + , flutter_rust_bridge::for_generated::SseCodec>>::sse_decode( + &mut deserializer, + ); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCFileReceiver::receive( + &*api_that_guard, + api_sink, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcFileSender_send_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcFileSender_send", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_data = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::api::webrtc::RTCFileSender::send(&*api_that_guard, api_data) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_decline_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_decline", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::api::webrtc::RTCReceiveController::decline(&*api_that_guard) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_listen_error_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_listen_error", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::webrtc::RTCReceiveController::listen_error( + &*api_that_guard, + api_sink, + ) + .await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_listen_files_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_listen_files", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCReceiveController::listen_files( + &*api_that_guard, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_listen_receiving_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_listen_receiving", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::webrtc::RTCReceiveController::listen_receiving( + &*api_that_guard, + api_sink, + ) + .await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_listen_status_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_listen_status", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::webrtc::RTCReceiveController::listen_status( + &*api_that_guard, + api_sink, + ) + .await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_send_file_status_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_send_file_status", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_status = + ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCReceiveController::send_file_status( + &*api_that_guard, + api_status, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_send_pin_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_send_pin", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_pin = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCReceiveController::send_pin( + &*api_that_guard, + api_pin, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcReceiveController_send_selection_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcReceiveController_send_selection", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_selection = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCReceiveController::send_selection( + &*api_that_guard, + api_selection, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcSendController_listen_error_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcSendController_listen_error", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, true, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref_mut().await) + } + _ => unreachable!(), + } + } + let mut api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::webrtc::RTCSendController::listen_error( + &mut *api_that_guard, + api_sink, + ) + .await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcSendController_listen_selected_files_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcSendController_listen_selected_files", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::api::webrtc::RTCSendController::listen_selected_files( + &*api_that_guard, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcSendController_listen_status_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcSendController_listen_status", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_sink = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, true, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref_mut().await) + } + _ => unreachable!(), + } + } + let mut api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::api::webrtc::RTCSendController::listen_status( + &mut *api_that_guard, + api_sink, + ) + .await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcSendController_send_file_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcSendController_send_file", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_file_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCSendController::send_file( + &*api_that_guard, + api_file_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__RtcSendController_send_pin_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "RtcSendController_send_pin", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_pin = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::webrtc::RTCSendController::send_pin( + &*api_that_guard, + api_pin, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__webrtc__connect_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "connect", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || { + let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) }; + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_sink = >::sse_decode(&mut deserializer); +let api_uri = ::sse_decode(&mut deserializer); +let api_info = ::sse_decode(&mut deserializer); +let api_private_key = ::sse_decode(&mut deserializer); +let api_on_connection = decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException(::sse_decode(&mut deserializer));deserializer.end(); move |context| async move { + transform_result_sse::<_, ()>((move || async move { + let output_ok = Result::<_,()>::Ok({ crate::api::webrtc::connect(api_sink, api_uri, api_info, api_private_key, api_on_connection).await; })?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__api__logging__enable_debug_logging_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "enable_debug_logging", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::logging::enable_debug_logging()?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__crypto__generate_key_pair_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "generate_key_pair", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::crypto::generate_key_pair()?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__crypto__verify_cert_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "verify_cert", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_cert = ::sse_decode(&mut deserializer); + let api_public_key = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::crypto::verify_cert(api_cert, api_public_key)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} + +// Section: static_checks + +#[allow(clippy::unnecessary_literal_unwrap)] +const _: fn() = || { + { + let ClientInfo = None::.unwrap(); + let _: uuid::Uuid = ClientInfo.id; + let _: String = ClientInfo.alias; + let _: String = ClientInfo.version; + let _: Option = ClientInfo.device_model; + let _: Option = ClientInfo.device_type; + let _: String = ClientInfo.token; + } + { + let ClientInfoWithoutId = None::.unwrap(); + let _: String = ClientInfoWithoutId.alias; + let _: String = ClientInfoWithoutId.version; + let _: Option = ClientInfoWithoutId.device_model; + let _: Option = ClientInfoWithoutId.device_type; + let _: String = ClientInfoWithoutId.token; + } + { + let FileDto = None::.unwrap(); + let _: String = FileDto.id; + let _: String = FileDto.file_name; + let _: u64 = FileDto.size; + let _: String = FileDto.file_type; + let _: Option = FileDto.sha256; + let _: Option = FileDto.preview; + let _: Option = FileDto.metadata; + } + { + let FileMetadata = None::.unwrap(); + let _: Option = FileMetadata.modified; + let _: Option = FileMetadata.accessed; + } + { + let PinConfig = None::.unwrap(); + let _: String = PinConfig.pin; + let _: u8 = PinConfig.max_tries; + } + { + let RTCFileError = None::.unwrap(); + let _: String = RTCFileError.file_id; + let _: String = RTCFileError.error; + } + { + let RTCSendFileResponse = None::.unwrap(); + let _: String = RTCSendFileResponse.id; + let _: bool = RTCSendFileResponse.success; + let _: Option = RTCSendFileResponse.error; + } + match None::.unwrap() { + crate::api::webrtc::RTCStatus::SdpExchanged => {} + crate::api::webrtc::RTCStatus::Connected => {} + crate::api::webrtc::RTCStatus::PinRequired => {} + crate::api::webrtc::RTCStatus::TooManyAttempts => {} + crate::api::webrtc::RTCStatus::Declined => {} + crate::api::webrtc::RTCStatus::Sending => {} + crate::api::webrtc::RTCStatus::Finished => {} + crate::api::webrtc::RTCStatus::Error(field0) => { + let _: String = field0; + } + } + match None::.unwrap() { + crate::api::webrtc::WsServerMessage::Hello { client, peers } => { + let _: crate::api::webrtc::ClientInfo = client; + let _: Vec = peers; + } + crate::api::webrtc::WsServerMessage::Join { peer } => { + let _: crate::api::webrtc::ClientInfo = peer; + } + crate::api::webrtc::WsServerMessage::Update { peer } => { + let _: crate::api::webrtc::ClientInfo = peer; + } + crate::api::webrtc::WsServerMessage::Left { peer_id } => { + let _: uuid::Uuid = peer_id; + } + crate::api::webrtc::WsServerMessage::Offer(field0) => { + let _: crate::api::webrtc::WsServerSdpMessage = field0; + } + crate::api::webrtc::WsServerMessage::Answer(field0) => { + let _: crate::api::webrtc::WsServerSdpMessage = field0; + } + crate::api::webrtc::WsServerMessage::Error { code } => { + let _: u16 = code; + } + } + { + let WsServerSdpMessage = None::.unwrap(); + let _: crate::api::webrtc::ClientInfo = WsServerSdpMessage.peer; + let _: String = WsServerSdpMessage.session_id; + let _: String = WsServerSdpMessage.sdp; + } +}; + +// Section: related_funcs + +fn decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(LsSignalingConnection) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: LsSignalingConnection) -> () { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: LsSignalingConnection| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); + +// Section: dart2rust + +impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::anyhow::anyhow!("{}", inner); + } +} + +impl SseDecode for LsSignalingConnection { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for RTCFileReceiver { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for RTCFileSender { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for RTCReceiveController { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for RTCSendController { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for flutter_rust_bridge::DartOpaque { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return unsafe { flutter_rust_bridge::for_generated::sse_decode_dart_opaque(inner) }; + } +} + +impl SseDecode + for RustOpaqueMoi< + flutter_rust_bridge::for_generated::RustAutoOpaqueInner, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode for std::collections::HashSet { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return inner.into_iter().collect(); + } +} + +impl SseDecode for StreamSink { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + +impl SseDecode for StreamSink, flutter_rust_bridge::for_generated::SseCodec> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + +impl SseDecode + for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + +impl SseDecode + for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + +impl SseDecode + for StreamSink< + crate::api::webrtc::WsServerMessage, + flutter_rust_bridge::for_generated::SseCodec, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + +impl SseDecode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return String::from_utf8(inner).unwrap(); + } +} + +impl SseDecode for uuid::Uuid { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return uuid::Uuid::from_slice(&inner).expect("fail to decode uuid"); + } +} + +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +impl SseDecode for crate::api::webrtc::ClientInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_alias = ::sse_decode(deserializer); + let mut var_version = ::sse_decode(deserializer); + let mut var_deviceModel = >::sse_decode(deserializer); + let mut var_deviceType = >::sse_decode(deserializer); + let mut var_token = ::sse_decode(deserializer); + return crate::api::webrtc::ClientInfo { + id: var_id, + alias: var_alias, + version: var_version, + device_model: var_deviceModel, + device_type: var_deviceType, + token: var_token, + }; + } +} + +impl SseDecode for crate::api::webrtc::ClientInfoWithoutId { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_alias = ::sse_decode(deserializer); + let mut var_version = ::sse_decode(deserializer); + let mut var_deviceModel = >::sse_decode(deserializer); + let mut var_deviceType = >::sse_decode(deserializer); + let mut var_token = ::sse_decode(deserializer); + return crate::api::webrtc::ClientInfoWithoutId { + alias: var_alias, + version: var_version, + device_model: var_deviceModel, + device_type: var_deviceType, + token: var_token, + }; + } +} + +impl SseDecode for crate::api::model::DeviceType { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return match inner { + 0 => crate::api::model::DeviceType::Mobile, + 1 => crate::api::model::DeviceType::Desktop, + 2 => crate::api::model::DeviceType::Web, + 3 => crate::api::model::DeviceType::Headless, + 4 => crate::api::model::DeviceType::Server, + _ => unreachable!("Invalid variant for DeviceType: {}", inner), + }; + } +} + +impl SseDecode for crate::api::webrtc::ExpectingPublicKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_publicKey = ::sse_decode(deserializer); + let mut var_kind = ::sse_decode(deserializer); + return crate::api::webrtc::ExpectingPublicKey { + public_key: var_publicKey, + kind: var_kind, + }; + } +} + +impl SseDecode for crate::api::model::FileDto { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_fileName = ::sse_decode(deserializer); + let mut var_size = ::sse_decode(deserializer); + let mut var_fileType = ::sse_decode(deserializer); + let mut var_sha256 = >::sse_decode(deserializer); + let mut var_preview = >::sse_decode(deserializer); + let mut var_metadata = >::sse_decode(deserializer); + return crate::api::model::FileDto { + id: var_id, + file_name: var_fileName, + size: var_size, + file_type: var_fileType, + sha256: var_sha256, + preview: var_preview, + metadata: var_metadata, + }; + } +} + +impl SseDecode for crate::api::model::FileMetadata { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_modified = >::sse_decode(deserializer); + let mut var_accessed = >::sse_decode(deserializer); + return crate::api::model::FileMetadata { + modified: var_modified, + accessed: var_accessed, + }; + } +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + +impl SseDecode for isize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() as _ + } +} + +impl SseDecode for crate::api::crypto::KeyPair { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_privateKey = ::sse_decode(deserializer); + let mut var_publicKey = ::sse_decode(deserializer); + return crate::api::crypto::KeyPair { + private_key: var_privateKey, + public_key: var_publicKey, + }; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for crate::api::webrtc::PinConfig { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_pin = ::sse_decode(deserializer); + let mut var_maxTries = ::sse_decode(deserializer); + return crate::api::webrtc::PinConfig { + pin: var_pin, + max_tries: var_maxTries, + }; + } +} + +impl SseDecode for crate::api::webrtc::ProposingClientInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_alias = ::sse_decode(deserializer); + let mut var_version = ::sse_decode(deserializer); + let mut var_deviceModel = >::sse_decode(deserializer); + let mut var_deviceType = >::sse_decode(deserializer); + return crate::api::webrtc::ProposingClientInfo { + alias: var_alias, + version: var_version, + device_model: var_deviceModel, + device_type: var_deviceType, + }; + } +} + +impl SseDecode for crate::api::webrtc::RTCFileError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_fileId = ::sse_decode(deserializer); + let mut var_error = ::sse_decode(deserializer); + return crate::api::webrtc::RTCFileError { + file_id: var_fileId, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::webrtc::RTCSendFileResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_success = ::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::webrtc::RTCSendFileResponse { + id: var_id, + success: var_success, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::webrtc::RTCStatus { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + return crate::api::webrtc::RTCStatus::SdpExchanged; + } + 1 => { + return crate::api::webrtc::RTCStatus::Connected; + } + 2 => { + return crate::api::webrtc::RTCStatus::PinRequired; + } + 3 => { + return crate::api::webrtc::RTCStatus::TooManyAttempts; + } + 4 => { + return crate::api::webrtc::RTCStatus::Declined; + } + 5 => { + return crate::api::webrtc::RTCStatus::Sending; + } + 6 => { + return crate::api::webrtc::RTCStatus::Finished; + } + 7 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::api::webrtc::RTCStatus::Error(var_field0); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for u16 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u16::().unwrap() + } +} + +impl SseDecode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() + } +} + +impl SseDecode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() + } +} + +impl SseDecode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} +} + +impl SseDecode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() as _ + } +} + +impl SseDecode for crate::api::webrtc::WsServerMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_client = ::sse_decode(deserializer); + let mut var_peers = >::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Hello { + client: var_client, + peers: var_peers, + }; + } + 1 => { + let mut var_peer = ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Join { peer: var_peer }; + } + 2 => { + let mut var_peer = ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Update { peer: var_peer }; + } + 3 => { + let mut var_peerId = ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Left { + peer_id: var_peerId, + }; + } + 4 => { + let mut var_field0 = + ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Offer(var_field0); + } + 5 => { + let mut var_field0 = + ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Answer(var_field0); + } + 6 => { + let mut var_code = ::sse_decode(deserializer); + return crate::api::webrtc::WsServerMessage::Error { code: var_code }; + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for crate::api::webrtc::WsServerSdpMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_peer = ::sse_decode(deserializer); + let mut var_sessionId = ::sse_decode(deserializer); + let mut var_sdp = ::sse_decode(deserializer); + return crate::api::webrtc::WsServerSdpMessage { + peer: var_peer, + session_id: var_sessionId, + sdp: var_sdp, + }; + } +} + +fn pde_ffi_dispatcher_primary_impl( + func_id: i32, + port: flutter_rust_bridge::for_generated::MessagePort, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 1 => wire__crate__api__webrtc__LsSignalingConnection_accept_offer_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 2 => wire__crate__api__webrtc__LsSignalingConnection_send_offer_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 3 => wire__crate__api__webrtc__LsSignalingConnection_update_info_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 4 => wire__crate__api__webrtc__RtcFileReceiver_get_file_id_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 5 => wire__crate__api__webrtc__RtcFileReceiver_receive_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 6 => wire__crate__api__webrtc__RtcFileSender_send_impl(port, ptr, rust_vec_len, data_len), + 7 => wire__crate__api__webrtc__RtcReceiveController_decline_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 8 => wire__crate__api__webrtc__RtcReceiveController_listen_error_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 9 => wire__crate__api__webrtc__RtcReceiveController_listen_files_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 10 => wire__crate__api__webrtc__RtcReceiveController_listen_receiving_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 11 => wire__crate__api__webrtc__RtcReceiveController_listen_status_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 12 => wire__crate__api__webrtc__RtcReceiveController_send_file_status_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 13 => wire__crate__api__webrtc__RtcReceiveController_send_pin_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 14 => wire__crate__api__webrtc__RtcReceiveController_send_selection_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 15 => wire__crate__api__webrtc__RtcSendController_listen_error_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 16 => wire__crate__api__webrtc__RtcSendController_listen_selected_files_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 17 => wire__crate__api__webrtc__RtcSendController_listen_status_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 18 => wire__crate__api__webrtc__RtcSendController_send_file_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 19 => wire__crate__api__webrtc__RtcSendController_send_pin_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 20 => wire__crate__api__webrtc__connect_impl(port, ptr, rust_vec_len, data_len), + 21 => { + wire__crate__api__logging__enable_debug_logging_impl(port, ptr, rust_vec_len, data_len) + } + 22 => wire__crate__api__crypto__generate_key_pair_impl(port, ptr, rust_vec_len, data_len), + 23 => wire__crate__api__crypto__verify_cert_impl(port, ptr, rust_vec_len, data_len), + _ => unreachable!(), + } +} + +fn pde_ffi_dispatcher_sync_impl( + func_id: i32, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + _ => unreachable!(), + } +} + +// Section: rust2dart + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> + for LsSignalingConnection +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} + +impl flutter_rust_bridge::IntoIntoDart> for RTCFileReceiver { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} + +impl flutter_rust_bridge::IntoIntoDart> for RTCFileSender { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> for RTCReceiveController { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} + +impl flutter_rust_bridge::IntoIntoDart> for RTCSendController { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.id.into_into_dart().into_dart(), + self.0.alias.into_into_dart().into_dart(), + self.0.version.into_into_dart().into_dart(), + self.0.device_model.into_into_dart().into_dart(), + self.0.device_type.into_into_dart().into_dart(), + self.0.token.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::ClientInfo +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.alias.into_into_dart().into_dart(), + self.0.version.into_into_dart().into_dart(), + self.0.device_model.into_into_dart().into_dart(), + self.0.device_type.into_into_dart().into_dart(), + self.0.token.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::ClientInfoWithoutId +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self.0 { + crate::api::model::DeviceType::Mobile => 0.into_dart(), + crate::api::model::DeviceType::Desktop => 1.into_dart(), + crate::api::model::DeviceType::Web => 2.into_dart(), + crate::api::model::DeviceType::Headless => 3.into_dart(), + crate::api::model::DeviceType::Server => 4.into_dart(), + _ => unreachable!(), + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::model::DeviceType +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::webrtc::ExpectingPublicKey { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.public_key.into_into_dart().into_dart(), + self.kind.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::webrtc::ExpectingPublicKey +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::webrtc::ExpectingPublicKey +{ + fn into_into_dart(self) -> crate::api::webrtc::ExpectingPublicKey { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.id.into_into_dart().into_dart(), + self.0.file_name.into_into_dart().into_dart(), + self.0.size.into_into_dart().into_dart(), + self.0.file_type.into_into_dart().into_dart(), + self.0.sha256.into_into_dart().into_dart(), + self.0.preview.into_into_dart().into_dart(), + self.0.metadata.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::model::FileDto +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.modified.into_into_dart().into_dart(), + self.0.accessed.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::model::FileMetadata +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::crypto::KeyPair { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.private_key.into_into_dart().into_dart(), + self.public_key.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::crypto::KeyPair {} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::crypto::KeyPair +{ + fn into_into_dart(self) -> crate::api::crypto::KeyPair { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.pin.into_into_dart().into_dart(), + self.0.max_tries.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::PinConfig +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::webrtc::ProposingClientInfo { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.alias.into_into_dart().into_dart(), + self.version.into_into_dart().into_dart(), + self.device_model.into_into_dart().into_dart(), + self.device_type.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::webrtc::ProposingClientInfo +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::webrtc::ProposingClientInfo +{ + fn into_into_dart(self) -> crate::api::webrtc::ProposingClientInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.file_id.into_into_dart().into_dart(), + self.0.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::RTCFileError +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.id.into_into_dart().into_dart(), + self.0.success.into_into_dart().into_dart(), + self.0.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::RTCSendFileResponse +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self.0 { + crate::api::webrtc::RTCStatus::SdpExchanged => [0.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::Connected => [1.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::PinRequired => [2.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::TooManyAttempts => [3.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::Declined => [4.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::Sending => [5.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::Finished => [6.into_dart()].into_dart(), + crate::api::webrtc::RTCStatus::Error(field0) => { + [7.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::RTCStatus +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self.0 { + crate::api::webrtc::WsServerMessage::Hello { client, peers } => [ + 0.into_dart(), + client.into_into_dart().into_dart(), + peers.into_into_dart().into_dart(), + ] + .into_dart(), + crate::api::webrtc::WsServerMessage::Join { peer } => { + [1.into_dart(), peer.into_into_dart().into_dart()].into_dart() + } + crate::api::webrtc::WsServerMessage::Update { peer } => { + [2.into_dart(), peer.into_into_dart().into_dart()].into_dart() + } + crate::api::webrtc::WsServerMessage::Left { peer_id } => { + [3.into_dart(), peer_id.into_into_dart().into_dart()].into_dart() + } + crate::api::webrtc::WsServerMessage::Offer(field0) => { + [4.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::api::webrtc::WsServerMessage::Answer(field0) => { + [5.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::api::webrtc::WsServerMessage::Error { code } => { + [6.into_dart(), code.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::WsServerMessage +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.peer.into_into_dart().into_dart(), + self.0.session_id.into_into_dart().into_dart(), + self.0.sdp.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::api::webrtc::WsServerSdpMessage +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(format!("{:?}", self), serializer); + } +} + +impl SseEncode for LsSignalingConnection { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + , + >>::sse_encode( + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), + serializer, + ); + } +} + +impl SseEncode for RTCFileReceiver { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for RTCFileSender { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for RTCReceiveController { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + , + >>::sse_encode( + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), + serializer, + ); + } +} + +impl SseEncode for RTCSendController { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for flutter_rust_bridge::DartOpaque { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.encode(), serializer); + } +} + +impl SseEncode + for RustOpaqueMoi< + flutter_rust_bridge::for_generated::RustAutoOpaqueInner, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode for std::collections::HashSet { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_iter().collect(), serializer); + } +} + +impl SseEncode for StreamSink { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + +impl SseEncode for StreamSink, flutter_rust_bridge::for_generated::SseCodec> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + +impl SseEncode + for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + +impl SseEncode + for StreamSink +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + +impl SseEncode + for StreamSink< + crate::api::webrtc::WsServerMessage, + flutter_rust_bridge::for_generated::SseCodec, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + +impl SseEncode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_bytes(), serializer); + } +} + +impl SseEncode for uuid::Uuid { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.as_bytes().to_vec(), serializer); + } +} + +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +impl SseEncode for crate::api::webrtc::ClientInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + ::sse_encode(self.alias, serializer); + ::sse_encode(self.version, serializer); + >::sse_encode(self.device_model, serializer); + >::sse_encode(self.device_type, serializer); + ::sse_encode(self.token, serializer); + } +} + +impl SseEncode for crate::api::webrtc::ClientInfoWithoutId { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.alias, serializer); + ::sse_encode(self.version, serializer); + >::sse_encode(self.device_model, serializer); + >::sse_encode(self.device_type, serializer); + ::sse_encode(self.token, serializer); + } +} + +impl SseEncode for crate::api::model::DeviceType { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode( + match self { + crate::api::model::DeviceType::Mobile => 0, + crate::api::model::DeviceType::Desktop => 1, + crate::api::model::DeviceType::Web => 2, + crate::api::model::DeviceType::Headless => 3, + crate::api::model::DeviceType::Server => 4, + _ => { + unimplemented!(""); + } + }, + serializer, + ); + } +} + +impl SseEncode for crate::api::webrtc::ExpectingPublicKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.public_key, serializer); + ::sse_encode(self.kind, serializer); + } +} + +impl SseEncode for crate::api::model::FileDto { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + ::sse_encode(self.file_name, serializer); + ::sse_encode(self.size, serializer); + ::sse_encode(self.file_type, serializer); + >::sse_encode(self.sha256, serializer); + >::sse_encode(self.preview, serializer); + >::sse_encode(self.metadata, serializer); + } +} + +impl SseEncode for crate::api::model::FileMetadata { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.modified, serializer); + >::sse_encode(self.accessed, serializer); + } +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + +impl SseEncode for isize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer + .cursor + .write_i64::(self as _) + .unwrap(); + } +} + +impl SseEncode for crate::api::crypto::KeyPair { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.private_key, serializer); + ::sse_encode(self.public_key, serializer); + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for crate::api::webrtc::PinConfig { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.pin, serializer); + ::sse_encode(self.max_tries, serializer); + } +} + +impl SseEncode for crate::api::webrtc::ProposingClientInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.alias, serializer); + ::sse_encode(self.version, serializer); + >::sse_encode(self.device_model, serializer); + >::sse_encode(self.device_type, serializer); + } +} + +impl SseEncode for crate::api::webrtc::RTCFileError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.file_id, serializer); + ::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::webrtc::RTCSendFileResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + ::sse_encode(self.success, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::webrtc::RTCStatus { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::api::webrtc::RTCStatus::SdpExchanged => { + ::sse_encode(0, serializer); + } + crate::api::webrtc::RTCStatus::Connected => { + ::sse_encode(1, serializer); + } + crate::api::webrtc::RTCStatus::PinRequired => { + ::sse_encode(2, serializer); + } + crate::api::webrtc::RTCStatus::TooManyAttempts => { + ::sse_encode(3, serializer); + } + crate::api::webrtc::RTCStatus::Declined => { + ::sse_encode(4, serializer); + } + crate::api::webrtc::RTCStatus::Sending => { + ::sse_encode(5, serializer); + } + crate::api::webrtc::RTCStatus::Finished => { + ::sse_encode(6, serializer); + } + crate::api::webrtc::RTCStatus::Error(field0) => { + ::sse_encode(7, serializer); + ::sse_encode(field0, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for u16 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u16::(self).unwrap(); + } +} + +impl SseEncode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u64::(self).unwrap(); + } +} + +impl SseEncode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self).unwrap(); + } +} + +impl SseEncode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + +impl SseEncode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer + .cursor + .write_u64::(self as _) + .unwrap(); + } +} + +impl SseEncode for crate::api::webrtc::WsServerMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::api::webrtc::WsServerMessage::Hello { client, peers } => { + ::sse_encode(0, serializer); + ::sse_encode(client, serializer); + >::sse_encode(peers, serializer); + } + crate::api::webrtc::WsServerMessage::Join { peer } => { + ::sse_encode(1, serializer); + ::sse_encode(peer, serializer); + } + crate::api::webrtc::WsServerMessage::Update { peer } => { + ::sse_encode(2, serializer); + ::sse_encode(peer, serializer); + } + crate::api::webrtc::WsServerMessage::Left { peer_id } => { + ::sse_encode(3, serializer); + ::sse_encode(peer_id, serializer); + } + crate::api::webrtc::WsServerMessage::Offer(field0) => { + ::sse_encode(4, serializer); + ::sse_encode(field0, serializer); + } + crate::api::webrtc::WsServerMessage::Answer(field0) => { + ::sse_encode(5, serializer); + ::sse_encode(field0, serializer); + } + crate::api::webrtc::WsServerMessage::Error { code } => { + ::sse_encode(6, serializer); + ::sse_encode(code, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for crate::api::webrtc::WsServerSdpMessage { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.peer, serializer); + ::sse_encode(self.session_id, serializer); + ::sse_encode(self.sdp, serializer); + } +} + +#[cfg(not(target_family = "wasm"))] +mod io { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.11.1. + + // Section: imports + + use super::*; + use crate::api::webrtc::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_io!(); + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +mod web { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.11.1. + + // Section: imports + + use super::*; + use crate::api::webrtc::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::wasm_bindgen; + use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_web!(); + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileReceiver( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCFileSender( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCReceiveController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRTCSendController( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(target_family = "wasm")] +pub use web::*; diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs new file mode 100644 index 00000000..0f7ab0aa --- /dev/null +++ b/app/rust/src/lib.rs @@ -0,0 +1,3 @@ +pub mod api; +mod frb_generated; +mod util; diff --git a/app/rust/src/util/bytes.rs b/app/rust/src/util/bytes.rs new file mode 100644 index 00000000..1e6f7454 --- /dev/null +++ b/app/rust/src/util/bytes.rs @@ -0,0 +1,35 @@ +use bytes::{Bytes, BytesMut}; +use tokio::sync::mpsc; + +/// Converts a stream of Bytes into a stream of Vec. +/// Also buffers the incoming data to reduce the number of +/// messages sent to the receiver. +pub(crate) async fn buffer_receiver( + mut rx_input: mpsc::Receiver, +) -> mpsc::Receiver> { + const BUFFER_SIZE: usize = 1024 * 1024; // 1 MB + let mut buffer = BytesMut::with_capacity(BUFFER_SIZE); + + let (tx, rx) = mpsc::channel(1); + + tokio::spawn(async move { + while let Some(data) = rx_input.recv().await { + buffer.extend_from_slice(&data); + + if buffer.len() >= BUFFER_SIZE { + let data = buffer.to_vec(); + buffer.clear(); + tx.send(data).await?; + } + } + + if !buffer.is_empty() { + let data = buffer.to_vec(); + tx.send(data).await?; + } + + Ok::<(), anyhow::Error>(()) + }); + + rx +} diff --git a/app/rust/src/util/mod.rs b/app/rust/src/util/mod.rs new file mode 100644 index 00000000..14e49498 --- /dev/null +++ b/app/rust/src/util/mod.rs @@ -0,0 +1 @@ +pub(crate) mod bytes; diff --git a/app/rust_builder/.gitignore b/app/rust_builder/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/app/rust_builder/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/app/rust_builder/README.md b/app/rust_builder/README.md new file mode 100644 index 00000000..922615f9 --- /dev/null +++ b/app/rust_builder/README.md @@ -0,0 +1 @@ +Please ignore this folder, which is just glue to build Rust with Flutter. \ No newline at end of file diff --git a/app/rust_builder/android/.gitignore b/app/rust_builder/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/app/rust_builder/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/app/rust_builder/android/build.gradle b/app/rust_builder/android/build.gradle new file mode 100644 index 00000000..da276090 --- /dev/null +++ b/app/rust_builder/android/build.gradle @@ -0,0 +1,56 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.flutter_rust_bridge.rust_lib_localsend_app' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.flutter_rust_bridge.rust_lib_localsend_app' + } + + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + 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 specfic NDK version. + // (e.g. ndkVersion "23.1.7779620") + ndkVersion android.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + } +} + +apply from: "../cargokit/gradle/plugin.gradle" +cargokit { + manifestDir = "../../rust" + libname = "rust_lib_localsend_app" +} diff --git a/app/rust_builder/android/settings.gradle b/app/rust_builder/android/settings.gradle new file mode 100644 index 00000000..0e797a31 --- /dev/null +++ b/app/rust_builder/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'rust_lib_localsend_app' diff --git a/app/rust_builder/android/src/main/AndroidManifest.xml b/app/rust_builder/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3329401c --- /dev/null +++ b/app/rust_builder/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/app/rust_builder/cargokit/.gitignore b/app/rust_builder/cargokit/.gitignore new file mode 100644 index 00000000..cf7bb868 --- /dev/null +++ b/app/rust_builder/cargokit/.gitignore @@ -0,0 +1,4 @@ +target +.dart_tool +*.iml +!pubspec.lock diff --git a/app/rust_builder/cargokit/LICENSE b/app/rust_builder/cargokit/LICENSE new file mode 100644 index 00000000..d33a5fea --- /dev/null +++ b/app/rust_builder/cargokit/LICENSE @@ -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. + diff --git a/app/rust_builder/cargokit/README b/app/rust_builder/cargokit/README new file mode 100644 index 00000000..398474db --- /dev/null +++ b/app/rust_builder/cargokit/README @@ -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. + diff --git a/app/rust_builder/cargokit/build_pod.sh b/app/rust_builder/cargokit/build_pod.sh new file mode 100644 index 00000000..ed0e0d98 --- /dev/null +++ b/app/rust_builder/cargokit/build_pod.sh @@ -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" diff --git a/app/rust_builder/cargokit/build_tool/README.md b/app/rust_builder/cargokit/build_tool/README.md new file mode 100644 index 00000000..a878c279 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/README.md @@ -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/`. diff --git a/app/rust_builder/cargokit/build_tool/analysis_options.yaml b/app/rust_builder/cargokit/build_tool/analysis_options.yaml new file mode 100644 index 00000000..0e16a8b0 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/analysis_options.yaml @@ -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 diff --git a/app/rust_builder/cargokit/build_tool/bin/build_tool.dart b/app/rust_builder/cargokit/build_tool/bin/build_tool.dart new file mode 100644 index 00000000..268eb524 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/bin/build_tool.dart @@ -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 arguments) { + build_tool.runMain(arguments); +} diff --git a/app/rust_builder/cargokit/build_tool/lib/build_tool.dart b/app/rust_builder/cargokit/build_tool/lib/build_tool.dart new file mode 100644 index 00000000..7c1bb750 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/build_tool.dart @@ -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 runMain(List args) async { + return build_tool.runMain(args); +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/android_environment.dart b/app/rust_builder/cargokit/build_tool/lib/src/android_environment.dart new file mode 100644 index 00000000..15fc9eed --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/android_environment.dart @@ -0,0 +1,195 @@ +/// 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 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> 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'); + + 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, + // 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; + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart b/app/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart new file mode 100644 index 00000000..e608cece --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart @@ -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>> getArtifacts(List 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 = { + ...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>> _getPrecompiledArtifacts( + List 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 = >{}; + + for (final target in targets) { + final requiredArtifacts = getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + remote: true, + ); + final artifactsForTarget = []; + + 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 _get(Uri url, {Map? 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 _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 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}"); + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart b/app/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart new file mode 100644 index 00000000..6f3b2a4e --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart @@ -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 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)); + } + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart b/app/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart new file mode 100644 index 00000000..7e61fcbb --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart @@ -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 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)); + } + } + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/build_pod.dart b/app/rust_builder/cargokit/build_tool/lib/src/build_pod.dart new file mode 100644 index 00000000..8a9c0db5 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/build_pod.dart @@ -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 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 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'); + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/build_tool.dart b/app/rust_builder/cargokit/build_tool/lib/src/build_tool.dart new file mode 100644 index 00000000..c8f36981 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/build_tool.dart @@ -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 runBuildCommand(CargokitUserOptions options); + + @override + Future 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 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 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 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 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; + 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 run() async { + final manifestDir = argResults!['manifest-dir'] as String; + final verifyBinaries = VerifyBinaries( + manifestDir: manifestDir, + ); + await verifyBinaries.run(); + } +} + +Future runMain(List 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); + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/builder.dart b/app/rust_builder/cargokit/build_tool/lib/src/builder.dart new file mode 100644 index 00000000..84c46e4f --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/builder.dart @@ -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 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> _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(); + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/cargo.dart b/app/rust_builder/cargokit/build_tool/lib/src/cargo.dart new file mode 100644 index 00000000..0d8958ff --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/cargo.dart @@ -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); + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart b/app/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart new file mode 100644 index 00000000..0c4d88d1 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart @@ -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 files) { + final output = AccumulatorSink(); + 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 files) { + final output = AccumulatorSink(); + 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 getFiles() { + final src = Directory(path.join(manifestDir, 'src')); + final files = src + .listSync(recursive: true, followLinks: false) + .whereType() + .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; +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/environment.dart b/app/rust_builder/cargokit/build_tool/lib/src/environment.dart new file mode 100644 index 00000000..996483a1 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/environment.dart @@ -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 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 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; + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/logging.dart b/app/rust_builder/cargokit/build_tool/lib/src/logging.dart new file mode 100644 index 00000000..5edd4fd1 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/logging.dart @@ -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; +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/options.dart b/app/rust_builder/cargokit/build_tool/lib/src/options.dart new file mode 100644 index 00000000..22aef1d3 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/options.dart @@ -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 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 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 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 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 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 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 = {}; + 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; +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart b/app/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart new file mode 100644 index 00000000..c27f4195 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart @@ -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 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 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 = []; + 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 _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; + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/rustup.dart b/app/rust_builder/cargokit/build_tool/lib/src/rustup.dart new file mode 100644 index 00000000..0ac8d086 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/rustup.dart @@ -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 targets; +} + +class Rustup { + List? 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? _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 _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; + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/target.dart b/app/rust_builder/cargokit/build_tool/lib/src/target.dart new file mode 100644 index 00000000..6fbc58b6 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/target.dart @@ -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 androidTargets() { + return all + .where((element) => element.android != null) + .toList(growable: false); + } + + /// Returns buildable targets on current host platform ignoring Android targets. + static List 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; +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/util.dart b/app/rust_builder/cargokit/build_tool/lib/src/util.dart new file mode 100644 index 00000000..8bb6a872 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/util.dart @@ -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 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 arguments; + final String? workingDirectory; + final Map? 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 arguments, { + String? workingDirectory, + Map? 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; + } +} diff --git a/app/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart b/app/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart new file mode 100644 index 00000000..2366b57b --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart @@ -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 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'); + } + } + } + } +} diff --git a/app/rust_builder/cargokit/build_tool/pubspec.lock b/app/rust_builder/cargokit/build_tool/pubspec.lock new file mode 100644 index 00000000..343bdd36 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/pubspec.lock @@ -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" diff --git a/app/rust_builder/cargokit/build_tool/pubspec.yaml b/app/rust_builder/cargokit/build_tool/pubspec.yaml new file mode 100644 index 00000000..18c61e33 --- /dev/null +++ b/app/rust_builder/cargokit/build_tool/pubspec.yaml @@ -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 diff --git a/app/rust_builder/cargokit/cmake/cargokit.cmake b/app/rust_builder/cargokit/cmake/cargokit.cmake new file mode 100644 index 00000000..ddd05df9 --- /dev/null +++ b/app/rust_builder/cargokit/cmake/cargokit.cmake @@ -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}/$") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${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=$" + "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() diff --git a/app/rust_builder/cargokit/cmake/resolve_symlinks.ps1 b/app/rust_builder/cargokit/cmake/resolve_symlinks.ps1 new file mode 100644 index 00000000..3d10d283 --- /dev/null +++ b/app/rust_builder/cargokit/cmake/resolve_symlinks.ps1 @@ -0,0 +1,27 @@ +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 + $item = Get-Item $realPath + if ($item.Target) { + $realPath = $item.Target.Replace('\', '/') + } + } + $realPath +} + +$path=Resolve-Symlinks -Path $args[0] +Write-Host $path diff --git a/app/rust_builder/cargokit/gradle/plugin.gradle b/app/rust_builder/cargokit/gradle/plugin.gradle new file mode 100644 index 00000000..1aead891 --- /dev/null +++ b/app/rust_builder/cargokit/gradle/plugin.gradle @@ -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 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 { + + 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 == "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 = plugin.getTargetPlatforms().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 + } + } + } +} diff --git a/app/rust_builder/cargokit/run_build_tool.cmd b/app/rust_builder/cargokit/run_build_tool.cmd new file mode 100644 index 00000000..c45d0aa8 --- /dev/null +++ b/app/rust_builder/cargokit/run_build_tool.cmd @@ -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^ 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%" %* +) diff --git a/app/rust_builder/cargokit/run_build_tool.sh b/app/rust_builder/cargokit/run_build_tool.sh new file mode 100644 index 00000000..6e594a23 --- /dev/null +++ b/app/rust_builder/cargokit/run_build_tool.sh @@ -0,0 +1,94 @@ +#!/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 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 + +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 diff --git a/app/rust_builder/ios/Classes/dummy_file.c b/app/rust_builder/ios/Classes/dummy_file.c new file mode 100644 index 00000000..e06dab99 --- /dev/null +++ b/app/rust_builder/ios/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/app/rust_builder/ios/rust_lib_localsend_app.podspec b/app/rust_builder/ios/rust_lib_localsend_app.podspec new file mode 100644 index 00000000..4c31b7ef --- /dev/null +++ b/app/rust_builder/ios/rust_lib_localsend_app.podspec @@ -0,0 +1,45 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_lib_localsend_app.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_lib_localsend_app' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # 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/**/*' + s.dependency 'Flutter' + 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 rust_lib_localsend_app', + :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}/librust_lib_localsend_app.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}/librust_lib_localsend_app.a', + } +end \ No newline at end of file diff --git a/app/rust_builder/linux/CMakeLists.txt b/app/rust_builder/linux/CMakeLists.txt new file mode 100644 index 00000000..83ae2414 --- /dev/null +++ b/app/rust_builder/linux/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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-level configuration. +set(PROJECT_NAME "rust_lib_localsend_app") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../../rust rust_lib_localsend_app "") + +# 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(rust_lib_localsend_app_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/app/rust_builder/macos/Classes/dummy_file.c b/app/rust_builder/macos/Classes/dummy_file.c new file mode 100644 index 00000000..e06dab99 --- /dev/null +++ b/app/rust_builder/macos/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/app/rust_builder/macos/rust_lib_localsend_app.podspec b/app/rust_builder/macos/rust_lib_localsend_app.podspec new file mode 100644 index 00000000..aea92e59 --- /dev/null +++ b/app/rust_builder/macos/rust_lib_localsend_app.podspec @@ -0,0 +1,44 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_lib_localsend_app.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_lib_localsend_app' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # 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/**/*' + 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 rust_lib_localsend_app', + :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}/librust_lib_localsend_app.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}/librust_lib_localsend_app.a', + } +end \ No newline at end of file diff --git a/app/rust_builder/pubspec.yaml b/app/rust_builder/pubspec.yaml new file mode 100644 index 00000000..aca6b918 --- /dev/null +++ b/app/rust_builder/pubspec.yaml @@ -0,0 +1,34 @@ +name: rust_lib_localsend_app +description: "Bindings for the core Rust library" +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + ffi: ^2.0.2 + ffigen: ^11.0.0 + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + windows: + ffiPlugin: true diff --git a/app/rust_builder/windows/.gitignore b/app/rust_builder/windows/.gitignore new file mode 100644 index 00000000..b3eb2be1 --- /dev/null +++ b/app/rust_builder/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/app/rust_builder/windows/CMakeLists.txt b/app/rust_builder/windows/CMakeLists.txt new file mode 100644 index 00000000..864ef615 --- /dev/null +++ b/app/rust_builder/windows/CMakeLists.txt @@ -0,0 +1,20 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. 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.14) + +# Project-level configuration. +set(PROJECT_NAME "rust_lib_localsend_app") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../../../../../../rust rust_lib_localsend_app "") + +# 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(rust_lib_localsend_app_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/app/test/mocks.mocks.dart b/app/test/mocks.mocks.dart index ba071171..158c76bd 100644 --- a/app/test/mocks.mocks.dart +++ b/app/test/mocks.mocks.dart @@ -11,7 +11,8 @@ import 'package:flutter/material.dart' as _i8; import 'package:localsend_app/gen/strings.g.dart' as _i10; import 'package:localsend_app/model/persistence/color_mode.dart' as _i9; import 'package:localsend_app/model/persistence/favorite_device.dart' as _i6; -import 'package:localsend_app/model/persistence/receive_history_entry.dart' as _i5; +import 'package:localsend_app/model/persistence/receive_history_entry.dart' + as _i5; import 'package:localsend_app/model/send_mode.dart' as _i11; import 'package:localsend_app/provider/persistence_provider.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; @@ -31,7 +32,8 @@ import 'package:shared_preferences/shared_preferences.dart' as _i13; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeStoredSecurityContext_0 extends _i1.SmartFake implements _i2.StoredSecurityContext { +class _FakeStoredSecurityContext_0 extends _i1.SmartFake + implements _i2.StoredSecurityContext { _FakeStoredSecurityContext_0( Object parent, Invocation parentInvocation, @@ -44,7 +46,8 @@ class _FakeStoredSecurityContext_0 extends _i1.SmartFake implements _i2.StoredSe /// A class which mocks [PersistenceService]. /// /// See the documentation for Mockito's code generation for more information. -class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService { +class MockPersistenceService extends _i1.Mock + implements _i3.PersistenceService { @override bool get isFirstAppStart => (super.noSuchMethod( Invocation.getter(#isFirstAppStart), @@ -85,7 +88,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as _i2.StoredSecurityContext); @override - _i4.Future setSecurityContext(_i2.StoredSecurityContext? context) => (super.noSuchMethod( + _i4.Future setSecurityContext(_i2.StoredSecurityContext? context) => + (super.noSuchMethod( Invocation.method( #setSecurityContext, [context], @@ -94,6 +98,27 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + @override + _i4.Future setSignalingServers(List? servers) => + (super.noSuchMethod( + Invocation.method( + #setSignalingServers, + [servers], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setStunServers(List? servers) => (super.noSuchMethod( + Invocation.method( + #setStunServers, + [servers], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override List<_i5.ReceiveHistoryEntry> getReceiveHistory() => (super.noSuchMethod( Invocation.method( @@ -105,7 +130,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as List<_i5.ReceiveHistoryEntry>); @override - _i4.Future setReceiveHistory(List<_i5.ReceiveHistoryEntry>? entries) => (super.noSuchMethod( + _i4.Future setReceiveHistory(List<_i5.ReceiveHistoryEntry>? entries) => + (super.noSuchMethod( Invocation.method( #setReceiveHistory, [entries], @@ -125,7 +151,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as List<_i6.FavoriteDevice>); @override - _i4.Future setFavorites(List<_i6.FavoriteDevice>? entries) => (super.noSuchMethod( + _i4.Future setFavorites(List<_i6.FavoriteDevice>? entries) => + (super.noSuchMethod( Invocation.method( #setFavorites, [entries], @@ -259,7 +286,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as _i4.Future); @override - _i4.Future setNetworkWhitelist(List? whitelist) => (super.noSuchMethod( + _i4.Future setNetworkWhitelist(List? whitelist) => + (super.noSuchMethod( Invocation.method( #setNetworkWhitelist, [whitelist], @@ -269,7 +297,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as _i4.Future); @override - _i4.Future setNetworkBlacklist(List? blacklist) => (super.noSuchMethod( + _i4.Future setNetworkBlacklist(List? blacklist) => + (super.noSuchMethod( Invocation.method( #setNetworkBlacklist, [blacklist], @@ -309,7 +338,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setShareViaLinkAutoAccept(bool? shareViaLinkAutoAccept) => (super.noSuchMethod( + _i4.Future setShareViaLinkAutoAccept(bool? shareViaLinkAutoAccept) => + (super.noSuchMethod( Invocation.method( #setShareViaLinkAutoAccept, [shareViaLinkAutoAccept], @@ -411,7 +441,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setAdvancedSettingsEnabled(bool? isEnabled) => (super.noSuchMethod( + _i4.Future setAdvancedSettingsEnabled(bool? isEnabled) => + (super.noSuchMethod( Invocation.method( #setAdvancedSettingsEnabled, [isEnabled], @@ -451,7 +482,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setQuickSaveFromFavorites(bool? quickSaveFromFavorites) => (super.noSuchMethod( + _i4.Future setQuickSaveFromFavorites(bool? quickSaveFromFavorites) => + (super.noSuchMethod( Invocation.method( #setQuickSaveFromFavorites, [quickSaveFromFavorites], @@ -501,7 +533,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setMinimizeToTray(bool? minimizeToTray) => (super.noSuchMethod( + _i4.Future setMinimizeToTray(bool? minimizeToTray) => + (super.noSuchMethod( Invocation.method( #setMinimizeToTray, [minimizeToTray], @@ -591,7 +624,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as _i4.Future); @override - _i4.Future setSaveWindowPlacement(bool? savePlacement) => (super.noSuchMethod( + _i4.Future setSaveWindowPlacement(bool? savePlacement) => + (super.noSuchMethod( Invocation.method( #setSaveWindowPlacement, [savePlacement], @@ -611,7 +645,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setEnableAnimations(bool? enableAnimations) => (super.noSuchMethod( + _i4.Future setEnableAnimations(bool? enableAnimations) => + (super.noSuchMethod( Invocation.method( #setEnableAnimations, [enableAnimations], @@ -631,7 +666,8 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService ) as bool); @override - _i4.Future setDeviceType(_i12.DeviceType? deviceType) => (super.noSuchMethod( + _i4.Future setDeviceType(_i12.DeviceType? deviceType) => + (super.noSuchMethod( Invocation.method( #setDeviceType, [deviceType], diff --git a/app/test/unit/provider/last_devices_provider_test.dart b/app/test/unit/provider/last_devices_provider_test.dart index 448792d7..2068ec04 100644 --- a/app/test/unit/provider/last_devices_provider_test.dart +++ b/app/test/unit/provider/last_devices_provider_test.dart @@ -52,6 +52,7 @@ void main() { Device _createDevice(String ip) { return Device( + signalingId: null, ip: ip, version: '1', port: 123, @@ -61,5 +62,6 @@ Device _createDevice(String ip) { deviceModel: 'A', deviceType: DeviceType.mobile, download: false, + discoveryMethods: {}, ); } diff --git a/app/test/unit/util/api_route_builder_test.dart b/app/test/unit/util/api_route_builder_test.dart index c7f347d5..e99073c6 100644 --- a/app/test/unit/util/api_route_builder_test.dart +++ b/app/test/unit/util/api_route_builder_test.dart @@ -46,6 +46,7 @@ Device _target({ required bool https, }) { return Device( + signalingId: null, ip: '0.0.0.0', version: version, port: 8080, @@ -55,5 +56,6 @@ Device _target({ deviceModel: 'deviceModel', deviceType: DeviceType.desktop, download: false, + discoveryMethods: {}, ); } diff --git a/app/test/unit/util/security_helper_test.dart b/app/test/unit/util/security_helper_test.dart index 106e089c..8a88ba36 100644 --- a/app/test/unit/util/security_helper_test.dart +++ b/app/test/unit/util/security_helper_test.dart @@ -63,14 +63,15 @@ IWwwBbDLI6KuU/iqqvk/1syLDHqeaCdDqTmmyoaKKa7kUhkZkhIlLw== expect( context.publicKey, - '''-----BEGIN RSA PUBLIC KEY----- -MIIBCgKCAQEAi9uDMYRn63SZtEPGRogZGdu5XXBAoQeMO60mycoinqLKDWyZdpMo -+XWY3wYVhoAyxgzDOcPjIf+Uq1oEy/0K4WwfpbK8SCy851qgYkfMCT9D9mFvXwWo -ULJCUHFF7f947ArDE1nmuK1nNx2RodN2wJCXyzPjw0jn06bwGeg0EqfUC8wvW4FT -Z6t1tErzmRqRdMUWuCJwsk1IMbDbFePhiK5jecOBG0RVVWLuw+TkuX8TUgrpIktH -2+qEM1KdLyAMnL71hx2wMvE+lDKFKK9p37zXK8omjl+VgTC8ocjGeYDDsl43ZtW0 -9V0pb7Vz2FM8b7BgM06kvJl48PIe5puYbQIDAQAB ------END RSA PUBLIC KEY-----''', + '''-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi9uDMYRn63SZtEPGRogZ +Gdu5XXBAoQeMO60mycoinqLKDWyZdpMo+XWY3wYVhoAyxgzDOcPjIf+Uq1oEy/0K +4WwfpbK8SCy851qgYkfMCT9D9mFvXwWoULJCUHFF7f947ArDE1nmuK1nNx2RodN2 +wJCXyzPjw0jn06bwGeg0EqfUC8wvW4FTZ6t1tErzmRqRdMUWuCJwsk1IMbDbFePh +iK5jecOBG0RVVWLuw+TkuX8TUgrpIktH2+qEM1KdLyAMnL71hx2wMvE+lDKFKK9p +37zXK8omjl+VgTC8ocjGeYDDsl43ZtW09V0pb7Vz2FM8b7BgM06kvJl48PIe5puY +bQIDAQAB +-----END PUBLIC KEY-----''', ); }); }); diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 26c6a0fb..148a3c26 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -22,6 +22,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST rhttp + rust_lib_localsend_app ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/common/lib/model/device.dart b/common/lib/model/device.dart index 14ac40f3..a1c215b1 100644 --- a/common/lib/model/device.dart +++ b/common/lib/model/device.dart @@ -11,11 +11,50 @@ enum DeviceType { server, } +@MappableClass() +sealed class DiscoveryMethod with DiscoveryMethodMappable { + const DiscoveryMethod(); +} + +@MappableClass() +class MulticastDiscovery extends DiscoveryMethod with MulticastDiscoveryMappable { + const MulticastDiscovery(); +} + +@MappableClass() +class HttpDiscovery extends DiscoveryMethod with HttpDiscoveryMappable { + final String ip; + + const HttpDiscovery({required this.ip}); +} + +@MappableClass() +class SignalingDiscovery extends DiscoveryMethod with SignalingDiscoveryMappable { + final String signalingServer; + + const SignalingDiscovery({required this.signalingServer}); +} + +enum TransmissionMethod { + http('HTTP'), + webrtc('WebRTC'); + + final String label; + + const TransmissionMethod(this.label); +} + /// Internal device model. /// It gets not serialized. @MappableClass() class Device with DeviceMappable { - final String ip; + /// A unique ID provided by the signaling server. + final String? signalingId; + + /// The IP address of the device. + /// Is null when found via signaling. + final String? ip; + final String version; final int port; final bool https; @@ -24,8 +63,33 @@ class Device with DeviceMappable { final String? deviceModel; final DeviceType deviceType; final bool download; + final Set discoveryMethods; + + Set get transmissionMethods { + bool http = false; + bool webrtc = false; + + for (final method in discoveryMethods) { + if (method is SignalingDiscovery) { + webrtc = true; + } else { + http = true; + } + } + + final methods = {}; + if (http) { + methods.add(TransmissionMethod.http); + } + if (webrtc) { + methods.add(TransmissionMethod.webrtc); + } + + return methods; + } const Device({ + required this.signalingId, required this.ip, required this.version, required this.port, @@ -35,5 +99,20 @@ class Device with DeviceMappable { required this.deviceModel, required this.deviceType, required this.download, + required this.discoveryMethods, }); + + static const empty = Device( + signalingId: null, + ip: null, + version: '', + port: -1, + https: false, + fingerprint: '', + alias: '', + deviceModel: null, + deviceType: DeviceType.desktop, + download: false, + discoveryMethods: {}, + ); } diff --git a/common/lib/model/device.mapper.dart b/common/lib/model/device.mapper.dart index ea593c19..6acc184a 100644 --- a/common/lib/model/device.mapper.dart +++ b/common/lib/model/device.mapper.dart @@ -64,6 +64,325 @@ extension DeviceTypeMapperExtension on DeviceType { } } +class DiscoveryMethodMapper extends ClassMapperBase { + DiscoveryMethodMapper._(); + + static DiscoveryMethodMapper? _instance; + static DiscoveryMethodMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DiscoveryMethodMapper._()); + MulticastDiscoveryMapper.ensureInitialized(); + HttpDiscoveryMapper.ensureInitialized(); + SignalingDiscoveryMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'DiscoveryMethod'; + + @override + final MappableFields fields = const {}; + + static DiscoveryMethod _instantiate(DecodingData data) { + throw MapperException.missingConstructor('DiscoveryMethod'); + } + + @override + final Function instantiate = _instantiate; + + static DiscoveryMethod fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static DiscoveryMethod deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin DiscoveryMethodMappable { + String serialize(); + Map toJson(); + DiscoveryMethodCopyWith get copyWith; +} + +abstract class DiscoveryMethodCopyWith<$R, $In extends DiscoveryMethod, $Out> implements ClassCopyWith<$R, $In, $Out> { + $R call(); + DiscoveryMethodCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class MulticastDiscoveryMapper extends ClassMapperBase { + MulticastDiscoveryMapper._(); + + static MulticastDiscoveryMapper? _instance; + static MulticastDiscoveryMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = MulticastDiscoveryMapper._()); + DiscoveryMethodMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'MulticastDiscovery'; + + @override + final MappableFields fields = const {}; + + static MulticastDiscovery _instantiate(DecodingData data) { + return MulticastDiscovery(); + } + + @override + final Function instantiate = _instantiate; + + static MulticastDiscovery fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static MulticastDiscovery deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin MulticastDiscoveryMappable { + String serialize() { + return MulticastDiscoveryMapper.ensureInitialized().encodeJson(this as MulticastDiscovery); + } + + Map toJson() { + return MulticastDiscoveryMapper.ensureInitialized().encodeMap(this as MulticastDiscovery); + } + + MulticastDiscoveryCopyWith get copyWith => + _MulticastDiscoveryCopyWithImpl(this as MulticastDiscovery, $identity, $identity); + @override + String toString() { + return MulticastDiscoveryMapper.ensureInitialized().stringifyValue(this as MulticastDiscovery); + } + + @override + bool operator ==(Object other) { + return MulticastDiscoveryMapper.ensureInitialized().equalsValue(this as MulticastDiscovery, other); + } + + @override + int get hashCode { + return MulticastDiscoveryMapper.ensureInitialized().hashValue(this as MulticastDiscovery); + } +} + +extension MulticastDiscoveryValueCopy<$R, $Out> on ObjectCopyWith<$R, MulticastDiscovery, $Out> { + MulticastDiscoveryCopyWith<$R, MulticastDiscovery, $Out> get $asMulticastDiscovery => + $base.as((v, t, t2) => _MulticastDiscoveryCopyWithImpl(v, t, t2)); +} + +abstract class MulticastDiscoveryCopyWith<$R, $In extends MulticastDiscovery, $Out> implements DiscoveryMethodCopyWith<$R, $In, $Out> { + @override + $R call(); + MulticastDiscoveryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _MulticastDiscoveryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, MulticastDiscovery, $Out> + implements MulticastDiscoveryCopyWith<$R, MulticastDiscovery, $Out> { + _MulticastDiscoveryCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = MulticastDiscoveryMapper.ensureInitialized(); + @override + $R call() => $apply(FieldCopyWithData({})); + @override + MulticastDiscovery $make(CopyWithData data) => MulticastDiscovery(); + + @override + MulticastDiscoveryCopyWith<$R2, MulticastDiscovery, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _MulticastDiscoveryCopyWithImpl($value, $cast, t); +} + +class HttpDiscoveryMapper extends ClassMapperBase { + HttpDiscoveryMapper._(); + + static HttpDiscoveryMapper? _instance; + static HttpDiscoveryMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = HttpDiscoveryMapper._()); + DiscoveryMethodMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'HttpDiscovery'; + + static String _$ip(HttpDiscovery v) => v.ip; + static const Field _f$ip = Field('ip', _$ip); + + @override + final MappableFields fields = const { + #ip: _f$ip, + }; + + static HttpDiscovery _instantiate(DecodingData data) { + return HttpDiscovery(ip: data.dec(_f$ip)); + } + + @override + final Function instantiate = _instantiate; + + static HttpDiscovery fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static HttpDiscovery deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin HttpDiscoveryMappable { + String serialize() { + return HttpDiscoveryMapper.ensureInitialized().encodeJson(this as HttpDiscovery); + } + + Map toJson() { + return HttpDiscoveryMapper.ensureInitialized().encodeMap(this as HttpDiscovery); + } + + HttpDiscoveryCopyWith get copyWith => + _HttpDiscoveryCopyWithImpl(this as HttpDiscovery, $identity, $identity); + @override + String toString() { + return HttpDiscoveryMapper.ensureInitialized().stringifyValue(this as HttpDiscovery); + } + + @override + bool operator ==(Object other) { + return HttpDiscoveryMapper.ensureInitialized().equalsValue(this as HttpDiscovery, other); + } + + @override + int get hashCode { + return HttpDiscoveryMapper.ensureInitialized().hashValue(this as HttpDiscovery); + } +} + +extension HttpDiscoveryValueCopy<$R, $Out> on ObjectCopyWith<$R, HttpDiscovery, $Out> { + HttpDiscoveryCopyWith<$R, HttpDiscovery, $Out> get $asHttpDiscovery => $base.as((v, t, t2) => _HttpDiscoveryCopyWithImpl(v, t, t2)); +} + +abstract class HttpDiscoveryCopyWith<$R, $In extends HttpDiscovery, $Out> implements DiscoveryMethodCopyWith<$R, $In, $Out> { + @override + $R call({String? ip}); + HttpDiscoveryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _HttpDiscoveryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, HttpDiscovery, $Out> + implements HttpDiscoveryCopyWith<$R, HttpDiscovery, $Out> { + _HttpDiscoveryCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = HttpDiscoveryMapper.ensureInitialized(); + @override + $R call({String? ip}) => $apply(FieldCopyWithData({if (ip != null) #ip: ip})); + @override + HttpDiscovery $make(CopyWithData data) => HttpDiscovery(ip: data.get(#ip, or: $value.ip)); + + @override + HttpDiscoveryCopyWith<$R2, HttpDiscovery, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _HttpDiscoveryCopyWithImpl($value, $cast, t); +} + +class SignalingDiscoveryMapper extends ClassMapperBase { + SignalingDiscoveryMapper._(); + + static SignalingDiscoveryMapper? _instance; + static SignalingDiscoveryMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SignalingDiscoveryMapper._()); + DiscoveryMethodMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'SignalingDiscovery'; + + static String _$signalingServer(SignalingDiscovery v) => v.signalingServer; + static const Field _f$signalingServer = Field('signalingServer', _$signalingServer); + + @override + final MappableFields fields = const { + #signalingServer: _f$signalingServer, + }; + + static SignalingDiscovery _instantiate(DecodingData data) { + return SignalingDiscovery(signalingServer: data.dec(_f$signalingServer)); + } + + @override + final Function instantiate = _instantiate; + + static SignalingDiscovery fromJson(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SignalingDiscovery deserialize(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SignalingDiscoveryMappable { + String serialize() { + return SignalingDiscoveryMapper.ensureInitialized().encodeJson(this as SignalingDiscovery); + } + + Map toJson() { + return SignalingDiscoveryMapper.ensureInitialized().encodeMap(this as SignalingDiscovery); + } + + SignalingDiscoveryCopyWith get copyWith => + _SignalingDiscoveryCopyWithImpl(this as SignalingDiscovery, $identity, $identity); + @override + String toString() { + return SignalingDiscoveryMapper.ensureInitialized().stringifyValue(this as SignalingDiscovery); + } + + @override + bool operator ==(Object other) { + return SignalingDiscoveryMapper.ensureInitialized().equalsValue(this as SignalingDiscovery, other); + } + + @override + int get hashCode { + return SignalingDiscoveryMapper.ensureInitialized().hashValue(this as SignalingDiscovery); + } +} + +extension SignalingDiscoveryValueCopy<$R, $Out> on ObjectCopyWith<$R, SignalingDiscovery, $Out> { + SignalingDiscoveryCopyWith<$R, SignalingDiscovery, $Out> get $asSignalingDiscovery => + $base.as((v, t, t2) => _SignalingDiscoveryCopyWithImpl(v, t, t2)); +} + +abstract class SignalingDiscoveryCopyWith<$R, $In extends SignalingDiscovery, $Out> implements DiscoveryMethodCopyWith<$R, $In, $Out> { + @override + $R call({String? signalingServer}); + SignalingDiscoveryCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _SignalingDiscoveryCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, SignalingDiscovery, $Out> + implements SignalingDiscoveryCopyWith<$R, SignalingDiscovery, $Out> { + _SignalingDiscoveryCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = SignalingDiscoveryMapper.ensureInitialized(); + @override + $R call({String? signalingServer}) => $apply(FieldCopyWithData({if (signalingServer != null) #signalingServer: signalingServer})); + @override + SignalingDiscovery $make(CopyWithData data) => SignalingDiscovery(signalingServer: data.get(#signalingServer, or: $value.signalingServer)); + + @override + SignalingDiscoveryCopyWith<$R2, SignalingDiscovery, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _SignalingDiscoveryCopyWithImpl($value, $cast, t); +} + class DeviceMapper extends ClassMapperBase { DeviceMapper._(); @@ -72,6 +391,7 @@ class DeviceMapper extends ClassMapperBase { if (_instance == null) { MapperContainer.globals.use(_instance = DeviceMapper._()); DeviceTypeMapper.ensureInitialized(); + DiscoveryMethodMapper.ensureInitialized(); } return _instance!; } @@ -79,7 +399,9 @@ class DeviceMapper extends ClassMapperBase { @override final String id = 'Device'; - static String _$ip(Device v) => v.ip; + static String? _$signalingId(Device v) => v.signalingId; + static const Field _f$signalingId = Field('signalingId', _$signalingId); + static String? _$ip(Device v) => v.ip; static const Field _f$ip = Field('ip', _$ip); static String _$version(Device v) => v.version; static const Field _f$version = Field('version', _$version); @@ -97,9 +419,12 @@ class DeviceMapper extends ClassMapperBase { static const Field _f$deviceType = Field('deviceType', _$deviceType); static bool _$download(Device v) => v.download; static const Field _f$download = Field('download', _$download); + static Set _$discoveryMethods(Device v) => v.discoveryMethods; + static const Field> _f$discoveryMethods = Field('discoveryMethods', _$discoveryMethods); @override final MappableFields fields = const { + #signalingId: _f$signalingId, #ip: _f$ip, #version: _f$version, #port: _f$port, @@ -109,10 +434,12 @@ class DeviceMapper extends ClassMapperBase { #deviceModel: _f$deviceModel, #deviceType: _f$deviceType, #download: _f$download, + #discoveryMethods: _f$discoveryMethods, }; static Device _instantiate(DecodingData data) { return Device( + signalingId: data.dec(_f$signalingId), ip: data.dec(_f$ip), version: data.dec(_f$version), port: data.dec(_f$port), @@ -121,7 +448,8 @@ class DeviceMapper extends ClassMapperBase { alias: data.dec(_f$alias), deviceModel: data.dec(_f$deviceModel), deviceType: data.dec(_f$deviceType), - download: data.dec(_f$download)); + download: data.dec(_f$download), + discoveryMethods: data.dec(_f$discoveryMethods)); } @override @@ -168,7 +496,8 @@ extension DeviceValueCopy<$R, $Out> on ObjectCopyWith<$R, Device, $Out> { abstract class DeviceCopyWith<$R, $In extends Device, $Out> implements ClassCopyWith<$R, $In, $Out> { $R call( - {String? ip, + {String? signalingId, + String? ip, String? version, int? port, bool? https, @@ -176,7 +505,8 @@ abstract class DeviceCopyWith<$R, $In extends Device, $Out> implements ClassCopy String? alias, String? deviceModel, DeviceType? deviceType, - bool? download}); + bool? download, + Set? discoveryMethods}); DeviceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); } @@ -187,7 +517,8 @@ class _DeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Device, $Out> late final ClassMapperBase $mapper = DeviceMapper.ensureInitialized(); @override $R call( - {String? ip, + {Object? signalingId = $none, + Object? ip = $none, String? version, int? port, bool? https, @@ -195,9 +526,11 @@ class _DeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Device, $Out> String? alias, Object? deviceModel = $none, DeviceType? deviceType, - bool? download}) => + bool? download, + Set? discoveryMethods}) => $apply(FieldCopyWithData({ - if (ip != null) #ip: ip, + if (signalingId != $none) #signalingId: signalingId, + if (ip != $none) #ip: ip, if (version != null) #version: version, if (port != null) #port: port, if (https != null) #https: https, @@ -205,10 +538,12 @@ class _DeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Device, $Out> if (alias != null) #alias: alias, if (deviceModel != $none) #deviceModel: deviceModel, if (deviceType != null) #deviceType: deviceType, - if (download != null) #download: download + if (download != null) #download: download, + if (discoveryMethods != null) #discoveryMethods: discoveryMethods })); @override Device $make(CopyWithData data) => Device( + signalingId: data.get(#signalingId, or: $value.signalingId), ip: data.get(#ip, or: $value.ip), version: data.get(#version, or: $value.version), port: data.get(#port, or: $value.port), @@ -217,7 +552,8 @@ class _DeviceCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Device, $Out> alias: data.get(#alias, or: $value.alias), deviceModel: data.get(#deviceModel, or: $value.deviceModel), deviceType: data.get(#deviceType, or: $value.deviceType), - download: data.get(#download, or: $value.download)); + download: data.get(#download, or: $value.download), + discoveryMethods: data.get(#discoveryMethods, or: $value.discoveryMethods)); @override DeviceCopyWith<$R2, Device, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _DeviceCopyWithImpl($value, $cast, t); diff --git a/common/lib/model/dto/file_dto.dart b/common/lib/model/dto/file_dto.dart index ee9ea3c0..68edbb28 100644 --- a/common/lib/model/dto/file_dto.dart +++ b/common/lib/model/dto/file_dto.dart @@ -73,19 +73,7 @@ class FileDtoMapper extends SimpleMapper { final FileType fileType; if (rawFileType.contains('/')) { // parse mime - if (rawFileType.startsWith('image/')) { - fileType = FileType.image; - } else if (rawFileType.startsWith('video/')) { - fileType = FileType.video; - } else if (rawFileType == 'application/pdf') { - fileType = FileType.pdf; - } else if (rawFileType.startsWith('text/')) { - fileType = FileType.text; - } else if (rawFileType == 'application/vnd.android.package-archive') { - fileType = FileType.apk; - } else { - fileType = FileType.other; - } + fileType = decodeFromMime(rawFileType); } else { // parse legacy enum to internal internal enum fileType = FileType.values.firstWhereOrNull((e) => e.name == rawFileType) ?? FileType.other; @@ -119,3 +107,19 @@ class FileDtoMapper extends SimpleMapper { }; } } + +FileType decodeFromMime(String mime) { + if (mime.startsWith('image/')) { + return FileType.image; + } else if (mime.startsWith('video/')) { + return FileType.video; + } else if (mime == 'application/pdf') { + return FileType.pdf; + } else if (mime.startsWith('text/')) { + return FileType.text; + } else if (mime == 'application/vnd.android.package-archive') { + return FileType.apk; + } else { + return FileType.other; + } +} diff --git a/common/lib/model/dto/info_dto.dart b/common/lib/model/dto/info_dto.dart index 0a96ba3d..d9203c9c 100644 --- a/common/lib/model/dto/info_dto.dart +++ b/common/lib/model/dto/info_dto.dart @@ -28,8 +28,9 @@ class InfoDto with InfoDtoMappable { extension InfoToDeviceExt on InfoDto { /// Convert [InfoDto] to [Device]. /// Since this HTTP request was successful, the [port] and [https] are known. - Device toDevice(String ip, int port, bool https) { + Device toDevice(String ip, int port, bool https, DiscoveryMethod method) { return Device( + signalingId: null, ip: ip, version: version ?? fallbackProtocolVersion, port: port, @@ -39,6 +40,7 @@ extension InfoToDeviceExt on InfoDto { deviceModel: deviceModel, deviceType: deviceType ?? DeviceType.desktop, download: download ?? false, + discoveryMethods: {method}, ); } } diff --git a/common/lib/model/dto/info_register_dto.dart b/common/lib/model/dto/info_register_dto.dart index f4832793..18c3c816 100644 --- a/common/lib/model/dto/info_register_dto.dart +++ b/common/lib/model/dto/info_register_dto.dart @@ -34,8 +34,9 @@ class InfoRegisterDto with InfoRegisterDtoMappable { } extension InfoRegisterDtoExt on InfoRegisterDto { - Device toDevice(String ip, int ownPort, bool ownHttps) { + Device toDevice(String ip, int ownPort, bool ownHttps, DiscoveryMethod? method) { return Device( + signalingId: null, ip: ip, version: version ?? fallbackProtocolVersion, port: port ?? ownPort, @@ -45,6 +46,7 @@ extension InfoRegisterDtoExt on InfoRegisterDto { deviceModel: deviceModel, deviceType: deviceType ?? DeviceType.desktop, download: download ?? false, + discoveryMethods: method == null ? const {} : {method}, ); } } diff --git a/common/lib/model/dto/multicast_dto.dart b/common/lib/model/dto/multicast_dto.dart index 36c0f362..fcca038b 100644 --- a/common/lib/model/dto/multicast_dto.dart +++ b/common/lib/model/dto/multicast_dto.dart @@ -39,6 +39,7 @@ class MulticastDto with MulticastDtoMappable { extension MulticastDtoToDeviceExt on MulticastDto { Device toDevice(String ip, int ownPort, bool ownHttps) { return Device( + signalingId: null, ip: ip, version: version ?? fallbackProtocolVersion, port: port ?? ownPort, @@ -48,6 +49,7 @@ extension MulticastDtoToDeviceExt on MulticastDto { deviceModel: deviceModel, deviceType: deviceType ?? DeviceType.desktop, download: download ?? false, + discoveryMethods: {MulticastDiscovery()}, ); } } diff --git a/common/lib/model/dto/register_dto.dart b/common/lib/model/dto/register_dto.dart index 811a8f37..93ef6805 100644 --- a/common/lib/model/dto/register_dto.dart +++ b/common/lib/model/dto/register_dto.dart @@ -31,8 +31,9 @@ class RegisterDto with RegisterDtoMappable { } extension RegisterDtoExt on RegisterDto { - Device toDevice(String ip, int ownPort, bool ownHttps) { + Device toDevice(String ip, int ownPort, bool ownHttps, DiscoveryMethod method) { return Device( + signalingId: null, ip: ip, version: version ?? fallbackProtocolVersion, port: port ?? ownPort, @@ -42,6 +43,7 @@ extension RegisterDtoExt on RegisterDto { deviceModel: deviceModel, deviceType: deviceType ?? DeviceType.desktop, download: download ?? false, + discoveryMethods: {method}, ); } } diff --git a/common/lib/src/task/discovery/http_target_discovery.dart b/common/lib/src/task/discovery/http_target_discovery.dart index 5384b04f..3554541c 100644 --- a/common/lib/src/task/discovery/http_target_discovery.dart +++ b/common/lib/src/task/discovery/http_target_discovery.dart @@ -35,7 +35,7 @@ class HttpTargetDiscoveryService { 'fingerprint': _fingerprint, }); final dto = InfoDtoMapper.deserialize(response); - return dto.toDevice(ip, port, https); + return dto.toDevice(ip, port, https, HttpDiscovery(ip: ip)); } catch (e) { onError?.call(url, e); return null; diff --git a/common/pubspec.yaml b/common/pubspec.yaml index f0b26489..9ed33b3b 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: logging: ^1.2.0 meta: ^1.9.1 mime: ^1.0.4 - refena: ^2.0.0 + refena: ^3.1.0 dev_dependencies: build_runner: ^2.4.7 diff --git a/common/test/unit/model/device_test.dart b/common/test/unit/model/device_test.dart new file mode 100644 index 00000000..6d08fd9f --- /dev/null +++ b/common/test/unit/model/device_test.dart @@ -0,0 +1,60 @@ +import 'package:common/model/device.dart'; +import 'package:test/test.dart'; + +void main() { + test('Should not have duplicate DiscoveryMethod', () { + final set = {}; + + set.add(MulticastDiscovery()); + + expect(set, hasLength(1)); + + set.add(MulticastDiscovery()); + + expect(set, hasLength(1)); + + set.add(HttpDiscovery(ip: 'a')); + + expect(set, hasLength(2)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + + set.add(HttpDiscovery(ip: 'a')); + + expect(set, hasLength(2)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + + set.add(HttpDiscovery(ip: 'b')); + + expect(set, hasLength(3)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + expect(set, contains(HttpDiscovery(ip: 'b'))); + + set.add(SignalingDiscovery(signalingServer: 'sa')); + + expect(set, hasLength(4)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + expect(set, contains(HttpDiscovery(ip: 'b'))); + expect(set, contains(SignalingDiscovery(signalingServer: 'sa'))); + + set.add(SignalingDiscovery(signalingServer: 'sa')); + + expect(set, hasLength(4)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + expect(set, contains(HttpDiscovery(ip: 'b'))); + expect(set, contains(SignalingDiscovery(signalingServer: 'sa'))); + + set.add(SignalingDiscovery(signalingServer: 'sb')); + + expect(set, hasLength(5)); + expect(set, contains(MulticastDiscovery())); + expect(set, contains(HttpDiscovery(ip: 'a'))); + expect(set, contains(HttpDiscovery(ip: 'b'))); + expect(set, contains(SignalingDiscovery(signalingServer: 'sa'))); + expect(set, contains(SignalingDiscovery(signalingServer: 'sb'))); + }); +} diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/core/Cargo.lock b/core/Cargo.lock new file mode 100644 index 00000000..8ba8dfeb --- /dev/null +++ b/core/Cargo.lock @@ -0,0 +1,3350 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.11", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.0", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interceptor" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ab04c530fd82e414e40394cabe5f0ebfe30d119f10fe29d6e3561926af412e" +dependencies = [ + "async-trait", + "bytes", + "log", + "portable-atomic", + "rand", + "rtcp", + "rtp", + "thiserror 1.0.69", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "localsend" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "bytes", + "ed25519-dalek", + "flate2", + "futures-util", + "http-body-util", + "hyper", + "hyper-util", + "pem", + "rand", + "reqwest", + "rsa", + "rustls", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.11", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", + "tungstenite", + "uuid", + "webrtc", + "x509-parser 0.17.0", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264c56d1492c13e769662197fb6b94e0a52abe52d27efac374615799a4bf453d" +dependencies = [ + "asn1-rs 0.7.0", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.16.0", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rtcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8306430fb118b7834bbee50e744dc34826eca1da2158657a3d6cbc70e24c2096" +dependencies = [ + "bytes", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68baca5b6cb4980678713f0d06ef3a432aa642baefcbfd0f4dd2ef9eb5ab550" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand", + "serde", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a526161f474ae94b966ba622379d939a8fe46c930eebbadb73e339622599d5" +dependencies = [ + "rand", + "substring", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stun" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea256fb46a13f9204e9dee9982997b2c3097db175a9fddaa8350310d03c4d5a3" +dependencies = [ + "base64", + "crc", + "lazy_static", + "md-5", + "rand", + "ring", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.11", + "utf-8", +] + +[[package]] +name = "turn" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0044fdae001dd8a1e247ea6289abf12f4fcea1331a2364da512f9cd680bbd8cb" +dependencies = [ + "async-trait", + "base64", + "futures", + "log", + "md-5", + "portable-atomic", + "rand", + "ring", + "stun", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "webrtc-util", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom 0.2.15", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webrtc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30367074d9f18231d28a74fab0120856b2b665da108d71a12beab7185a36f97b" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror 1.0.69", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec93b991efcd01b73c5b3503fa8adba159d069abe5785c988ebe14fcf8f05d1" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.69", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c9b89fc909f9da0499283b1112cd98f72fec28e55a54a9e352525ca65cd95c" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser 9.0.0", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "portable-atomic", + "rand", + "rand_core", + "rcgen", + "ring", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser 0.16.0", +] + +[[package]] +name = "webrtc-ice" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348b28b593f7709ac98d872beb58c0009523df652c78e01b950ab9c537ff17d" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand", + "serde", + "serde_json", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dfe9686c6c9c51428da4de415cb6ca2dc0591ce2b63212e23fd9cccf0e316b" +dependencies = [ + "log", + "socket2", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e153be16b8650021ad3e9e49ab6e5fa9fb7f6d1c23c213fd8bbd1a1135a4c704" +dependencies = [ + "byteorder", + "bytes", + "rand", + "rtp", + "thiserror 1.0.69", +] + +[[package]] +name = "webrtc-sctp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5faf3846ec4b7e64b56338d62cbafe084aa79806b0379dff5cc74a8b7a2b3063" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771db9993712a8fb3886d5be4613ebf27250ef422bd4071988bf55f1ed1a64fa" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1438a8fd0d69c5775afb4a71470af92242dbd04059c61895163aa3c1ef933375" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "portable-atomic", + "rand", + "thiserror 1.0.69", + "tokio", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry 0.7.1", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.0", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.0", + "ring", + "rusticata-macros", + "thiserror 2.0.11", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 00000000..7559ace0 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "localsend" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.95" +base64 = "0.22.1" +bytes = "1.9.0" +ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"], optional = true } +flate2 = { version = "1.0.35", optional = true } +futures-util = { version = "0.3.31", features = ["sink"] } +http-body-util = { version = "0.1.2", optional = true } +hyper = { version = "1.6.0", optional = true } +hyper-util = { version = "0.1.10", features = ["server"], optional = true } +pem = { version = "3.0.4", optional = true } +reqwest = { version = "0.12", features = ["json", "rustls-tls-webpki-roots-no-provider", "stream"], optional = true } +rand = "0.8.5" +rsa = { version = "0.9.7", optional = true } +rustls = { version = "0.23.21", default-features = false, features = ["ring", "tls12", "std"], optional = true } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.135" +sha2 = { version = "0.10.8", optional = true } +thiserror = "2.0.11" +tokio = { version = "1.43.0", features = ["full"] } +tokio-rustls = { version = "0.26.1", default-features = false, features = ["ring", "tls12"], optional = true } +tokio-stream = "0.1.7" +tokio-tungstenite = { version = "0.26.1", features = ["rustls-tls-webpki-roots"], optional = true } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19" } +tungstenite = "0.26.1" +uuid = { version = "1.11.0", features = ["serde", "v4"] } +webrtc = { version = "0.12.0", optional = true } +x509-parser = { version = "0.17.0", features = ["verify"], optional = true } + +[features] +default = [] +crypto = ["ed25519-dalek", "rsa", "sha2"] +http = ["crypto", "http-body-util", "hyper", "hyper-util", "pem", "reqwest", "rustls", "tokio-rustls", "x509-parser"] +webrtc-signaling = ["tokio-tungstenite"] +webrtc = ["crypto", "flate2", "dep:webrtc", "webrtc-signaling", "x509-parser"] +full = ["crypto", "http", "webrtc"] diff --git a/core/src/crypto/cert.rs b/core/src/crypto/cert.rs new file mode 100644 index 00000000..cbf3186b --- /dev/null +++ b/core/src/crypto/cert.rs @@ -0,0 +1,186 @@ +use std::io::Cursor; +use x509_parser::asn1_rs::FromDer; +use x509_parser::certificate::X509Certificate; +use x509_parser::pem::Pem; +use x509_parser::x509::SubjectPublicKeyInfo; + +pub fn verify_cert_from_pem(cert: String, public_key: Option) -> anyhow::Result<()> { + let (cert_pem, _) = Pem::read(Cursor::new(cert.into_bytes()))?; + let parsed_cert: X509Certificate = cert_pem.parse_x509()?; + + verify_cert_from_cert(parsed_cert, public_key) +} + +pub fn verify_cert_from_der(cert: &[u8], public_key: Option) -> anyhow::Result<()> { + let (_, parsed_cert) = X509Certificate::from_der(&cert)?; + + verify_cert_from_cert(parsed_cert, public_key) +} + +/// Verifies if the certificate is valid +/// - according to the signature +/// - according to the time validity +/// - according to the public key (if provided) +fn verify_cert_from_cert(cert: X509Certificate, public_key: Option) -> anyhow::Result<()> { + if !cert.validity.is_valid() { + return Err(anyhow::anyhow!("Time validity error")); + } + + if let Some(public_key) = public_key { + let cert_public_key = cert.tbs_certificate.subject_pki.parsed()?; + + let (public_key_pem, _) = Pem::read(Cursor::new(public_key.into_bytes()))?; + let (_, public_key_spki) = SubjectPublicKeyInfo::from_der(&public_key_pem.contents)?; + let expected_public_key = public_key_spki.parsed()?; + + if cert_public_key != expected_public_key { + // We catch public key mismatch separately from signature verification + // so that we can better unit test the function + return Err(anyhow::anyhow!("Public key mismatch")); + } + } + + cert.verify_signature(None)?; + + Ok(()) +} + +pub fn public_key_from_cert_pem(cert: String) -> anyhow::Result { + let (cert_pem, _) = Pem::read(Cursor::new(cert.into_bytes()))?; + let parsed_cert: X509Certificate = cert_pem.parse_x509()?; + public_key_from_cert(parsed_cert) +} + +pub fn public_key_from_cert_der(cert: &[u8]) -> anyhow::Result { + let (_, parsed_cert) = X509Certificate::from_der(&cert)?; + public_key_from_cert(parsed_cert) +} + +/// Extracts the public key from the certificate. +/// Encodes the public key in PEM format. +pub fn public_key_from_cert(cert: X509Certificate) -> anyhow::Result { + let cert_public_key = cert.tbs_certificate.subject_pki.raw; + let public_key = pem::encode(&pem::Pem::new("PUBLIC KEY", cert_public_key.to_vec())); + Ok(public_key) +} + +#[cfg(test)] +mod tests { + use super::*; + + static PUBLIC_KEY: &str = "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi9uDMYRn63SZtEPGRogZ +Gdu5XXBAoQeMO60mycoinqLKDWyZdpMo+XWY3wYVhoAyxgzDOcPjIf+Uq1oEy/0K +4WwfpbK8SCy851qgYkfMCT9D9mFvXwWoULJCUHFF7f947ArDE1nmuK1nNx2RodN2 +wJCXyzPjw0jn06bwGeg0EqfUC8wvW4FTZ6t1tErzmRqRdMUWuCJwsk1IMbDbFePh +iK5jecOBG0RVVWLuw+TkuX8TUgrpIktH2+qEM1KdLyAMnL71hx2wMvE+lDKFKK9p +37zXK8omjl+VgTC8ocjGeYDDsl43ZtW09V0pb7Vz2FM8b7BgM06kvJl48PIe5puY +bQIDAQAB +-----END PUBLIC KEY-----"; + + #[test] + fn test_verify_cert() { + let cert = "-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh +bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG +A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjA5MDAwMzE0WhcNMzUwMjA3MDAwMzE0 +WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL +EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCL24MxhGfrdJm0Q8ZGiBkZ27ldcEChB4w7rSbJ +yiKeosoNbJl2kyj5dZjfBhWGgDLGDMM5w+Mh/5SrWgTL/QrhbB+lsrxILLznWqBi +R8wJP0P2YW9fBahQskJQcUXt/3jsCsMTWea4rWc3HZGh03bAkJfLM+PDSOfTpvAZ +6DQSp9QLzC9bgVNnq3W0SvOZGpF0xRa4InCyTUgxsNsV4+GIrmN5w4EbRFVVYu7D +5OS5fxNSCukiS0fb6oQzUp0vIAycvvWHHbAy8T6UMoUor2nfvNcryiaOX5WBMLyh +yMZ5gMOyXjdm1bT1XSlvtXPYUzxvsGAzTqS8mXjw8h7mm5htAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBABZ+I7D6wkeSrsi1NBLP2zoZ5oGh+INNcGTravfOQHs4Fbas +/CysaUYjsD3fmaDh4MxgWEqAmWnnBiojfpGX2SGuFqRBKyT9DgitBt0L7Ezg1k3h +bfSiFW4hXWp75grVO8xfML7ZcWMlhKrOsOMUGiy1qs3qsyJ3w7B2Tz78HhXGO5dd +jyPmZarhixKO92UpEvKGxjO0E/3UUNUzxKTAAgFfhKpuwHUgIijM/EppZtA8OcSh +fEztiV0xKfcPVx4d6dqRt/NMElK1Ivw2vUuxTymphZkkFOzht9m73/kyKaeFp8Ij +VRus1zGVD8IVpIdPMyz01WJyS7M0fWaHXKWo+Bo= +-----END CERTIFICATE-----" + .to_string(); + assert_eq!( + verify_cert_from_pem(cert, Some(PUBLIC_KEY.to_owned())).map_err(|e| e.to_string()), + Ok(()) + ); + + let cert_invalid_signature = "-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh +bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG +A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjA5MDAwMzE0WhcNMzUwMjA3MDAwMzE0 +WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL +EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCL24MxhGfrdJm0Q8ZGiBkZ27ldcEChB4w7rSbJ +yiKeosoNbJl2kyj5dZjfBhWGgDLGDMM5w+Mh/5SrWgTL/QrhbB+lsrxILLznWqBi +R8wJP0P2YW9fBahQskJQcUXt/3jsCsMTWea4rWc3HZGh03bAkJfLM+PDSOfTpvAZ +6DQSp9QLzC9bgVNnq3W0SvOZGpF0xRa4InCyTUgxsNsV4+GIrmN5w4EbRFVVYu7D +5OS5fxNSCukiS0fb6oQzUp0vIAycvvWHHbAy8T6UMoUor2nfvNcryiaOX5WBMLyh +yMZ5gMOyXjdm1bT1XSlvtXPYUzxvsGAzTqS8mXjw8h7mm5htAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBABZ+I7D6wkeSrsi1NBLP2zoZ5oGh+INNcGTravfOQHs4Fbas +/CysaUYjsD3fmaDh4MxgWEqAmWnnBiojfpGX2SGuFqRBKyT9DgitBt0L7Ezg1k3h +bfSiFW4hXWp75grVO8xfML7ZcWMlhKrOsOMUGiy1qs3qsyJ3w7B2Tz78HhXGO5dd +jyPmZarhixKO92UpEvKGxjO0E/3UUNUzxKTAAgFfhKpuwHUgIijM/EppZtA8OcSh +fEztiV0xKfcPVx4d6dqRt/NMElK1Ivw2vUuxTymphZkkFOzht9m73/kyKaeFp8Ij +VRus1zGVD8IVpIdPMyz01WJySwAAAAAAAAAAAAA= +-----END CERTIFICATE-----" + .to_string(); + assert_eq!( + verify_cert_from_pem(cert_invalid_signature, Some(PUBLIC_KEY.to_owned())) + .map_err(|e| e.to_string()), + Err("signature verification error".to_string()) + ); + + let cert_invalid_public_key = "-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh +bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG +A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjA5MDI1ODQxWhcNMzUwMjA3MDI1ODQx +WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL +EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCnrVjQQ0mAfBaucJd5rbZX9usLROwHDuXdczFq +XJhb8pPjEF18FoDHzobjz5JWq+GDkBmcg0k6+AeETGQEaJisZDBWH7NOjJahGGnQ +0okw1iVUoEpQ26ZSFkr3H5NNtGAa6EkS4xb0bsEDb3vs69zRvFyrVd6OEqmdsRy3 +aU2AvAMoLthgY8bUZ/XyWpbA8euV3VjkRSHsju+DOQH4oj46ZITJ3M2/x5o/3jqJ +ILBhoLcu7UJJTHYqeBsPSTkMIGKLkYSUPOd/mSgwQB854wks4nf+hO4VWvKQFx9X +4gIjS7vJ6e9rQOn2NFfluPbRiijmWIiwUDUWz3UW2RS0b6gDAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBAAO3rG2YcQqH8Z7jDX82q0nn/bglOWvTySv4EP3FNrVPZKfN +aR+oLo8WdAWulbxXDIOK7XLk1V9SxEJvVxOTp2EIgcoWqJANoWjp+5nNInE02eNX +G8euvPvh+p/1cTbHxhrZqtsSpkAx1AbbbcvT+5hUUDXSU7cMN+vFjUqkEVrBlj7S +vFbLDHP82ywisZrkOfNapxV67U4ENaEwJ4P4OERnqOOieJr0elv598cSDu+OSKmt +rFNYYHERELX36g4+KcWGN223Pg4Xl0bFYqV0xwRUThh0657t8cioXaOsjjpKnGAm +eVVihnrJ3sdk7nnreAYMse/OipyufRyZ9t3WU8A= +-----END CERTIFICATE-----" + .to_string(); + assert_eq!( + verify_cert_from_pem(cert_invalid_public_key, Some(PUBLIC_KEY.to_owned())) + .map_err(|e| e.to_string()), + Err("Public key mismatch".to_string()) + ); + + let cert_expired = "-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh +bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG +A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjA5MjEwOTQ0WhcNMjUwMjA5MjEwOTQ0 +WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL +EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCL24MxhGfrdJm0Q8ZGiBkZ27ldcEChB4w7rSbJ +yiKeosoNbJl2kyj5dZjfBhWGgDLGDMM5w+Mh/5SrWgTL/QrhbB+lsrxILLznWqBi +R8wJP0P2YW9fBahQskJQcUXt/3jsCsMTWea4rWc3HZGh03bAkJfLM+PDSOfTpvAZ +6DQSp9QLzC9bgVNnq3W0SvOZGpF0xRa4InCyTUgxsNsV4+GIrmN5w4EbRFVVYu7D +5OS5fxNSCukiS0fb6oQzUp0vIAycvvWHHbAy8T6UMoUor2nfvNcryiaOX5WBMLyh +yMZ5gMOyXjdm1bT1XSlvtXPYUzxvsGAzTqS8mXjw8h7mm5htAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBAH2/F6iEH8W5gIHcKJ6/EbrG2BY5Uhg5U8X6yPk6z9ctmY6w +n7fDT749PMVDJq+qhIcnoBlUgVJdJ2qFa5h3VaSF+tUFu/CImr+S8TYHCdQGYXA5 +6b/pnHmbrWqZdNdxs6Y80A9Mu+iNeLDcrTo60/zGfJsiD9Cnlj0Q6c8nn+Obzeqq +iIwUmPFw0krH+ku/DlSenKnyL8jaktf48neufu0jObUvCuj62I2WlFZwzXd8CnDR +X2/mKq6FWHCDR6RTh1yMLfD+NoVNcswxwMFq8ILCfBuTjNVaSFm3eUqKeEOAaDes +nidU/qXQvBJ7NPUkXXgbcgqxK735iijOqQHmKts= +-----END CERTIFICATE-----" + .to_string(); + assert_eq!( + verify_cert_from_pem(cert_expired, Some(PUBLIC_KEY.to_owned())) + .map_err(|e| e.to_string()), + Err("Time validity error".to_string()) + ); + } +} diff --git a/core/src/crypto/hash.rs b/core/src/crypto/hash.rs new file mode 100644 index 00000000..7bfa671c --- /dev/null +++ b/core/src/crypto/hash.rs @@ -0,0 +1,7 @@ +use sha2::{Digest, Sha256}; + +pub fn sha256(data: &[u8]) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().to_vec() +} diff --git a/core/src/crypto/mod.rs b/core/src/crypto/mod.rs new file mode 100644 index 00000000..d6e7ef6b --- /dev/null +++ b/core/src/crypto/mod.rs @@ -0,0 +1,4 @@ +pub mod cert; +pub mod hash; +pub mod nonce; +pub mod token; diff --git a/core/src/crypto/nonce.rs b/core/src/crypto/nonce.rs new file mode 100644 index 00000000..116b71de --- /dev/null +++ b/core/src/crypto/nonce.rs @@ -0,0 +1,11 @@ +use rand::RngCore; + +pub fn generate_nonce() -> Vec { + let mut nonce = vec![0; 32]; + rand::thread_rng().fill_bytes(&mut nonce); + nonce +} + +pub fn validate_nonce(nonce: &[u8]) -> bool { + nonce.len() >= 16 && nonce.len() <= 128 +} diff --git a/core/src/crypto/token.rs b/core/src/crypto/token.rs new file mode 100644 index 00000000..bc5d74a5 --- /dev/null +++ b/core/src/crypto/token.rs @@ -0,0 +1,254 @@ +use crate::crypto::hash; +use crate::util; +use ed25519_dalek::ed25519::signature::rand_core::OsRng; +use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; +use ed25519_dalek::pkcs8::spki::der::zeroize::Zeroizing; +use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey}; +use ed25519_dalek::{Signer, Verifier}; + +pub struct SigningTokenKey { + inner: ed25519_dalek::SigningKey, +} + +impl SigningTokenKey { + pub fn to_verifying_key(&self) -> Box { + Box::new(Ed25519VerifyingKey { + inner: self.inner.verifying_key(), + }) + } +} + +pub trait VerifyingTokenKey { + fn verify(&self, msg: &[u8], signature: &[u8]) -> anyhow::Result<()>; + + fn to_der(&self) -> anyhow::Result>; + + fn signature_method(&self) -> &'static str; +} + +struct Ed25519VerifyingKey { + inner: ed25519_dalek::VerifyingKey, +} + +struct RsaPssVerifyingKey { + inner: rsa::pss::VerifyingKey, +} + +impl VerifyingTokenKey for Ed25519VerifyingKey { + fn verify(&self, msg: &[u8], signature: &[u8]) -> anyhow::Result<()> { + let signature = ed25519_dalek::Signature::from_slice(signature)?; + self.inner.verify(msg, &signature)?; + Ok(()) + } + + fn to_der(&self) -> anyhow::Result> { + Ok(self.inner.to_public_key_der()?.into_vec()) + } + + fn signature_method(&self) -> &'static str { + "ed25519" + } +} + +impl VerifyingTokenKey for RsaPssVerifyingKey { + fn verify(&self, msg: &[u8], signature: &[u8]) -> anyhow::Result<()> { + let signature = rsa::pss::Signature::try_from(signature)?; + self.inner.verify(msg, &signature)?; + Ok(()) + } + + fn to_der(&self) -> anyhow::Result> { + Ok(self.inner.to_public_key_der()?.into_vec()) + } + + fn signature_method(&self) -> &'static str { + "rsa-pss" + } +} + +pub fn generate_key() -> SigningTokenKey { + let mut csprng = OsRng; + SigningTokenKey { + inner: ed25519_dalek::SigningKey::generate(&mut csprng), + } +} + +pub fn export_private_key(key: &SigningTokenKey) -> anyhow::Result> { + let pem = key.inner.to_pkcs8_pem(LineEnding::LF)?; + Ok(pem) +} + +pub fn parse_private_key(private_key: &str) -> anyhow::Result { + let parsed = ed25519_dalek::SigningKey::from_pkcs8_pem(private_key)?; + Ok(SigningTokenKey { inner: parsed }) +} + +pub fn export_public_key(key: &SigningTokenKey) -> anyhow::Result { + let pem = key + .inner + .verifying_key() + .to_public_key_pem(LineEnding::LF)?; + Ok(pem) +} + +pub fn parse_public_key( + public_key: &str, + identifier: &str, +) -> anyhow::Result> { + Ok(match identifier { + "ed25519" => Box::new(Ed25519VerifyingKey { + inner: ed25519_dalek::VerifyingKey::from_public_key_pem(public_key)?, + }), + "rsa-pss" => Box::new(RsaPssVerifyingKey { + inner: { + let public_key = rsa::RsaPublicKey::from_public_key_pem(public_key)?; + rsa::pss::VerifyingKey::new(public_key) + }, + }), + _ => return Err(anyhow::anyhow!("Unsupported key type")), + }) +} + +pub fn generate_token_timestamp(key: &SigningTokenKey) -> anyhow::Result { + let salt = util::time::unix_timestamp_u64()?.to_le_bytes(); + let result = generate_token_nonce(key, &salt)?; + Ok(result) +} + +pub fn generate_token_nonce(key: &SigningTokenKey, salt: &[u8]) -> anyhow::Result { + let digest = { + let public_key = key.inner.verifying_key().to_public_key_der()?; + let hash_input = [public_key.as_bytes(), &salt].concat(); + hash::sha256(&hash_input) + }; + let signature = key.inner.sign(&digest); + + let hash_method = "sha256"; + let hash_base64 = util::base64::encode(&digest); + let salt_base64 = util::base64::encode(&salt); + let sign_method = "ed25519"; + let signature_base64 = util::base64::encode(signature.to_bytes()); + + let result = + format!("{hash_method}.{hash_base64}.{salt_base64}.{sign_method}.{signature_base64}"); + + Ok(result) +} + +pub fn extract_signature_identifier(token: &str) -> Option<&str> { + let parts: Vec<&str> = token.split('.').collect(); + parts.get(3).copied() +} + +pub fn verify_token_timestamp(public_key: &dyn VerifyingTokenKey, token: &str) -> bool { + verify_token_with_result(public_key, token, |salt| { + let salt = { + if salt.len() != 8 { + return Err(anyhow::anyhow!("Invalid salt length")); + } + u64::from_le_bytes( + salt.try_into() + .map_err(|_| anyhow::anyhow!("Invalid salt"))?, + ) + }; + + let now_seconds = util::time::unix_timestamp_u64()?; + if now_seconds - salt > 60 * 60 { + // Fingerprint is older than 1h, reject + return Err(anyhow::anyhow!("Fingerprint timestamp expired")); + } + + Ok(()) + }) + .is_ok() +} + +pub fn verify_token_nonce(public_key: &dyn VerifyingTokenKey, token: &str, nonce: &[u8]) -> bool { + verify_token_with_result(public_key, token, |salt| { + if salt != nonce { + return Err(anyhow::anyhow!("Invalid nonce")); + } + + Ok(()) + }) + .is_ok() +} + +pub fn verify_token_with_result( + public_key: &dyn VerifyingTokenKey, + token: &str, + verify_salt: impl Fn(&[u8]) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + let parts: Vec<&str> = token.split('.').collect(); + let [hash_method, hash_base64, salt_base64, sign_method, signature_base64] = parts[0..5] else { + return Err(anyhow::anyhow!("Invalid structure")); + }; + + if hash_method != "sha256" { + return Err(anyhow::anyhow!("Invalid hash method")); + } + + if sign_method != public_key.signature_method() { + return Err(anyhow::anyhow!("Invalid sign method")); + } + + let salt = { + let salt_bytes = util::base64::decode(salt_base64)?; + verify_salt(&salt_bytes)?; + salt_bytes + }; + + let digest = { + let public_key_der = public_key.to_der()?; + let hash_input = [public_key_der.as_slice(), &salt].concat(); + hash::sha256(&hash_input) + }; + + if util::base64::encode(&digest) != hash_base64 { + return Err(anyhow::anyhow!("Hash mismatch")); + } + + let Ok(signature) = util::base64::decode(signature_base64) else { + return Err(anyhow::anyhow!("Invalid signature base64 encoding")); + }; + + public_key + .verify(&digest, &signature) + .map_err(|_| anyhow::anyhow!("Invalid signature"))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_key() { + let key = generate_key(); + let pem = export_private_key(&key).unwrap(); + + let parsed = parse_private_key(&pem).unwrap(); + assert_eq!(parsed.inner.as_bytes(), key.inner.as_bytes()); + } + + #[test] + fn test_sign_verify() { + let key = generate_key(); + let data = b"hello world"; + let signature = key.inner.sign(data); + let verified = key + .to_verifying_key() + .verify(data, signature.to_vec().as_ref()) + .is_ok(); + assert!(verified); + } + + #[test] + fn test_fingerprint() { + let key = generate_key(); + let fingerprint = generate_token_timestamp(&key).unwrap(); + let verified = verify_token_timestamp(&*key.to_verifying_key(), &fingerprint); + assert!(verified); + } +} diff --git a/core/src/http/client.rs b/core/src/http/client.rs new file mode 100644 index 00000000..a9a686e6 --- /dev/null +++ b/core/src/http/client.rs @@ -0,0 +1,207 @@ +use crate::http::StatusCodeError; +use crate::model::discovery::{ProtocolType, RegisterDto, RegisterResponseDto}; +use crate::model::transfer::{PrepareUploadRequestDto, PrepareUploadResponseDto}; +use futures_util::StreamExt; +use reqwest::{Response, StatusCode}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +const BASE_PATH: &str = "/api/localsend/v2"; + +pub struct LsHttpClient { + client: reqwest::Client, +} + +pub struct RegisterResult { + /// The public key extracted from the certificate. + /// Encoded in PEM format. + /// Only available in HTTPS. + pub public_key: Option, + + /// The response body from the register request. + pub body: RegisterResponseDto, +} + +impl LsHttpClient { + pub fn try_new(private_key: &str, cert: &str) -> anyhow::Result { + let _ = rustls::crypto::ring::default_provider().install_default(); + + let identity = { + let pem = &[cert.as_bytes(), "\n".as_bytes(), private_key.as_bytes()].concat(); + reqwest::Identity::from_pem(pem)? + }; + + let client = reqwest::Client::builder() + .use_rustls_tls() + .danger_accept_invalid_certs(true) + .tls_info(true) + .identity(identity) + .build()?; + + Ok(Self { client }) + } + + pub async fn register( + &self, + protocol: &ProtocolType, + ip: &str, + port: u16, + payload: RegisterDto, + ) -> anyhow::Result { + let res = self + .client + .post(format!( + "{}://{}:{}{}/register", + protocol.as_str(), + ip, + port, + BASE_PATH + )) + .body(serde_json::to_string(&payload)?) + .send() + .await?; + + let public_key = match protocol { + ProtocolType::Https => Some(verify_cert_from_res(&res, None)?), + _ => None, + }; + + let body = res.json::().await?; + + Ok(RegisterResult { public_key, body }) + } + + pub async fn prepare_upload( + &self, + protocol: &ProtocolType, + ip: &str, + port: u16, + public_key: Option, + payload: PrepareUploadRequestDto, + ) -> anyhow::Result { + let res = self + .client + .post(format!( + "{}://{}:{}{}/prepare-upload", + protocol.as_str(), + ip, + port, + BASE_PATH + )) + .body(serde_json::to_string(&payload)?) + .send() + .await?; + + if let Some(public_key) = public_key { + verify_cert_from_res(&res, Some(public_key))?; + } + + if res.status() != StatusCode::OK { + return Err(status_code_error_from_res(res).await?); + } + + let body = res.json::().await?; + + Ok(body) + } + + /// Uploads a file to the server. + pub async fn upload( + &self, + protocol: &ProtocolType, + ip: &str, + port: u16, + session_id: String, + file_id: String, + token: String, + binary: mpsc::Receiver>, + ) -> anyhow::Result<()> { + let res = self + .client + .post(format!( + "{}://{}:{}{}/upload?sessionId={}&fileId={}&token={}", + protocol.as_str(), + ip, + port, + BASE_PATH, + session_id, + file_id, + token + )) + .body({ + let stream = ReceiverStream::new(binary).map(Ok::, anyhow::Error>); + reqwest::Body::wrap_stream(stream) + }) + .send() + .await?; + + if res.status() != StatusCode::OK { + return Err(status_code_error_from_res(res).await?); + } + + Ok(()) + } + + pub async fn cancel( + &self, + protocol: &ProtocolType, + ip: &str, + port: u16, + session_id: String, + ) -> anyhow::Result<()> { + self.client + .post(format!( + "{}://{}:{}{}/cancel?sessionId={}", + protocol.as_str(), + ip, + port, + BASE_PATH, + session_id + )) + .send() + .await?; + + Ok(()) + } +} + +/// Verifies the certificate from the response. +/// Returns the public key extracted from the certificate. +fn verify_cert_from_res(response: &Response, public_key: Option) -> anyhow::Result { + let tls_info_ext = response + .extensions() + .get::() + .ok_or_else(|| anyhow::anyhow!("TLS info not found"))?; + let cert = tls_info_ext + .peer_certificate() + .ok_or_else(|| anyhow::anyhow!("Certificate not found"))?; + let public_key = match public_key { + Some(public_key) => public_key.to_owned(), + None => crate::crypto::cert::public_key_from_cert_der(cert)?, + }; + crate::crypto::cert::verify_cert_from_der(cert, Some(public_key.clone()))?; + Ok(public_key) +} + +#[derive(Serialize, Deserialize)] +struct ErrorResponse { + message: String, +} + +async fn status_code_error_from_res(response: Response) -> anyhow::Result { + let status = response.status().as_u16(); + let body = response.text().await?; + let body = match serde_json::from_str::(&body) { + Ok(error) => error.message, + Err(_) => body, + }; + + Ok(anyhow::Error::new(StatusCodeError { + status, + message: match body { + _ if body.is_empty() => None, + _ => Some(body), + }, + })) +} diff --git a/core/src/http/mod.rs b/core/src/http/mod.rs new file mode 100644 index 00000000..6d462e24 --- /dev/null +++ b/core/src/http/mod.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +pub mod client; +pub mod server; + +#[derive(Debug, Error)] +#[error("{status};{message:?}")] +pub(crate) struct StatusCodeError { + status: u16, + message: Option, +} diff --git a/core/src/http/server.rs b/core/src/http/server.rs new file mode 100644 index 00000000..aca209d1 --- /dev/null +++ b/core/src/http/server.rs @@ -0,0 +1,274 @@ +use crate::crypto::cert::public_key_from_cert_der; +use bytes::Bytes; +use http_body_util::{BodyExt, Full}; +use hyper::body::Incoming; +use hyper::{Method, Request, Response, StatusCode}; +use hyper_util::rt::{TokioExecutor, TokioIo}; +use hyper_util::server::conn::auto::Builder; +use rustls::client::danger::HandshakeSignatureValid; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, UnixTime}; +use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; +use rustls::server::WebPkiClientVerifier; +use rustls::{DigitallySignedStruct, DistinguishedName, Error, RootCertStore, SignatureScheme}; +use std::fmt::{Debug, Formatter}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::ops::Deref; +use std::sync::Arc; +use tokio::sync::{oneshot, Mutex}; +use x509_parser::nom::AsBytes; + +pub struct LsHttpServer { + stop_tx: Arc>>>, +} + +impl LsHttpServer { + pub async fn start_with_port( + port: u16, + tls_config: Option, + ) -> anyhow::Result { + let ipv4_socket_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port); + let ipv6_socket_addr = SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port); + + let (stop_tx, stop_rx) = oneshot::channel::<()>(); + + tokio::spawn(async move { + tokio::select! { + result = start_server_with_addr(ipv4_socket_addr, tls_config.clone()) => { + tracing::info!("Server stopped on: {}, {:?}", ipv4_socket_addr, result); + } + _ = async { + if start_server_with_addr(ipv6_socket_addr, tls_config).await.is_err() { + tracing::warn!("Failed to start server on: {}", ipv6_socket_addr); + + // Keep the future running forever so we continue using ipv4 only. + tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await; + } + } => {} + _ = stop_rx => {} + } + }); + + Ok(LsHttpServer { + stop_tx: Arc::new(Mutex::new(Some(stop_tx))), + }) + } + + pub async fn stop(&self) -> anyhow::Result<()> { + let mut stop_tx = self.stop_tx.lock().await; + + if let Some(stop_tx) = stop_tx.take() { + stop_tx + .send(()) + .map_err(|_| anyhow::anyhow!("Failed to send stop signal"))?; + } + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct TlsConfig { + pub cert: String, + pub private_key: String, +} + +async fn start_server_with_addr( + socket_addr: SocketAddr, + tls_config: Option, +) -> anyhow::Result<()> { + let _ = rustls::crypto::ring::default_provider().install_default(); + + let incoming = tokio::net::TcpListener::bind(socket_addr).await?; + + let tls_acceptor = match tls_config { + Some(tls_config) => Some(create_tls_config(&tls_config).inspect_err(|err| { + tracing::error!("failed to create tls config: {err:#}"); + })?), + None => None, + }; + + tracing::info!( + "Started server on {} (TLS: {})", + socket_addr, + tls_acceptor.is_some() + ); + + loop { + let (tcp_stream, _remote_addr) = incoming.accept().await?; + + let tls_acceptor = tls_acceptor.clone(); + tokio::spawn(async move { + let res = match tls_acceptor { + Some(tls_acceptor) => { + let tls_stream = match tls_acceptor.accept(tcp_stream).await { + Ok(tls_stream) => tls_stream, + Err(err) => { + tracing::warn!("TLS handshake error: {err:#}"); + return; + } + }; + + let client_cert = { + let (_, server_connection) = tls_stream.get_ref(); + ClientCertExt { + cert: server_connection + .deref() + .deref() + .peer_certificates() + .map(|cert| cert.get(0).unwrap().to_vec()), + } + }; + + Builder::new(TokioExecutor::new()) + .serve_connection( + TokioIo::new(tls_stream), + hyper::service::service_fn(move |mut req: Request| { + req.extensions_mut() + .insert::(client_cert.clone()); + echo(req) + }), + ) + .await + } + None => { + Builder::new(TokioExecutor::new()) + .serve_connection( + TokioIo::new(tcp_stream), + hyper::service::service_fn(move |req: Request| echo(req)), + ) + .await + } + }; + + if let Err(err) = res { + tracing::warn!("Failed to serve connection: {err:#}"); + } + }); + } +} + +fn create_tls_config(tls_config: &TlsConfig) -> anyhow::Result { + let config = { + let certs = vec![CertificateDer::from_pem_slice(&tls_config.cert.as_bytes())?]; + let key = PrivateKeyDer::from_pem_slice(&tls_config.private_key.as_bytes())?; + + rustls::ServerConfig::builder() + .with_client_cert_verifier(Arc::new(CustomClientCertVerifier::try_new( + &tls_config.cert, + )?)) + .with_single_cert(certs, key)? + }; + Ok(tokio_rustls::TlsAcceptor::from(Arc::new(config))) +} + +async fn echo(req: Request) -> Result>, hyper::Error> { + let mut response = Response::new(Full::default()); + + match (req.method(), req.uri().path()) { + (&Method::GET, "/") => { + let cert = req.extensions().get::(); + println!("{:?}", cert.extract_public_key()); + + *response.body_mut() = Full::from("Try POST /echo\n"); + } + (&Method::POST, "/echo") => { + *response.body_mut() = Full::from(req.into_body().collect().await?.to_bytes()); + } + // Catch-all 404. + _ => { + *response.status_mut() = StatusCode::NOT_FOUND; + } + }; + Ok(response) +} + +#[derive(Clone, Debug)] +struct ClientCertExt { + /// The client certificate in DER format. + cert: Option>, +} + +trait PublicCertExt { + fn extract_public_key(&self) -> Option; +} + +impl PublicCertExt for Option<&ClientCertExt> { + fn extract_public_key(&self) -> Option { + self.as_ref() + .map(|cert| public_key_from_cert_der(cert.cert.as_ref().unwrap()).unwrap()) + } +} + +struct CustomClientCertVerifier { + inner: Arc, +} + +impl CustomClientCertVerifier { + fn try_new(cert: &str) -> anyhow::Result { + // We add the certificate of the server itself just so that no "empty" error is returned. + // We don't care about the authority of the certificate, just that it is valid. + let mut root_cert_store = RootCertStore::empty(); + root_cert_store.add(PemObject::from_pem_slice(cert.as_bytes())?)?; + + Ok(Self { + inner: WebPkiClientVerifier::builder(Arc::new(root_cert_store)).build()?, + }) + } +} + +impl Debug for CustomClientCertVerifier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl ClientCertVerifier for CustomClientCertVerifier { + fn offer_client_auth(&self) -> bool { + true + } + + fn client_auth_mandatory(&self) -> bool { + true + } + + fn root_hint_subjects(&self) -> &[DistinguishedName] { + self.inner.root_hint_subjects() + } + + fn verify_client_cert( + &self, + cert: &CertificateDer<'_>, + _: &[CertificateDer<'_>], + _: UnixTime, + ) -> Result { + // We trust any certificate that is valid. + crate::crypto::cert::verify_cert_from_der(cert.as_bytes(), None).map_err(|e| { + tracing::warn!("Client certificate verification failed: {e:#}"); + Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure) + })?; + Ok(ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.inner.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.inner.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + self.inner.supported_verify_schemes() + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 00000000..8a3ad592 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "crypto")] +pub mod crypto; +#[cfg(feature = "http")] +pub mod http; +pub mod model; +pub(crate) mod util; +pub mod webrtc; diff --git a/core/src/main.rs b/core/src/main.rs new file mode 100644 index 00000000..d1bad83e --- /dev/null +++ b/core/src/main.rs @@ -0,0 +1,480 @@ +mod crypto; +mod http; +mod model; +mod util; +mod webrtc; + +use crate::crypto::token; +use crate::http::client::LsHttpClient; +use crate::http::server::TlsConfig; +use crate::model::discovery::{DeviceType, ProtocolType, RegisterDto}; +use crate::model::transfer::PrepareUploadRequestDto; +use crate::webrtc::signaling::{ClientInfo, WsServerMessage}; +use crate::webrtc::webrtc::{PinConfig, RTCFile, RTCFileError, RTCSendFileResponse, RTCStatus}; +use anyhow::Result; +use bytes::Bytes; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use tokio::fs::File; +use tokio::io; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::{mpsc, oneshot}; +use tracing::Level; + +#[tokio::main] +#[cfg(feature = "full")] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .init(); + + webrtc_test().await?; + + Ok(()) +} + +const PRIVATE_KEY: &str = "-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqeusikjBGJ/mqG+RYPyNaP2M6/YafR5bVcEr0NirDntRaSI8 +SBVy6ezqGnpJJpez2rVcLfqPOZNW+yhiWmX/DFGAbKWNUjpAfEgQ0ySS3EKEfTGa +kpbBgVmSgnJKu0cuFHk3LRQXZc9USWRtfZu/HLwrxeTy0ynKBjqctkcJmyEOleSE +tWwx/sFUszI4j3QH7iAg+jJu07qCaBv1iOVoFLwtvtkHP4pIflPi4FR0nUn8VpTR +8j3h1Z/Ea6j2nW/CfatfhOiwrlOgjpd1CFtU5OoUk0OiHYgUTLRvOR0ebmKLJZp9 +2x85h3ucuwzcNHXds6IrBsV7dcMeN9+nI2yYHwIDAQABAoIBAAYVzZEKN/gUyeLg +U/mAMeQ/qEtO/fXbH3Q7vcD18XJMUkcMldITCpF8DYozNOlv513+vrVa0sRCFYxb +DuKj4nVjedDqQNxf/60zu36EQconi60cGKgFRBrIxWlshGaejvTmvmYb4RahTShv +s0gbSsXRq1Oj9lo/ld+RO8l/U8W9Y2KlHc14VbAHCxlBd70Ngpw/hKzt7jVwUt2O +QMAgek5Ffbjoqk/GvwdYFtgLHLYKWNdaqt/dGCZcDWPNOv93Lb+XuI3orAcSr5T7 +V2fseLMrrQfKr4dK+DSxvB/McAahSY+6sxpm972D1MYBoDM/yCK/jcdV4T83ofIl +tnavOyECgYEA077OpgXt798miVtybRgPv2cEZHnMZStHhNOO/3A2J0mnT5lCGT8M +FnB45LE7NxP5yXLi1/cBSazwF/W2TKu4g42y9X0lPzik6uM4F7AZLCwO6zd4etwa +NwKjCiPBGJkWBODQ2IeK6Gy9gnTAnGhzuPoTYEl2AH5REbM8btsRve8CgYEAzW8U +8sztt1cLWKwAsf66KZOjU03UoAhgofhXeyCDW4tLZ/l7F2cEfRaRkIx40TU1tNSy +R130DsiRmJ/7vSy00qaC7IZgFRnDXM8oWSvE31p3AiZEvMzh3cMl67u/TPFn/Zhr +iDE2fxTmNf9a860IbYeGqbOj11fFsSMNZIYf+NECgYACR/Ht8+5mQR8nJ6cJ6dJx +m2h+tJkxFdBFbAoEUm8i6TY2M050+yrkKv4CaK5cn4h3VReAgBaxdn13pJv8I3Vv +ZV1iK6D1F2Ufaqc2Ch2bTjYy7nwLxsc5hHvBJjV0UGHeV5WoX31tl45LE3rntHBa +s8b1qJTu2G2DJU0nXJDKXQKBgEhxvrpsp/u6d2baqRgb0vxscvEihjO1IJadpAPo +kEoNEhdldBHpozyVY9nMn6JvGDRfuUrPiAxakHV5HWY1yMJsM8lDDckDH9CvwPPJ +KpD1LviUFDNcMN5qPgomWCzDCL/2Kx2I9UXVUeWC2kkKIOm3HDbmAYYkDrQLv2JO +piGxAoGAfbhVHgMhroI64t64NaVpXiHy2bd36q7hLVm2+bTDpPphHwn4kIsFvVr3 +uGPVsyoOa68s1eXnOnh5TzhTltsjyAYfiKo/7ZX6mHMctFlgt4njailDWOqHwj0c +Uy/QlsvXsOcN/Y99HULigND8C49F5Sz9Ih9G1DGLvd0BUUI/+qg= +-----END RSA PRIVATE KEY-----"; + +const CERT: &str = "-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh +bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG +A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjEwMDE1ODM3WhcNMzUwMjA4MDE1ODM3 +WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL +EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCp66yKSMEYn+aob5Fg/I1o/Yzr9hp9HltVwSvQ +2KsOe1FpIjxIFXLp7Ooaekkml7PatVwt+o85k1b7KGJaZf8MUYBspY1SOkB8SBDT +JJLcQoR9MZqSlsGBWZKCckq7Ry4UeTctFBdlz1RJZG19m78cvCvF5PLTKcoGOpy2 +RwmbIQ6V5IS1bDH+wVSzMjiPdAfuICD6Mm7TuoJoG/WI5WgUvC2+2Qc/ikh+U+Lg +VHSdSfxWlNHyPeHVn8RrqPadb8J9q1+E6LCuU6COl3UIW1Tk6hSTQ6IdiBRMtG85 +HR5uYoslmn3bHzmHe5y7DNw0dd2zoisGxXt1wx4336cjbJgfAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBAJ/bopM5NjK/Roi1bS+qAQ7EHeVNfLyPgAReyJESHsg3mBEE +FhP729KlHcNCvaAnmxEaUH2XZTmP3s0m9IVabHhdFEyIibMQ/Qpid/JIDsG2IRw7 +oNJj8z0C7eDjC9eWR+wZ2d0nnyNpWghcqAqqZSBuNpJ9jDqmg4LzdNXZUvh9e1Cq +qizxa3CQEHRYqdL/hA1N6eq7GkeiIeP+cbvWGmcSf8SS/ORMKvvDGzkGs2mFZnY/ +DZmiOOqkzvgZOgOVQ2vFuJIXyZ/tY0ez35dtQYLhKRljlXjckA/PFuTJDa2kq1Rv +qqsPsY3pRq93zkKNx1xRtURBiJEvA/Js2+hHWrU= +-----END CERTIFICATE-----"; + +async fn crypto_test() -> Result<()> { + let key = token::generate_key(); + + let pem = token::export_private_key(&key)?; + + println!("Pem: {}", pem.as_str()); + + let public_key = token::export_public_key(&key)?; + + println!("Public Key: {}", public_key); + + let fingerprint = token::generate_token_timestamp(&key)?; + + println!("Fingerprint: {}", fingerprint); + + let parsed_key = token::parse_public_key( + "-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAZmdXP230oqK92o65ra3XaF2F8r3+fK5DEBK4c40qVts= +-----END PUBLIC KEY-----", + "ed25519", + )?; + + let signature = token::verify_token_with_result( + &*parsed_key, + "sha256.RikOdJlAUTdMVFZjEk7Bft5G9cxnNBBLfgttPpyS2FY.hJCuZwAAAAA.ed25519.iNgHrRzX2Iel-Ozj47yn5o5v0cGY_BswK6JYqwY65j7Krpr43KanAaCrjUng7gHtc2pCcylUrKswR_rxyswhDA", + |_| Ok(()), + ); + + println!("Signature Verification: {:?}", signature); + + Ok(()) +} + +async fn server_test() -> Result<()> { + let server = http::server::LsHttpServer::start_with_port( + 53317, + Some(TlsConfig { + cert: CERT.to_string(), + private_key: PRIVATE_KEY.to_string(), + }), + ) + .await?; + tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await; + server.stop().await?; + Ok(()) +} + +async fn http_test() -> Result<()> { + let client = LsHttpClient::try_new(PRIVATE_KEY, CERT)?; + + let register_dto = RegisterDto { + alias: "test 2".to_string(), + version: "2.3".to_string(), + device_model: Some("test".to_string()), + device_type: Some(crate::model::discovery::DeviceType::Headless), + fingerprint: "test".to_string(), + port: 53317, + protocol: ProtocolType::Https, + download: false, + }; + + let response = client + .register( + &ProtocolType::Https, + "localhost", + 53317, + register_dto.clone(), + ) + .await?; + + println!("Public Key: {:?}", response.public_key); + println!("Body: {:?}", response.body); + + let prepare_upload_dto = PrepareUploadRequestDto { + info: register_dto, + files: { + let mut map = HashMap::new(); + let id = "test-123-id".to_string(); + let file = crate::model::transfer::FileDto { + id: id.clone(), + file_name: "test.mp4".to_string(), + size: 1000, + file_type: "video/mp4".to_string(), + sha256: None, + preview: None, + metadata: None, + }; + map.insert(id, file); + map + }, + }; + + let prepare_upload_response = client + .prepare_upload( + &ProtocolType::Https, + "localhost", + 53317, + response.public_key, + prepare_upload_dto, + ) + .await?; + + println!("Prepare Upload Response: {:?}", prepare_upload_response); + + Ok(()) +} + +async fn webrtc_test() -> Result<()> { + let info = webrtc::signaling::ClientInfoWithoutId { + alias: "test".to_string(), + version: "2.3".to_string(), + device_model: Some("test".to_string()), + device_type: Some(DeviceType::Desktop), + token: "test".to_string(), + }; + let connection = + webrtc::signaling::SignalingConnection::connect("wss://public.localsend.org/v1/ws", &info) + .await?; + + let (managed_connection, mut rx) = connection.start_listener(); + let managed_connection = Arc::new(managed_connection); + + while let Some(message) = rx.recv().await { + let stun_servers = vec!["stun:stun.l.google.com:19302".to_string()]; + match message { + WsServerMessage::Join { peer } => { + send_handler(managed_connection.clone(), stun_servers, peer).await; + return Ok(()); + } + WsServerMessage::Offer(offer) => { + receive_handler(managed_connection.clone(), stun_servers, offer).await; + } + _ => {} + } + } + + Ok(()) +} + +async fn send_handler( + connection: Arc, + stun_servers: Vec, + peer: ClientInfo, +) { + tracing::info!("Joined: {peer:?}"); + let (status_tx, mut status_rx) = mpsc::channel::(1); + let (selected_tx, mut selected_rx) = oneshot::channel::>(); + let (error_tx, mut error_rx) = mpsc::channel::(1); + let (pin_tx, mut pin_rx) = mpsc::channel::>(1); + let (pair_tx, mut pair_rx) = oneshot::channel::>(); + let (send_tx, send_rx) = mpsc::channel::(1); + + let files = vec![model::transfer::FileDto { + id: "test-123-id".to_string(), + file_name: "test.mp4".to_string(), + size: 100, + file_type: "video/mp4".to_string(), + sha256: None, + preview: None, + metadata: None, + }]; + + let send_task = tokio::spawn({ + let files = files.clone(); + async move { + webrtc::webrtc::send_offer( + &connection, + stun_servers, + peer.id, + token::generate_key(), + None, + Some(PinConfig { + pin: "456".to_string(), + max_tries: 3, + }), + files, + status_tx, + selected_tx, + error_tx, + pin_tx, + pair_tx, + send_rx, + ) + .await + .expect("Failed to send offer"); + + tracing::info!("Send offer completed"); + } + }); + + tokio::spawn(async move { + while let Some(status) = status_rx.recv().await { + tracing::info!("Status: {status:?}"); + } + tracing::info!("Closed channel: status"); + }); + + tokio::spawn(async move { + while let Some(error) = error_rx.recv().await { + tracing::info!("Error: {error:?}"); + } + tracing::info!("Closed channel: error"); + }); + + tokio::spawn(async move { + let mut pin_tries = vec!["1".to_string(), "2".to_string(), "123".to_string()].into_iter(); + while let Some(send_pin) = pin_rx.recv().await { + let pin = pin_tries.next().expect("Failed to get pin"); + + tracing::info!("Sending pin: {pin}"); + send_pin.send(pin).expect("Failed to send pin"); + } + tracing::info!("Closed channel: status"); + }); + + tokio::spawn(async move { + let Ok(send_pair) = pair_rx.await else { + return; + }; + + tracing::info!("Declining Pair"); + send_pair.send(false).expect("Failed to send pair"); + tracing::info!("Closed channel: status"); + }); + + tokio::spawn(async move { + let Ok(selected) = selected_rx.await else { + return; + }; + + tracing::info!("Selected: {selected:?}"); + + let file = files.first().unwrap(); + let (tx, mut rx) = mpsc::channel::(16); + send_tx + .try_send(RTCFile { + file_id: file.id.clone(), + binary_rx: rx, + }) + .expect("Failed to send file"); + + let file_path = "/Users/user/Downloads/test/send/test.mp4"; + let start_time = std::time::Instant::now(); + read_file_to_sender(file_path, tx) + .await + .expect("Failed to read file"); + + let file_size = std::fs::metadata(file_path).unwrap().len(); + tracing::info!( + "Sending file completed in {:?}, speed: {} MB/s", + start_time.elapsed(), + file_size as f64 / 1024.0 / 1024.0 / start_time.elapsed().as_secs_f64() + ); + }); + + let result = send_task.await; + tracing::info!("Send task finished with result: {:?}", result); +} + +async fn receive_handler( + connection: Arc, + stun_servers: Vec, + offer: webrtc::signaling::WsServerSdpMessage, +) { + tracing::info!("Offer: {offer:?}"); + let (status_tx, mut status_rx) = mpsc::channel::(1); + let (files_tx, files_rx) = oneshot::channel::>(); + let (selected_tx, selected_rx) = oneshot::channel::>>(); + let (error_tx, mut error_rx) = mpsc::channel::(1); + let (pin_tx, mut pin_rx) = mpsc::channel::>(1); + let (receiving_tx, mut receiving_rx) = mpsc::channel::(1); + let (user_error_tx, user_error_rx) = mpsc::channel::(1); + + let receive_task = tokio::spawn(async move { + webrtc::webrtc::accept_offer( + &connection, + stun_servers, + &offer, + token::generate_key(), + None, + Some(PinConfig { + pin: "123".to_string(), + max_tries: 3, + }), + status_tx, + files_tx, + selected_rx, + error_tx, + pin_tx, + receiving_tx, + user_error_rx, + ) + .await + .expect("Failed to accept offer"); + + tracing::info!("Accept offer completed"); + }); + + tokio::spawn(async move { + while let Some(status) = status_rx.recv().await { + tracing::info!("Status: {status:?}"); + } + tracing::info!("Closed channel: status"); + }); + + tokio::spawn(async move { + while let Some(error) = error_rx.recv().await { + tracing::info!("Error: {error:?}"); + } + tracing::info!("Closed channel: error"); + }); + + tokio::spawn(async move { + let mut pin_tries = vec!["1".to_string(), "2".to_string(), "456".to_string()].into_iter(); + while let Some(send_pin) = pin_rx.recv().await { + let pin = pin_tries.next().expect("Failed to get pin"); + + tracing::info!("Sending pin: {pin}"); + send_pin.send(pin).expect("Failed to send pin"); + } + tracing::info!("Closed channel: status"); + }); + + tokio::spawn(async move { + let Ok(files) = files_rx.await else { + return; + }; + + tracing::info!("Files: {files:?}"); + + selected_tx + .send(Some(files.iter().map(|file| file.id.clone()).collect())) + .expect("Failed to send selected"); + + while let Some(file) = receiving_rx.recv().await { + tracing::info!("Receiving file: {file:?}"); + + let file_dto = files.iter().find(|f| f.id == file.file_id).unwrap(); + + let file_path = format!("/Users/user/Downloads/test/{}", file_dto.file_name); + write_file_from_receiver(file_path.as_ref(), file.binary_rx) + .await + .expect("Failed to write file"); + + user_error_tx + .send(RTCSendFileResponse { + id: file.file_id, + success: true, + error: None, + }) + .await + .expect("Failed to send response"); + } + + tracing::info!("Receiving files completed"); + }); + + let result = receive_task.await; + tracing::info!("Receive task finished with result: {:?}", result); +} + +async fn read_file_to_sender(file_path: &str, sender: mpsc::Sender) -> io::Result<()> { + let mut file = File::open(file_path).await?; + + let mut buffer = [0u8; 1024]; + + loop { + // Read a chunk of the file + let bytes_read = file.read(&mut buffer).await?; + + if bytes_read == 0 { + break; // EOF + } + + // Send the chunk through the channel + let chunk = Bytes::copy_from_slice(&buffer[..bytes_read]); + if sender.send(chunk).await.is_err() { + tracing::error!("Receiver dropped, stopping."); + break; + } + } + + Ok(()) +} + +async fn write_file_from_receiver( + file_path: &str, + mut receiver: mpsc::Receiver, +) -> io::Result<()> { + let mut file = File::create(file_path).await?; + + while let Some(chunk) = receiver.recv().await { + file.write_all(&chunk).await?; + } + + Ok(()) +} diff --git a/core/src/model/discovery.rs b/core/src/model/discovery.rs new file mode 100644 index 00000000..44e4a1dd --- /dev/null +++ b/core/src/model/discovery.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisterDto { + pub alias: String, + pub version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_type: Option, + pub fingerprint: String, + pub port: u16, + pub protocol: ProtocolType, + pub download: bool, +} + +/// Similar to `RegisterDto`, but without `port` and `protocol` (those are already known). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisterResponseDto { + pub alias: String, + pub version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_type: Option, + pub fingerprint: String, + pub download: bool, +} + +// TODO: Change enums to SCREAMING_SNAKE_CASE for v3 + +#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum DeviceType { + Mobile, + Desktop, + Web, + Headless, + Server, +} + +#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum ProtocolType { + Http, + Https, +} + +impl ProtocolType { + pub fn as_str(&self) -> &str { + match self { + ProtocolType::Http => "http", + ProtocolType::Https => "https", + } + } +} diff --git a/core/src/model/mod.rs b/core/src/model/mod.rs new file mode 100644 index 00000000..0fffa94f --- /dev/null +++ b/core/src/model/mod.rs @@ -0,0 +1,2 @@ +pub mod discovery; +pub mod transfer; diff --git a/core/src/model/transfer.rs b/core/src/model/transfer.rs new file mode 100644 index 00000000..24610555 --- /dev/null +++ b/core/src/model/transfer.rs @@ -0,0 +1,40 @@ +use crate::model::discovery::RegisterDto; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FileDto { + pub id: String, + pub file_name: String, + pub size: u64, + pub file_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub sha256: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub preview: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileMetadata { + #[serde(skip_serializing_if = "Option::is_none")] + pub modified: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub accessed: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PrepareUploadRequestDto { + pub info: RegisterDto, + pub files: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PrepareUploadResponseDto { + pub session_id: String, + pub files: HashMap, +} diff --git a/core/src/util/base64.rs b/core/src/util/base64.rs new file mode 100644 index 00000000..e3505860 --- /dev/null +++ b/core/src/util/base64.rs @@ -0,0 +1,13 @@ +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::engine::GeneralPurpose; +use base64::{DecodeError, Engine}; + +const BASE_64_ENGINE: GeneralPurpose = URL_SAFE_NO_PAD; + +pub fn encode>(data: T) -> String { + BASE_64_ENGINE.encode(data) +} + +pub fn decode(data: &str) -> Result, DecodeError> { + BASE_64_ENGINE.decode(data) +} diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs new file mode 100644 index 00000000..dad3ff51 --- /dev/null +++ b/core/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod base64; +pub(crate) mod time; diff --git a/core/src/util/time.rs b/core/src/util/time.rs new file mode 100644 index 00000000..6c229e43 --- /dev/null +++ b/core/src/util/time.rs @@ -0,0 +1,7 @@ +use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; + +pub(crate) fn unix_timestamp_u64() -> Result { + let seconds = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + + Ok(seconds) +} diff --git a/core/src/webrtc/mod.rs b/core/src/webrtc/mod.rs new file mode 100644 index 00000000..dcc8d73d --- /dev/null +++ b/core/src/webrtc/mod.rs @@ -0,0 +1,3 @@ +pub mod signaling; +#[cfg(feature = "webrtc")] +pub mod webrtc; diff --git a/core/src/webrtc/signaling.rs b/core/src/webrtc/signaling.rs new file mode 100644 index 00000000..4a6ca57a --- /dev/null +++ b/core/src/webrtc/signaling.rs @@ -0,0 +1,528 @@ +use crate::model::discovery::DeviceType; +use crate::util::base64; +use anyhow::Result; +use futures_util::stream::StreamExt; +use futures_util::SinkExt; +use serde::{Deserialize, Serialize}; +use std::cmp::PartialEq; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +use tokio::time::Duration; +#[cfg(feature = "webrtc-signaling")] +use tokio_tungstenite::connect_async; +#[cfg(feature = "webrtc-signaling")] +use tungstenite::{Bytes, Message}; +use uuid::Uuid; + +/// A message sent by the server to the client. +#[derive(Clone, Deserialize, Eq, Serialize, Debug, PartialEq)] +#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WsServerMessage { + /// The initial message sent to the client that has just connected. + Hello { + /// The client that has just connected. + client: ClientInfo, + + /// The list of members (excluding the client) in the IP room. + peers: Vec, + }, + + /// A new peer has joined the IP room. + Join { + /// The peer that triggered the message. + peer: ClientInfo, + }, + + /// A peer has updated its information. + Update { + /// The peer that triggered the message. + peer: ClientInfo, + }, + + /// A peer has left the IP room. + Left { + /// The ID of the peer that triggered the message. + #[serde(rename = "peerId")] + peer_id: Uuid, + }, + + /// SDP offer from a peer to another peer. + Offer(WsServerSdpMessage), + + /// SDP answer from a peer to another peer. + Answer(WsServerSdpMessage), + + /// Error message. + Error { + /// The error code. + code: u16, + }, +} + +#[derive(Clone, Deserialize, Eq, Serialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct WsServerSdpMessage { + /// The peer that triggered the message. + pub peer: ClientInfo, + + /// The session ID of the answer. + pub session_id: String, + + /// The SDP string for the answer. + /// Compressed with zlib, then encoded with base64 without padding. + pub sdp: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ClientInfo { + /// The ID of the peer. Generated by the server. + pub id: Uuid, + + /// The name of the peer. + pub alias: String, + + /// Client Protocol Version (major.minor) + pub version: String, + + /// The device model of the peer. + /// Windows, macOS, iPhone, Samsung, etc. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_model: Option, + + /// The device type of the peer. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_type: Option, + + /// A token generated by the client. + /// Used to merge the same peers detected on different channels (LAN, WebRTC, etc.). + pub token: String, +} + +impl ClientInfo { + pub fn from(info: ClientInfoWithoutId, id: Uuid) -> Self { + Self { + id, + alias: info.alias, + version: info.version, + device_model: info.device_model, + device_type: info.device_type, + token: info.token, + } + } +} + +/// The data that is encoded as JSON which is again encoded as base64. +/// Sent as query during websocket connection. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientInfoWithoutId { + /// The name of the peer. + pub alias: String, + + /// Client Protocol Version (major.minor) + pub version: String, + + /// The device model of the peer. + /// Windows, macOS, iPhone, Samsung, etc. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_model: Option, + + /// The device type of the peer. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_type: Option, + + /// A fingerprint generated by the client. + /// Used to merge the same peers detected on different channels (LAN, WebRTC, etc.). + pub token: String, +} + +impl From for ClientInfoWithoutId { + fn from(info: ClientInfo) -> Self { + Self { + alias: info.alias, + version: info.version, + device_model: info.device_model, + device_type: info.device_type, + token: info.token, + } + } +} + +/// The HTTP request sent by the client to the server. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WsClientMessage { + Update { info: ClientInfoWithoutId }, + Offer(WsClientSdpMessage), + Answer(WsClientSdpMessage), +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WsClientSdpMessage { + /// The session id to correctly associate answers with offers. + /// Generated by the client (the peer that sends the offer). + pub session_id: String, + + /// Target peer ID. + pub target: Uuid, + + /// The SDP offer. + /// Compressed with zlib, then encoded with base64 without padding. + pub sdp: String, +} + +pub struct SignalingConnection { + /// The peer info received from the server of the client. + pub client: ClientInfo, + + /// The sender to send messages to the server. + pub tx: mpsc::Sender, + + /// The receiver to receive messages from the server. + pub rx: mpsc::Receiver, +} + +#[cfg(feature = "webrtc-signaling")] +impl SignalingConnection { + pub async fn connect>( + uri: S, + info: &ClientInfoWithoutId, + ) -> Result { + let encoded_info = base64::encode(&serde_json::to_string(info)?); + let uri = format!("{}?d={}", uri.into(), encoded_info); + + tracing::debug!("Connecting to the signaling server at {uri}"); + + let (ws_stream, _) = connect_async(&uri).await?; + + tracing::debug!("Connected to the signaling server. Waiting for hello..."); + + let (mut write, read) = ws_stream.split(); + + let (send_tx, mut send_rx) = mpsc::channel(1); + + tokio::spawn(async move { + let timeout = Duration::from_secs(120); + loop { + let send_result = tokio::time::timeout(timeout, async { + if let Some(message) = send_rx.recv().await { + let message = + serde_json::to_string(&message).expect("Failed to serialize message"); + if write.send(Message::Text(message.into())).await.is_ok() { + return true; + } + } + + false + }) + .await; + + match send_result { + Ok(success) => { + if !success { + return; + } + } + Err(_) => { + // Timeout: send a ping message to keep the connection alive + if write.send(Message::Ping(Bytes::new())).await.is_err() { + return; + } + } + } + } + }); + + let (receive_tx, receive_rx) = mpsc::channel(1); + let (client_tx, mut client_rx) = mpsc::channel::(1); + + tokio::spawn(async move { + read.for_each(|message| async { + if let Ok(Message::Text(message)) = message { + match serde_json::from_str::(&message) { + Ok(message) => { + if let WsServerMessage::Hello { + client, + peers: _peers, + } = &message + { + if client_tx.send(client.clone()).await.is_err() { + return; + } + } + + match receive_tx.send(message).await { + Ok(_) => {} + Err(e) => tracing::error!("{e:?}"), + } + } + Err(e) => tracing::error!("Error: {e}, Server: {message}"), + } + } + }) + .await; + }); + + let client = client_rx.recv().await.unwrap(); + + tracing::debug!("Received hello from server: {client:?}"); + + Ok(SignalingConnection { + client, + tx: send_tx, + rx: receive_rx, + }) + } + + /// Listen for incoming messages from the server. + /// Upgrades the API to a higher-level API. + pub fn start_listener( + mut self, + ) -> (ManagedSignalingConnection, mpsc::Receiver) { + let (tx, rx) = mpsc::channel::(16); + let on_answer: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + + { + let on_answer = on_answer.clone(); + tokio::spawn(async move { + while let Some(message) = self.rx.recv().await { + // send answer + if let WsServerMessage::Answer(sdp) = message.clone() { + if let Some(callback) = on_answer.lock().await.remove(&sdp.session_id) { + callback(sdp); + } + } + + tx.send(message).await.unwrap(); + } + }); + } + + ( + ManagedSignalingConnection { + client: self.client, + tx: self.tx, + on_answer, + }, + rx, + ) + } + + pub async fn send_update(&self, info: ClientInfoWithoutId) -> Result<()> { + send_update(&self.tx, info).await?; + + Ok(()) + } + + pub async fn send_offer(&self, session_id: String, target: Uuid, sdp: String) -> Result<()> { + send_offer(&self.tx, session_id, target, sdp).await?; + + Ok(()) + } + + pub async fn send_answer(&self, session_id: String, target: Uuid, sdp: String) -> Result<()> { + send_answer(&self.tx, session_id, target, sdp).await?; + + Ok(()) + } +} + +type AnswerCallback = Box; + +pub struct ManagedSignalingConnection { + /// The peer info received from the server of the client. + pub client: ClientInfo, + tx: mpsc::Sender, + on_answer: Arc>>, +} + +#[cfg(feature = "webrtc-signaling")] +impl ManagedSignalingConnection { + pub async fn send_update(&self, info: ClientInfoWithoutId) -> Result<()> { + send_update(&self.tx, info).await?; + + Ok(()) + } + + pub async fn send_offer(&self, session_id: String, target: Uuid, sdp: String) -> Result<()> { + send_offer(&self.tx, session_id, target, sdp).await?; + + Ok(()) + } + + pub async fn send_answer(&self, session_id: String, target: Uuid, sdp: String) -> Result<()> { + send_answer(&self.tx, session_id, target, sdp).await?; + + Ok(()) + } + + /// Adds a callback to be called when an answer having a specific `session_id` is received. + pub async fn on_answer(&self, session_id: String, callback: F) + where + F: FnOnce(WsServerSdpMessage) + Send + Sync + 'static, + { + let mut callbacks = self.on_answer.lock().await; + callbacks.insert(session_id, Box::new(callback)); + } +} + +async fn send_update(tx: &mpsc::Sender, info: ClientInfoWithoutId) -> Result<()> { + tx.send(WsClientMessage::Update { info }).await?; + + tracing::debug!("Sent update to the server"); + + Ok(()) +} + +async fn send_offer( + tx: &mpsc::Sender, + session_id: String, + target: Uuid, + sdp: String, +) -> Result<()> { + tx.send(WsClientMessage::Offer(WsClientSdpMessage { + session_id: session_id.clone(), + target, + sdp, + })) + .await?; + + tracing::debug!("Sent offer to {target} with session ID {session_id}"); + + Ok(()) +} + +async fn send_answer( + tx: &mpsc::Sender, + session_id: String, + target: Uuid, + sdp: String, +) -> Result<()> { + tx.send(WsClientMessage::Answer(WsClientSdpMessage { + session_id: session_id.clone(), + target, + sdp, + })) + .await?; + + tracing::debug!("Sent answer to {target} with session ID {session_id}"); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ws_server_hello_message_encoding() { + let message = WsServerMessage::Hello { + client: ClientInfo { + id: Uuid::nil(), + alias: "Cute Apple".to_string(), + version: "2.3".to_string(), + device_model: Some("Dell".to_string()), + device_type: Some(DeviceType::Desktop), + token: "123".to_string(), + }, + peers: vec![], + }; + + let encoded = serde_json::to_string_pretty(&message).unwrap(); + + assert_eq!( + encoded, + r#"{ + "type": "HELLO", + "client": { + "id": "00000000-0000-0000-0000-000000000000", + "alias": "Cute Apple", + "version": "2.3", + "deviceModel": "Dell", + "deviceType": "desktop", + "token": "123" + }, + "peers": [] +}"# + ); + + let decoded: WsServerMessage = serde_json::from_str(&encoded).unwrap(); + + assert_eq!(message, decoded); + } + + #[test] + fn ws_server_offer_message_encoding() { + let message = WsServerMessage::Offer(WsServerSdpMessage { + peer: ClientInfo { + id: Uuid::nil(), + alias: "Cute Apple".to_string(), + version: "2.3".to_string(), + device_model: None, + device_type: Some(DeviceType::Desktop), + token: "123".to_string(), + }, + session_id: "456".to_string(), + sdp: "my-sdp".to_string(), + }); + + let encoded = serde_json::to_string_pretty(&message).unwrap(); + + assert_eq!( + encoded, + r#"{ + "type": "OFFER", + "peer": { + "id": "00000000-0000-0000-0000-000000000000", + "alias": "Cute Apple", + "version": "2.3", + "deviceType": "desktop", + "token": "123" + }, + "sessionId": "456", + "sdp": "my-sdp" +}"# + ); + + let decoded: WsServerMessage = serde_json::from_str(&encoded).unwrap(); + + assert_eq!(message, decoded); + } + + #[test] + fn ws_client_update_message_encoding() { + let message = WsClientMessage::Update { + info: ClientInfoWithoutId { + alias: "Cute Apple".to_string(), + version: "2.3".to_string(), + device_model: Some("Dell".to_string()), + device_type: Some(DeviceType::Desktop), + token: "123".to_string(), + }, + }; + + let encoded = serde_json::to_string_pretty(&message).unwrap(); + + assert_eq!( + encoded, + r#"{ + "type": "UPDATE", + "info": { + "alias": "Cute Apple", + "version": "2.3", + "deviceModel": "Dell", + "deviceType": "desktop", + "token": "123" + } +}"# + ); + + let decoded: WsClientMessage = serde_json::from_str(&encoded).unwrap(); + + assert_eq!(message, decoded); + } +} diff --git a/core/src/webrtc/webrtc.rs b/core/src/webrtc/webrtc.rs new file mode 100644 index 00000000..3bdd99db --- /dev/null +++ b/core/src/webrtc/webrtc.rs @@ -0,0 +1,1401 @@ +use crate::crypto::nonce::{generate_nonce, validate_nonce}; +use crate::crypto::token; +use crate::crypto::token::{generate_token_nonce, SigningTokenKey}; +use crate::model::transfer::FileDto; +use crate::util::base64; +use crate::webrtc::signaling::{ManagedSignalingConnection, WsServerSdpMessage}; +use anyhow::Result; +use bytes::{Bytes, BytesMut}; +use flate2::read::ZlibDecoder; +use flate2::write::ZlibEncoder; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::future::Future; +use std::io::{Read, Write}; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{mpsc, oneshot}; +use uuid::Uuid; +use webrtc::api::APIBuilder; +use webrtc::data_channel::data_channel_init::RTCDataChannelInit; +use webrtc::data_channel::data_channel_message::DataChannelMessage; +use webrtc::data_channel::RTCDataChannel; +use webrtc::ice_transport::ice_server::RTCIceServer; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc::peer_connection::RTCPeerConnection; + +/// Nonce message exchanged by both peers +/// starting with the sending peer. +#[derive(Debug, Deserialize, Serialize)] +struct RTCNonceMessage { + /// Nonce to be used to hash in combination with the public key. + /// Encoded in base64 (url-safe without padding). + nonce: String, +} + +/// Sending peer sends the token. +#[derive(Debug, Deserialize, Serialize)] +struct RTCTokenRequest { + /// Token using the received nonce and the public key. + /// HASH_METHOD.HASH.SIGN_METHOD.SIGN + token: String, +} + +/// Receiving peer sends the token as well but +/// also the status for the next step. +/// After this step, the sending peer may close the connection due to `InvalidSignature`. +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +enum RTCTokenResponse { + Ok { token: String }, + PinRequired { token: String }, + InvalidSignature, +} + +/// This struct is used by both sender and receiver. +/// (1) The sender tries to resolve PIN first, waiting for `RTCPinReceivingResponse`. +/// (2) The receiver tries to resolve PIN, waiting for `RTCPinSendingResponse`. +/// +/// This step may be skipped by the sending peer if `RTCInitResponse` is `Ok`. +/// In this case, the sending peer sends the `RTCPinSendingResponse` right away. +#[derive(Debug, Deserialize, Serialize)] +struct RTCPinMessage { + pin: String, +} + +/// Response to the PIN message by the receiving peer. +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +enum RTCPinReceivingResponse { + Ok, + PinRequired, + TooManyAttempts, +} + +/// Response to the PIN message by the sending peer. +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +enum RTCPinSendingResponse { + Ok { files: Vec }, + PinRequired, + TooManyAttempts, +} + +/// Sent by receiving peer after receiving `RTCPinSendingResponse::Ok`. +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +enum RTCFileListResponse { + Ok { + files: HashMap, + }, + Pair { + #[serde(rename = "publicKey")] + public_key: String, + }, + Declined, + InvalidSignature, +} + +/// Pair response by the sending peer. +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +enum RTCPairResponse { + Ok { + #[serde(rename = "publicKey")] + public_key: String, + }, + PairDeclined, + InvalidSignature, +} + +#[derive(Debug)] +pub struct RTCFile { + pub file_id: String, + pub binary_rx: mpsc::Receiver, +} + +#[derive(Debug)] +struct RTCFileState { + file_id: String, + size: u64, + binary_tx: mpsc::Sender, +} + +#[derive(Debug, Deserialize, Serialize)] +struct RTCSendFileHeaderRequest { + pub id: String, + pub token: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RTCSendFileResponse { + pub id: String, + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum RTCStatus { + /// Received remote SDP offer/answer. Ready to start P2P connection. + SdpExchanged, + + /// Opened data channel. Ready to further communication. + Connected, + + /// PIN is required to proceed. + PinRequired, + + /// Too many attempts. Connection is closed. + TooManyAttempts, + + /// Declined by the receiving peer. + Declined, + + /// Files are being sent. + Sending, + + /// Data channel closed. Connection is closed. + Finished, + + /// Error occurred. Connection is closed. + Error(String), +} + +#[derive(Debug, Eq, PartialEq)] +pub struct RTCFileError { + pub file_id: String, + pub error: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct PinConfig { + pub pin: String, + pub max_tries: u8, +} + +const CHANNEL_LABEL: &str = "data"; + +pub async fn send_offer( + signaling: &ManagedSignalingConnection, + stun_servers: Vec, + target_id: Uuid, + signing_key: SigningTokenKey, + expecting_public_key: Option>, + pin: Option, + files: Vec, + status_tx: mpsc::Sender, + selected_files_tx: oneshot::Sender>, + error_tx: mpsc::Sender, + pin_tx: mpsc::Sender>, + pair_tx: oneshot::Sender>, + mut sending_rx: mpsc::Receiver, +) -> Result<()> { + let (peer_connection, mut done_rx) = create_peer_connection(stun_servers).await?; + + let data_channel = peer_connection + .create_data_channel( + CHANNEL_LABEL, + Some(RTCDataChannelInit { + ordered: Some(true), + max_packet_life_time: None, + max_retransmits: None, + protocol: None, + negotiated: None, + }), + ) + .await?; + + let (connected_tx, mut connected_rx) = mpsc::channel::<()>(1); + data_channel.on_open(Box::new(move || { + Box::pin(async move { + let _ = connected_tx.send(()).await; + }) + })); + + let mut receive_rx = to_receive_stream(&data_channel, 16); + + let send_task = { + let data_channel = Arc::clone(&data_channel); + let status_tx = status_tx.clone(); + tokio::spawn(async move { + connected_rx + .recv() + .await + .ok_or_else(|| anyhow::anyhow!("Data channel not opened"))?; + + wait_buffer_empty(&data_channel).await; + + tracing::debug!("Data channel opened. Exchanging nonce..."); + + // Nonce exchange + let nonce = { + let mut local_nonce = generate_nonce(); + data_channel + .send_text(&serde_json::to_string(&RTCNonceMessage { + nonce: base64::encode(&local_nonce), + })?) + .await + .map_err(|e| anyhow::anyhow!("Failed to send nonce: {e}"))?; + + let mut remote_nonce = receive_nonce(&mut receive_rx).await?; + + // Final nonce: sender_nonce || receiver_nonce + local_nonce.append(&mut remote_nonce); + local_nonce + }; + + tracing::debug!("Nonce exchanged. Exchanging token..."); + + // Token exchange + let local_token = generate_token_nonce(&signing_key, &nonce) + .map_err(|e| anyhow::anyhow!("Failed to generate token: {e}"))?; + + data_channel + .send_text(&serde_json::to_string(&RTCTokenRequest { + token: local_token, + })?) + .await + .map_err(|e| anyhow::anyhow!("Failed to send token: {e}"))?; + + let token_response: RTCTokenResponse = match receive_rx.recv().await { + Some(msg) => { + if !msg.is_string { + return Err(anyhow::anyhow!("Expected string message")); + } + + serde_json::from_slice(&msg.data)? + } + None => { + return Err(anyhow::anyhow!("Failed to receive token response")); + } + }; + + let remote_token = match &token_response { + RTCTokenResponse::Ok { token } | RTCTokenResponse::PinRequired { token } => { + if let Some(expecting_public_key) = expecting_public_key { + if !token::verify_token_nonce(&*expecting_public_key, &token, &nonce) { + return Err(anyhow::anyhow!("Invalid token signature or nonce")); + } + } + token.to_owned() + } + RTCTokenResponse::InvalidSignature => { + return Err(anyhow::anyhow!( + "Invalid token signature from receiving peer" + )); + } + }; + + tracing::debug!("Tokens exchanged."); + + if let RTCTokenResponse::PinRequired { .. } = token_response { + tracing::debug!("PIN challenge by receiver... (I need to send correct PIN)"); + handle_pin::<()>( + &data_channel, + &status_tx, + &pin_tx, + &mut receive_rx, + false, + |data| { + let Ok(pin_res) = serde_json::from_slice::(&data) + else { + return ChallengePinResult::ParseError(anyhow::anyhow!( + "Failed to parse pin response" + )); + }; + + match pin_res { + RTCPinReceivingResponse::Ok => ChallengePinResult::Ok(()), + RTCPinReceivingResponse::PinRequired => ChallengePinResult::PinRequired, + RTCPinReceivingResponse::TooManyAttempts => { + ChallengePinResult::TooManyAttempts + } + } + }, + ) + .await?; + + tracing::debug!("PIN challenge done."); + } + + if let Some(pin) = &pin { + tracing::debug!("PIN challenge by sender... (I need to verify the correct PIN)"); + verify_pin( + pin, + &data_channel, + &status_tx, + &mut receive_rx, + true, + |data_channel, result| { + let data_channel = Arc::clone(&data_channel); + async move { + send_string_in_chunks( + &data_channel, + serde_json::to_string(&match result { + VerifyPinResult::PinRequired => { + RTCPinSendingResponse::PinRequired + } + VerifyPinResult::TooManyAttempts => { + RTCPinSendingResponse::TooManyAttempts + } + })?, + |data_channel, chunk| async move { + data_channel.send(&chunk).await?; + Ok(data_channel) + }, + ) + .await?; + + send_delimiter(&data_channel).await?; + + Ok(()) + } + }, + ) + .await?; + } + + status_tx.send(RTCStatus::Connected).await?; + + { + // send file list message + let file_list_req = serde_json::to_string(&RTCPinSendingResponse::Ok { files })?; + + let result = send_string_in_chunks( + Arc::clone(&data_channel), + file_list_req, + |data_channel, chunk| async move { + data_channel.send(&chunk).await?; + Ok(data_channel) + }, + ) + .await; + + if let Err(e) = result { + let _ = status_tx.try_send(RTCStatus::Error(format!( + "Failed to send file list message: {e}" + ))); + return Err(e.into()); + } + + if let Err(e) = send_delimiter(&data_channel).await { + let _ = status_tx.try_send(RTCStatus::Error(format!( + "Failed to send file list message: {e}" + ))); + return Err(e.into()); + } + } + + tracing::debug!("Sent file list message. Waiting for file tokens..."); + + // Receive file tokens + let file_list_res = { + let bytes = receive_string_from_chunks(&mut receive_rx).await; + let parsed: RTCFileListResponse = serde_json::from_slice(&*bytes).map_err(|e| { + anyhow::anyhow!("Failed to deserialize file list response: {e}") + })?; + parsed + }; + + let file_map = match file_list_res { + RTCFileListResponse::Ok { files } => files, + RTCFileListResponse::Pair { public_key } => { + tracing::debug!("Pair request received. Sending pair response..."); + let signature_identifier = + token::extract_signature_identifier(&remote_token) + .ok_or(anyhow::anyhow!("Failed to extract signature identifier"))?; + let parsed_key = token::parse_public_key(&public_key, signature_identifier)?; + if !token::verify_token_nonce(&*parsed_key, &remote_token, &nonce) { + wait_buffer_empty(&data_channel).await; + data_channel + .send_text(&serde_json::to_string(&RTCPairResponse::InvalidSignature)?) + .await?; + return Err(anyhow::anyhow!("Invalid token signature or nonce")); + } + + let (pair_res_tx, pair_res_rx) = oneshot::channel::(); + if pair_tx.send(pair_res_tx).is_err() { + return Err(anyhow::anyhow!("Failed to send pair response")); + } + + let pair_user_response = pair_res_rx.await?; + if pair_user_response { + data_channel + .send_text(&serde_json::to_string(&RTCPairResponse::Ok { public_key })?) + .await?; + } else { + data_channel + .send_text(&serde_json::to_string(&RTCPairResponse::PairDeclined)?) + .await?; + } + + let file_list_res = { + let bytes = receive_string_from_chunks(&mut receive_rx).await; + let parsed: RTCFileListResponse = + serde_json::from_slice(&*bytes).map_err(|e| { + anyhow::anyhow!("Failed to deserialize file list response: {e}") + })?; + parsed + }; + + match file_list_res { + RTCFileListResponse::Ok { files } => files, + RTCFileListResponse::Declined => { + tracing::debug!("Declined by the receiving peer."); + let _ = status_tx.send(RTCStatus::Declined).await; + return Ok(()); + } + RTCFileListResponse::InvalidSignature => { + let _ = status_tx + .send(RTCStatus::Error( + "Invalid signature (not expected)".to_owned(), + )) + .await; + return Err(anyhow::anyhow!("Invalid signature (not expected)")); + } + RTCFileListResponse::Pair { .. } => { + let _ = status_tx + .send(RTCStatus::Error("Unexpected pair response".to_owned())) + .await; + return Err(anyhow::anyhow!("Unexpected pair response")); + } + } + } + RTCFileListResponse::Declined => { + tracing::debug!("Declined by the receiving peer."); + let _ = status_tx.send(RTCStatus::Declined).await; + return Ok(()); + } + RTCFileListResponse::InvalidSignature => { + // This is not expected because the public key is not sent yet. + // Likely a bug in the implementation on the receiving side. + let _ = status_tx + .send(RTCStatus::Error( + "Invalid signature (not expected)".to_owned(), + )) + .await; + return Err(anyhow::anyhow!("Invalid signature (not expected)")); + } + }; + + // Publish selected files + if selected_files_tx + .send(file_map.keys().cloned().collect()) + .is_err() + { + let error = "Could not publish selection"; + let _ = status_tx.send(RTCStatus::Error(error.to_owned())); + return Err(anyhow::anyhow!(error)); + } + + tracing::debug!("Received file tokens. Sending files..."); + + while let Some(message) = sending_rx.recv().await { + let file_token = match file_map.get(&message.file_id) { + Some(file_token) => file_token, + None => { + let _ = error_tx + .send(RTCFileError { + file_id: message.file_id, + error: "Failed to get file token".to_string(), + }) + .await; + + continue; + } + }; + + let header = RTCSendFileHeaderRequest { + id: message.file_id.clone(), + token: file_token.clone(), + }; + + if let Err(e) = data_channel + .send_text(serde_json::to_string(&header)?) + .await + { + let _ = error_tx + .send(RTCFileError { + file_id: message.file_id, + error: e.to_string(), + }) + .await; + continue; + } + + let result = process_in_chunks( + Arc::clone(&data_channel), + message.binary_rx, + |data_channel, chunk| async move { + data_channel.send(&chunk).await?; + Ok(data_channel) + }, + ) + .await; + + if let Err(e) = result { + let _ = error_tx + .send(RTCFileError { + file_id: message.file_id, + error: e.to_string(), + }) + .await; + continue; + } + } + + wait_buffer_empty(&data_channel).await; + + send_delimiter(&data_channel).await?; + + receive_rx.recv().await; + + Ok(()) + }) + }; + + let offer = peer_connection.create_offer(None).await?; + let mut gather_complete = peer_connection.gathering_complete_promise().await; + peer_connection.set_local_description(offer).await?; + let _ = gather_complete.recv().await; + + let session_id = Uuid::new_v4().to_string(); + let local_description = peer_connection + .local_description() + .await + .ok_or_else(|| anyhow::anyhow!("Could not generate local_description"))?; + + signaling + .send_offer( + session_id.clone(), + target_id, + encode_sdp(&local_description.sdp), + ) + .await?; + + let (tx_answer, rx_answer) = oneshot::channel(); + + signaling + .on_answer(session_id, |message| { + tx_answer.send(message.sdp).unwrap(); + }) + .await; + + let remote_desc = rx_answer.await?; + + if let Err(e) = status_tx.send(RTCStatus::SdpExchanged).await { + peer_connection.close().await?; + return Err(e.into()); + } + + let answer = RTCSessionDescription::answer(decode_sdp(&remote_desc)?)?; + + peer_connection.set_remote_description(answer).await?; + + tokio::select! { + result = send_task => { + match result { + Ok(Ok(_)) => tracing::debug!("Sending done."), + Ok(Err(result)) => { + return Err(result); + }, + Err(e) => { + tracing::error!("Sending error: {e}"); + return Err(anyhow::anyhow!("Sending error: {e}")); + } + } + } + _ = done_rx.recv() => {} + } + + let _ = status_tx.send(RTCStatus::Finished).await; + if let Err(e) = data_channel.close().await { + tracing::error!("Failed to close data channel: {e}"); + } + + peer_connection.close().await?; + + Ok(()) +} + +pub async fn accept_offer( + signaling: &ManagedSignalingConnection, + stun_servers: Vec, + offer: &WsServerSdpMessage, + signing_key: SigningTokenKey, + expecting_public_key: Option>, + pin: Option, + status_tx: mpsc::Sender, + files_tx: oneshot::Sender>, + selected_files_rx: oneshot::Receiver>>, + error_tx: mpsc::Sender, + pin_tx: mpsc::Sender>, + receiving_tx: mpsc::Sender, + mut user_error_tx: mpsc::Receiver, +) -> Result<()> { + let (peer_connection, mut done_rx) = create_peer_connection(stun_servers).await?; + + let (data_channel_tx, mut data_channel_rx) = mpsc::channel::>(1); + + peer_connection.on_data_channel(Box::new(move |d: Arc| { + if d.label() != CHANNEL_LABEL { + return Box::pin(async {}); + } + + let data_channel_tx = data_channel_tx.clone(); + Box::pin(async move { + let d_clone = Arc::clone(&d); + d.on_open(Box::new(move || { + let _ = data_channel_tx.try_send(d_clone); + + Box::pin(async {}) + })); + }) + })); + + let receive_task = { + let status_tx = status_tx.clone(); + tokio::spawn(async move { + let Some(data_channel) = data_channel_rx.recv().await else { + return Err::<(), anyhow::Error>(anyhow::anyhow!("Data channel not found")); + }; + + // We convert on_message to a stream of messages + // to improve readability using a sequential implementation + let mut receive_rx = to_receive_stream(&data_channel, 16); + + tracing::debug!("Data channel opened. Exchanging nonce..."); + + // Nonce exchange + let nonce = { + let mut remote_nonce = receive_nonce(&mut receive_rx).await?; + + let mut local_nonce = generate_nonce(); + data_channel + .send_text(&serde_json::to_string(&RTCNonceMessage { + nonce: base64::encode(&local_nonce), + })?) + .await + .map_err(|e| anyhow::anyhow!("Failed to send nonce: {e}"))?; + + // Final nonce: sender_nonce || receiver_nonce + remote_nonce.append(&mut local_nonce); + remote_nonce + }; + + tracing::debug!("Nonce exchanged. Exchanging token..."); + + // Token exchange + let remote_token = match receive_rx.recv().await { + Some(msg) => { + if !msg.is_string { + return Err(anyhow::anyhow!("Expected string message")); + } + + serde_json::from_slice::(&msg.data)?.token + } + None => { + return Err(anyhow::anyhow!("Failed to receive token")); + } + }; + + if let Some(expecting_public_key) = expecting_public_key { + // Optionally, verify the token signature + if !token::verify_token_nonce(&*expecting_public_key, &remote_token, &nonce) { + data_channel + .send_text(&serde_json::to_string(&RTCTokenResponse::InvalidSignature)?) + .await?; + wait_buffer_empty(&data_channel).await; + return Err(anyhow::anyhow!("Invalid token signature or nonce")); + } + tracing::debug!("Token signature verified."); + } + + { + let local_token = generate_token_nonce(&signing_key, &nonce) + .map_err(|e| anyhow::anyhow!("Failed to generate token: {e}"))?; + + data_channel + .send_text(&serde_json::to_string(&match pin { + Some(_) => RTCTokenResponse::PinRequired { token: local_token }, + None => RTCTokenResponse::Ok { token: local_token }, + })?) + .await + .map_err(|e| anyhow::anyhow!("Failed to send token: {e}"))?; + } + + if let Some(pin) = &pin { + tracing::debug!("PIN required. Challenging..."); + verify_pin( + pin, + &data_channel, + &status_tx, + &mut receive_rx, + false, + |data_channel, result| { + let data_channel = Arc::clone(&data_channel); + async move { + data_channel + .send_text(&serde_json::to_string(&match result { + VerifyPinResult::PinRequired => { + RTCPinReceivingResponse::PinRequired + } + VerifyPinResult::TooManyAttempts => { + RTCPinReceivingResponse::TooManyAttempts + } + })?) + .await?; + Ok(()) + } + }, + ) + .await?; + + data_channel + .send_text( + serde_json::to_string(&RTCPinReceivingResponse::Ok) + .expect("Failed to serialize"), + ) + .await?; + + tracing::debug!("PIN challenge done."); + } + + tracing::debug!("Waiting for sender PIN status..."); + + let pin_response = { + let bytes = receive_string_from_chunks(&mut receive_rx).await; + let parsed: RTCPinSendingResponse = + serde_json::from_slice(&*bytes).map_err(|e| { + anyhow::anyhow!("Failed to deserialize file list response: {e}") + })?; + parsed + }; + + let file_list = match pin_response { + RTCPinSendingResponse::Ok { files } => files, + RTCPinSendingResponse::PinRequired => { + tracing::debug!("PIN challenge by sender... (I need to send correct PIN)"); + let result = handle_pin::>( + &data_channel, + &status_tx, + &pin_tx, + &mut receive_rx, + true, + |data| { + let Ok(pin_res) = + serde_json::from_slice::(&data) + else { + return ChallengePinResult::ParseError(anyhow::anyhow!( + "Failed to parse pin response" + )); + }; + match pin_res { + RTCPinSendingResponse::Ok { files } => { + ChallengePinResult::Ok(files) + } + RTCPinSendingResponse::PinRequired => { + ChallengePinResult::PinRequired + } + RTCPinSendingResponse::TooManyAttempts => { + ChallengePinResult::TooManyAttempts + } + } + }, + ) + .await?; + + result + } + RTCPinSendingResponse::TooManyAttempts => { + let _ = status_tx + .send(RTCStatus::Error( + "Unexpected TooManyAttempts response".to_owned(), + )) + .await; + return Err(anyhow::anyhow!("Unexpected TooManyAttempts response")); + } + }; + + status_tx.send(RTCStatus::Connected).await?; + + tracing::debug!("Received file list."); + + files_tx + .send(file_list.clone()) + .map_err(|_| anyhow::anyhow!("Failed to send file list"))?; + + // Init: Receive user selection + let Ok(selected_files) = selected_files_rx.await else { + return Ok(()); + }; + + let Some(selected_files) = selected_files else { + // Declined by the user + send_string_in_chunks( + Arc::clone(&data_channel), + serde_json::to_string(&RTCFileListResponse::Declined)?, + |data_channel, chunk| async move { + data_channel.send(&chunk).await?; + Ok(data_channel) + }, + ) + .await?; + + send_delimiter(&data_channel).await?; + + return Ok(()); + }; + + let file_tokens = selected_files + .into_iter() + .map(|file_id| { + let token = Uuid::new_v4().to_string(); + (file_id, token) + }) + .collect::>(); + + send_string_in_chunks( + Arc::clone(&data_channel), + serde_json::to_string(&RTCFileListResponse::Ok { + files: file_tokens.clone(), + })?, + |data_channel, chunk| async move { + data_channel.send(&chunk).await?; + Ok(data_channel) + }, + ) + .await?; + + send_delimiter(&data_channel).await?; + + tracing::debug!("Sent file tokens."); + + // Receive files + let mut file_state: Option = None; + while let Some(msg) = receive_rx.recv().await { + if msg.is_string { + // End of last file + let last_file_id = file_state.as_ref().map(|s| s.file_id.clone()); + file_state = None; + + if let Some(last_file_id) = last_file_id { + let error = match user_error_tx.recv().await { + Some(result) => { + if result.success { + None + } else { + Some(result.error.map_or("Unknown error".to_string(), |e| e)) + } + } + None => Some("Failed to receive file result".to_string()), + }; + + data_channel + .send_text(serde_json::to_string(&RTCSendFileResponse { + id: last_file_id, + success: error.is_none(), + error, + })?) + .await?; + + if is_delimiter(&msg) { + // End of all files + + // Wait for the last status to be sent + let _ = tokio::time::timeout(Duration::from_secs(5), async { + wait_buffer_empty(&data_channel).await; + }) + .await; + + break; + } + } + + let header: RTCSendFileHeaderRequest = serde_json::from_slice(&msg.data)?; + match file_tokens.get(&header.id) { + Some(entry) => { + if header.token != *entry { + let _ = error_tx + .send(RTCFileError { + file_id: header.id, + error: "Invalid token".to_string(), + }) + .await; + continue; + } + } + None => { + let _ = error_tx + .send(RTCFileError { + file_id: header.id, + error: "File not found".to_string(), + }) + .await; + continue; + } + } + + let (tx, rx) = mpsc::channel::(4); + + let size = { + let entry = file_list.iter().find(|f| f.id == header.id); + match entry { + Some(file) => file.size, + None => { + let _ = error_tx + .send(RTCFileError { + file_id: header.id, + error: "Expected size to be available".to_string(), + }) + .await; + continue; + } + } + }; + + file_state = Some(RTCFileState { + file_id: header.id.clone(), + size, + binary_tx: tx, + }); + + let _ = receiving_tx + .send(RTCFile { + file_id: header.id.clone(), + binary_rx: rx, + }) + .await; + } else { + // publish binary data + match &mut file_state { + Some(state) => { + state.binary_tx.send(msg.data).await?; + } + None => { + let _ = error_tx + .send(RTCFileError { + file_id: "unknown".to_string(), + error: "Received binary data without a header".to_string(), + }) + .await; + } + } + } + } + + Ok(()) + }) + }; + + let remote_desc_sdp = decode_sdp(&offer.sdp)?; + let remote_desc = RTCSessionDescription::offer(remote_desc_sdp)?; + peer_connection.set_remote_description(remote_desc).await?; + + let answer = peer_connection.create_answer(None).await?; + + let mut gather_complete = peer_connection.gathering_complete_promise().await; + peer_connection.set_local_description(answer).await?; + let _ = gather_complete.recv().await; + + let local_description = peer_connection + .local_description() + .await + .ok_or_else(|| anyhow::anyhow!("generate local_description failed!"))?; + + signaling + .send_answer( + offer.session_id.clone(), + offer.peer.id, + encode_sdp(&local_description.sdp), + ) + .await?; + + if let Err(e) = status_tx.send(RTCStatus::SdpExchanged).await { + peer_connection.close().await?; + return Err(e.into()); + } + + tokio::select! { + result = receive_task => { + match result { + Ok(Ok(_)) => tracing::debug!("Receiving done."), + Ok(Err(result)) => { + return Err(result); + }, + Err(e) => { + tracing::error!("Receiving error: {e}"); + return Err(anyhow::anyhow!("Receiving error: {e}")); + } + } + } + _ = done_rx.recv() => { + tracing::debug!("Peer connection remotely closed."); + } + } + + let _ = status_tx.send(RTCStatus::Finished).await; + peer_connection.close().await?; + + Ok(()) +} + +async fn create_peer_connection( + stun: Vec, +) -> Result<(Arc, mpsc::Receiver<()>)> { + let api = APIBuilder::new().build(); + + let config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: stun, + ..Default::default() + }], + ..Default::default() + }; + + let peer_connection = api.new_peer_connection(config).await?; + + let (done_tx, done_rx) = mpsc::channel::<()>(1); + + peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { + if s == RTCPeerConnectionState::Disconnected { + let _ = done_tx.try_send(()); + } + Box::pin(async {}) + })); + + Ok((Arc::new(peer_connection), done_rx)) +} + +async fn receive_nonce(receive_rx: &mut mpsc::Receiver) -> Result> { + let remote_nonce = match receive_rx.recv().await { + Some(msg) => { + if !msg.is_string { + return Err(anyhow::anyhow!("Expected string message")); + } + + let nonce_msg: RTCNonceMessage = serde_json::from_slice(&msg.data)?; + let remote_nonce = base64::decode(&nonce_msg.nonce) + .map_err(|e| anyhow::anyhow!("Failed to decode remote nonce: {e}"))?; + + if !validate_nonce(&remote_nonce) { + return Err(anyhow::anyhow!("Invalid remote nonce")); + } + + remote_nonce + } + None => { + return Err(anyhow::anyhow!("Failed to receive nonce")); + } + }; + + Ok(remote_nonce) +} + +enum ChallengePinResult { + Ok(T), + PinRequired, + TooManyAttempts, + ParseError(anyhow::Error), +} + +async fn handle_pin( + data_channel: &Arc, + status_tx: &mpsc::Sender, + pin_tx: &mpsc::Sender>, + receive_rx: &mut mpsc::Receiver, + receive_in_chunks: bool, + parse_response: fn(Bytes) -> ChallengePinResult, +) -> Result { + loop { + status_tx.send(RTCStatus::PinRequired).await?; + let (new_pin_tx, new_pin_rx) = oneshot::channel::(); + + if pin_tx.send(new_pin_tx).await.is_err() { + return Err(anyhow::anyhow!("Failed to send PIN request")); + } + + let Ok(pin) = new_pin_rx.await else { + return Err(anyhow::anyhow!("PIN required but not provided")); + }; + + data_channel + .send_text(&serde_json::to_string(&RTCPinMessage { pin })?) + .await?; + + let bytes = match receive_in_chunks { + true => receive_string_from_chunks(receive_rx).await, + false => { + let Some(init_status) = receive_rx.recv().await else { + return Err(anyhow::anyhow!("Failed to receive initial message")); + }; + + if !init_status.is_string { + return Err(anyhow::anyhow!("Expected string message")); + } + + init_status.data + } + }; + + let pin_res = parse_response(bytes); + match pin_res { + ChallengePinResult::Ok(result) => { + return Ok(result); + } + ChallengePinResult::PinRequired => { + continue; + } + ChallengePinResult::TooManyAttempts => { + status_tx.send(RTCStatus::TooManyAttempts).await?; + return Err(anyhow::anyhow!("Too many requests")); + } + ChallengePinResult::ParseError(e) => { + return Err(e); + } + } + } +} + +#[derive(Debug)] +enum VerifyPinResult { + PinRequired, + TooManyAttempts, +} + +async fn verify_pin( + pin_config: &PinConfig, + data_channel: &Arc, + status_tx: &mpsc::Sender, + receive_rx: &mut mpsc::Receiver, + mut send_initial_notice: bool, + send_result: F, +) -> Result<()> +where + F: Fn(&Arc, VerifyPinResult) -> Fut, + Fut: Future>, +{ + status_tx.send(RTCStatus::PinRequired).await?; + + let mut remote_pin = "".to_string(); + let mut pin_try = 0u8; + + loop { + if remote_pin == pin_config.pin { + return Ok(()); + } + + if pin_try >= pin_config.max_tries { + let _ = tokio::time::timeout(Duration::from_secs(5), async { + let _ = send_result(&data_channel, VerifyPinResult::TooManyAttempts).await; + + wait_buffer_empty(&data_channel).await; + }) + .await; + + status_tx.send(RTCStatus::TooManyAttempts).await?; + + return Err(anyhow::anyhow!("Too many requests")); + } + + if send_initial_notice || pin_try > 0 { + send_initial_notice = false; + send_result(&data_channel, VerifyPinResult::PinRequired).await?; + } + + remote_pin = match receive_rx.recv().await { + Some(msg) => { + let pin_req: RTCPinMessage = serde_json::from_slice(&msg.data)?; + pin_req.pin + } + None => { + return Err(anyhow::anyhow!("Failed to receive pin")); + } + }; + + pin_try += 1; + } +} + +fn encode_sdp(s: &str) -> String { + let mut e = ZlibEncoder::new(Vec::new(), flate2::Compression::best()); + e.write_all(s.as_bytes()) + .map_err(|e| anyhow::anyhow!("Failed to compress SDP: {e}")) + .unwrap(); + let compressed = e + .finish() + .map_err(|e| anyhow::anyhow!("Failed to finish compression of SDP: {e}")) + .unwrap(); + base64::encode(&compressed) +} + +fn decode_sdp(s: &str) -> Result { + let decoded_data = + base64::decode(s).map_err(|e| anyhow::anyhow!("Base64 decode of SDP failed: {e}"))?; + let mut d = ZlibDecoder::new(&*decoded_data); + let mut result = String::new(); + d.read_to_string(&mut result) + .map_err(|e| anyhow::anyhow!("Failed to decompress SDP: {e}"))?; + Ok(result) +} + +async fn send_delimiter(data_channel: &Arc) -> Result<()> { + // Somehow, empty messages are not received by the other peer, so we send a non-empty message + data_channel.send_text("0".to_string()).await?; + Ok(()) +} + +fn is_delimiter(msg: &DataChannelMessage) -> bool { + msg.is_string && msg.data.len() <= 1 +} + +async fn wait_buffer_empty(data_channel: &Arc) { + while data_channel.buffered_amount().await != 0 { + tokio::time::sleep(Duration::from_millis(100)).await; + } +} + +const CHUNK_SIZE: usize = 16 * 1024; // 16 KiB + +/// Process incoming data in chunks of CHUNK_SIZE +/// The callback returns the same data_channel to avoid re-creating or lifetime issues. +/// Note: You need to send the delimiter (or any other string) after the last chunk. +pub async fn process_in_chunks( + mut data_channel: T, + mut rx: mpsc::Receiver, + mut callback: F, +) -> Result<()> +where + F: FnMut(T, Bytes) -> Fut, + Fut: Future>, +{ + let mut buffer = BytesMut::with_capacity(CHUNK_SIZE); + + while let Some(data) = rx.recv().await { + buffer.extend_from_slice(&data); + + while buffer.len() >= CHUNK_SIZE { + let chunk = buffer.split_to(CHUNK_SIZE).freeze(); + + // Process the chunk, reuse the data_channel + data_channel = callback(data_channel, chunk).await?; + } + } + + // After the channel is closed, if there's leftover data, handle it as needed: + if !buffer.is_empty() { + callback(data_channel, buffer.freeze()).await?; + } + + Ok(()) +} + +/// Convenience function for `process_in_chunks` that processes a string in chunks. +/// Note: You need to send the delimiter (or any other string) after the last chunk. +pub async fn send_string_in_chunks( + data_channel: T, + string: String, + callback: F, +) -> Result<()> +where + F: FnMut(T, Bytes) -> Fut, + Fut: Future>, +{ + let (tx, rx) = mpsc::channel(1); + + tokio::spawn(async move { + let _ = tx.send(Bytes::from(string)).await; + }); + + process_in_chunks(data_channel, rx, callback).await +} + +async fn receive_string_from_chunks(receive_rx: &mut mpsc::Receiver) -> Bytes { + let mut buffer = BytesMut::new(); + while let Some(msg) = receive_rx.recv().await { + if msg.is_string { + break; + } + + buffer.extend_from_slice(&msg.data); + } + + buffer.freeze() +} + +fn to_receive_stream( + data_channel: &Arc, + buffer: usize, +) -> mpsc::Receiver { + let (tx, rx) = mpsc::channel(buffer); + + data_channel.on_message(Box::new(move |msg: DataChannelMessage| { + let tx = tx.clone(); + Box::pin(async move { + let _ = tx.send(msg).await; + }) + })); + + rx +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_process_in_chunks() { + let (tx, rx) = mpsc::channel(16); + + tokio::spawn(async move { + let mut test_vec = vec![0; CHUNK_SIZE * 2 + 5]; + test_vec[CHUNK_SIZE..CHUNK_SIZE * 2] + .iter_mut() + .for_each(|x| *x = 1); + test_vec[CHUNK_SIZE * 2..].iter_mut().for_each(|x| *x = 2); + tx.send(Bytes::from(test_vec)).await.unwrap(); + }); + + let mut chunks = Vec::new(); + + let result = process_in_chunks(0, rx, |_, chunk| { + chunks.push(chunk); + async { Ok(0) } + }) + .await; + + assert!(result.is_ok()); + assert_eq!(chunks.len(), 3); + assert_eq!(chunks[0].len(), CHUNK_SIZE); + assert_eq!(chunks[1].len(), CHUNK_SIZE); + assert_eq!(chunks[2].len(), 5); + + assert_eq!(chunks[0].iter().all(|x| *x == 0), true); + assert_eq!(chunks[1].iter().all(|x| *x == 1), true); + assert_eq!(chunks[2].iter().all(|x| *x == 2), true); + } + + #[test] + fn rtc_file_list_response_encoding() { + let response = RTCFileListResponse::Pair { + public_key: "123".to_string(), + }; + + let encoded = serde_json::to_string_pretty(&response).unwrap(); + + assert_eq!( + encoded, + r#"{ + "status": "PAIR", + "publicKey": "123" +}"# + ); + + let decoded: RTCFileListResponse = serde_json::from_str(&encoded).unwrap(); + + assert_eq!(response, decoded); + } +} diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 00000000..8c6dc65b --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,1293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "croner" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" +dependencies = [ + "chrono", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "localsend" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "bytes", + "futures-util", + "rand", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", + "tungstenite", + "uuid", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "base64", + "futures-util", + "localsend", + "serde", + "serde_json", + "tokio", + "tokio-cron-scheduler", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-cron-scheduler" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5597b569b4712cf78aa0c9ae29742461b7bda1e49c2a5fdad1d79bf022f8f0" +dependencies = [ + "chrono", + "croner", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 00000000..93ef9a3d --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.95" +axum = { version = "0.8.1", features = ["ws"] } +base64 = "0.22.1" +futures-util = "0.3.31" +localsend = { path = "../core" } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.135" +tokio = { version = "1.43.0", features = ["full"] } +tokio-cron-scheduler = "0.13.0" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19" } +uuid = {version = "1.11.0", features = ["serde", "v4"]} diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 00000000..5c7bb427 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,24 @@ +FROM rust:1.83 AS builder + +WORKDIR /app + +# Compile Dependencies +COPY Cargo.* ./ +RUN mkdir src && echo 'fn main() { panic!("Dummy Image Called!") }' > ./src/main.rs +RUN cargo build --release + +# Add Source +COPY src ./src + +# Break cargo cache +RUN touch ./src/main.rs + +# Compile +RUN cargo build --release + +FROM debian:bookworm AS runtime + +COPY --from=builder /app/target/release/server /server + +EXPOSE 3000 +ENTRYPOINT ["/server"] diff --git a/server/README.md b/server/README.md new file mode 100644 index 00000000..07b8b65d --- /dev/null +++ b/server/README.md @@ -0,0 +1,3 @@ +# LocalSend Signaling Server + +A signaling server for LocalSend. Using Rust and WebSockets. diff --git a/server/src/config/error.rs b/server/src/config/error.rs new file mode 100644 index 00000000..32b5cb05 --- /dev/null +++ b/server/src/config/error.rs @@ -0,0 +1,59 @@ +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; + +// https://github.com/tokio-rs/axum/blob/main/examples/oauth/src/main.rs + +#[derive(Debug)] +pub struct AppError { + status: StatusCode, + message: Option, + error: Option, +} + +impl AppError { + pub fn status(status: StatusCode, message: Option) -> Self { + Self { + status, + message, + error: None, + } + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + if self.status >= StatusCode::INTERNAL_SERVER_ERROR { + tracing::error!( + "{:?}; {:?}", + match &self.message { + Some(message) => message, + None => "(no message)", + }, + match &self.error { + Some(error) => error.to_string(), + None => "(no error)".to_string(), + } + ); + } + + // return status code with message + if let Some(message) = self.message { + (self.status, message).into_response() + } else { + self.status.into_response() + } + } +} + +impl From for AppError +where + E: Into, +{ + fn from(err: E) -> Self { + Self { + status: StatusCode::INTERNAL_SERVER_ERROR, + message: None, + error: Some(err.into()), + } + } +} diff --git a/server/src/config/init.rs b/server/src/config/init.rs new file mode 100644 index 00000000..9d51d6e2 --- /dev/null +++ b/server/src/config/init.rs @@ -0,0 +1,20 @@ +use crate::config::scheduler; +use crate::config::state::AppState; +use tracing::Level; + +pub async fn init() -> AppState { + // Set up tracing / logging + tracing_subscriber::fmt().with_max_level(Level::INFO).init(); + + tracing::info!("Starting LocalSend WebRTC signaling server..."); + + // Initialize the AppState + let app_state = AppState::new(); + + // Setup scheduler + scheduler::configure_scheduling(app_state.request_count_map.clone()) + .await + .expect("Error configuring scheduler"); + + app_state +} diff --git a/server/src/config/mod.rs b/server/src/config/mod.rs new file mode 100644 index 00000000..a0c4d8d1 --- /dev/null +++ b/server/src/config/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod error; +pub(crate) mod init; +mod scheduler; +pub(crate) mod state; diff --git a/server/src/config/scheduler.rs b/server/src/config/scheduler.rs new file mode 100644 index 00000000..a446711b --- /dev/null +++ b/server/src/config/scheduler.rs @@ -0,0 +1,24 @@ +use tokio_cron_scheduler::{Job, JobScheduler}; + +use crate::config::state::IpRequestCountMap; + +pub async fn configure_scheduling( + ip_request_count_map: IpRequestCountMap, +) -> Result<(), Box> { + let scheduler = JobScheduler::new().await?; + + scheduler + .add(Job::new_async("0 0 * * * *", move |_uuid, _l| { + Box::pin({ + let value = ip_request_count_map.clone(); + async move { + value.lock().await.clear(); + } + }) + })?) + .await?; + + scheduler.start().await?; + + Ok(()) +} diff --git a/server/src/config/state.rs b/server/src/config/state.rs new file mode 100644 index 00000000..4de801b2 --- /dev/null +++ b/server/src/config/state.rs @@ -0,0 +1,33 @@ +use localsend::webrtc::signaling::{ClientInfoWithoutId, WsServerMessage}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +use uuid::Uuid; + +/// IP -> Peer ID -> PeerInfo + WebSocket message sender. +pub type TxMap = Arc>>>; + +pub struct ClientState { + pub client: ClientInfoWithoutId, + pub tx: mpsc::Sender, +} + +pub type IpRequestCountMap = Arc>>; + +#[derive(Clone)] +pub struct AppState { + /// Map of peer IDs to WebSocket message senders. + pub tx_map: TxMap, + + /// Map of IP addresses to the number of requests. + pub request_count_map: IpRequestCountMap, +} + +impl AppState { + pub fn new() -> Self { + Self { + tx_map: Arc::new(Mutex::new(HashMap::new())), + request_count_map: Arc::new(Mutex::new(HashMap::new())), + } + } +} diff --git a/server/src/controller/mod.rs b/server/src/controller/mod.rs new file mode 100644 index 00000000..60da5791 --- /dev/null +++ b/server/src/controller/mod.rs @@ -0,0 +1 @@ +pub(crate) mod ws_controller; diff --git a/server/src/controller/ws_controller.rs b/server/src/controller/ws_controller.rs new file mode 100644 index 00000000..2a817f86 --- /dev/null +++ b/server/src/controller/ws_controller.rs @@ -0,0 +1,369 @@ +use crate::config::error::AppError; +use crate::config::state::{AppState, ClientState, IpRequestCountMap, TxMap}; +use crate::util; +use crate::util::ip::get_ip_group; +use axum::body::Body; +use axum::extract::ws::{Message, WebSocket}; +use axum::extract::{ConnectInfo, Query, State, WebSocketUpgrade}; +use axum::http::{HeaderMap, StatusCode}; +use axum::response::Response; +use futures_util::stream::StreamExt; +use futures_util::SinkExt; +use localsend::webrtc::signaling::{ + ClientInfo, ClientInfoWithoutId, WsClientMessage, WsClientSdpMessage, WsServerMessage, + WsServerSdpMessage, +}; +use serde::Deserialize; +use std::collections::HashMap; +use std::net::{IpAddr, SocketAddr}; +use std::str::FromStr; +use std::sync::LazyLock; +use tokio::sync::mpsc; +use uuid::Uuid; + +static MAX_CONNECTIONS: LazyLock = LazyLock::new(|| { + std::env::var("MAX_CONNECTIONS_PER_IP") + .unwrap_or_else(|_| "10".to_string()) + .parse::() + .unwrap() +}); + +static MAX_REQUESTS: LazyLock = LazyLock::new(|| { + std::env::var("MAX_REQUESTS_PER_IP_PER_HOUR") + .unwrap_or_else(|_| "1000".to_string()) + .parse::() + .unwrap() +}); + +#[derive(Deserialize)] +pub struct WsQuery { + /// `PeerRegisterDto` encoded as base64. + pub d: String, +} + +pub async fn ws_handler( + State(state): State, + ws: WebSocketUpgrade, + Query(payload): Query, + ConnectInfo(addr): ConnectInfo, + headers: HeaderMap, +) -> Result, AppError> { + let peer_info = { + let base64_decoded: Vec = util::base64::decode(&payload.d) + .map_err(|_| AppError::status(StatusCode::BAD_REQUEST, None))?; + + let base64_decoded: String = String::from_utf8(base64_decoded) + .map_err(|_| AppError::status(StatusCode::BAD_REQUEST, None))?; + + let register_dto = serde_json::from_str::(&base64_decoded) + .map_err(|_| AppError::status(StatusCode::BAD_REQUEST, None))?; + + ClientInfo::from(register_dto.clone(), Uuid::new_v4()) + }; + + let ip = { + // Prefer the forwarded IP if available. + let raw_forwarded = headers + .get("x-forwarded-for") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.split(',').last()) // Get the last component + .map(|v| v.trim().to_string()) + .and_then(|v| IpAddr::from_str(&v).ok()); + + raw_forwarded.unwrap_or(addr.ip()) + }; + + Ok(ws.on_upgrade(move |socket| { + handle_socket( + state.tx_map, + state.request_count_map, + socket, + get_ip_group(ip), + peer_info, + ) + })) +} + +/// The websocket context (one per connected device) is handled here. +async fn handle_socket( + tx_map: TxMap, + request_count_map: IpRequestCountMap, + socket: WebSocket, + ip_group: String, + peer: ClientInfo, +) { + let peer_id = peer.id; + let (tx, mut rx) = mpsc::channel(4); + { + // Tx of other peers in the IP group. + let mut peers_tx: Vec> = Vec::new(); + + // Peers in the IP group including the current user. + let mut peers: Vec = Vec::new(); + + // If the limit of connections is reached. + // Used to break out of the lock as early as possible. + let mut limit_reached = false; + + 'lock: { + let mut tx_map = tx_map.lock().await; + + let tx_local_map = tx_map.entry(ip_group.clone()).or_insert_with(HashMap::new); + if tx_local_map.len() >= *MAX_CONNECTIONS { + limit_reached = true; + break 'lock; + } + + if protect_ddos_request_count(&request_count_map, &ip_group) + .await + .is_err() + { + limit_reached = true; + break 'lock; + } + + peers_tx = tx_local_map.values().map(|p| p.tx.clone()).collect(); + + peers = tx_local_map + .iter() + .map(|(k, v)| ClientInfo::from(v.client.clone(), *k)) + .collect(); + + tx_local_map.insert( + peer_id, + ClientState { + client: ClientInfoWithoutId::from(peer.clone()), + tx: tx.clone(), + }, + ); + + let debug_active_connections = tx_map.len(); + let debug_total_active_connections: usize = tx_map.values().map(|m| m.len()).sum(); + tracing::info!("Connect: {ip_group} / {peer_id} (active: {debug_active_connections}, total active: {debug_total_active_connections})"); + } + + if limit_reached { + let (mut sender, _) = socket.split(); + let _ = sender.send(Message::Text("Limit reached".into())).await; + let _ = sender.close().await; + return; + } + + for peer_tx in peers_tx { + let _ = peer_tx + .send(WsServerMessage::Join { peer: peer.clone() }) + .await; + } + + let _ = tx + .send(WsServerMessage::Hello { + client: peer.clone(), + peers, + }) + .await; + } + + let (mut sender, mut receiver) = socket.split(); + + let mut send_task = tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + let serialized = serde_json::to_string(&msg).unwrap(); + drop(msg); + + if sender.send(Message::Text(serialized.into())).await.is_err() { + break; + } + } + }); + + let tx_map_clone = tx_map.clone(); + let ip_group_clone = ip_group.clone(); + let mut recv_task = tokio::spawn(async move { + while let Some(Ok(msg)) = receiver.next().await { + if let Message::Text(text) = msg { + if let Ok(msg) = serde_json::from_str::(&text) { + if protect_ddos_request_count(&request_count_map, &ip_group_clone) + .await + .is_err() + { + let _ = tx + .send(WsServerMessage::Error { + code: StatusCode::TOO_MANY_REQUESTS.as_u16(), + }) + .await; + return; + } + + match msg { + WsClientMessage::Update { info } => { + send_update_to_other_peers_with_lock( + &tx_map_clone, + &ip_group_clone, + peer_id, + info, + ) + .await + } + WsClientMessage::Offer(sdp) => { + send_to_peer_with_lock( + &tx_map_clone, + &ip_group_clone, + peer.clone(), + WsClientSdpMessageWrapper::Offer(sdp), + ) + .await + } + WsClientMessage::Answer(sdp) => { + send_to_peer_with_lock( + &tx_map_clone, + &ip_group_clone, + peer.clone(), + WsClientSdpMessageWrapper::Answer(sdp), + ) + .await + } + } + } + } + } + }); + + // If any one of the tasks exit, abort the other. + tokio::select! { + rv_a = &mut send_task => { + match rv_a { + Ok(_) => (), + Err(e) => tracing::warn!("Error sending messages {e:?}") + } + recv_task.abort(); + }, + rv_b = &mut recv_task => { + match rv_b { + Ok(_) => (), + Err(e) => tracing::warn!("Error receiving messages {e:?}") + } + send_task.abort(); + } + } + + let mut remaining_tx: Vec> = Vec::new(); + + { + let mut tx_map = tx_map.lock().await; + let final_active_connections = match tx_map.get_mut(&ip_group) { + Some(tx_local_map) => { + tx_local_map.remove(&peer_id); + if tx_local_map.is_empty() { + tx_map.remove(&ip_group); + 0 + } else { + remaining_tx = tx_local_map.values().map(|p| p.tx.clone()).collect(); + tx_local_map.len() + } + } + None => 0, + }; + + tracing::info!("Disconnect: {peer_id} (active: {final_active_connections})"); + } + + for tx in remaining_tx { + let _ = tx.send(WsServerMessage::Left { peer_id }).await; + } +} + +async fn send_update_to_other_peers_with_lock( + tx_map: &TxMap, + ip_group: &str, + peer_id: Uuid, + info: ClientInfoWithoutId, +) { + // Tx of other peers in the IP group. + let mut peers_tx: Vec> = Vec::new(); + let response_info = ClientInfo::from(info.clone(), peer_id); + { + let mut tx_map = tx_map.lock().await; + if let Some(tx_local_map) = tx_map.get_mut(ip_group) { + if let Some(peer_state) = tx_local_map.get_mut(&peer_id) { + peer_state.client = info; + + peers_tx = tx_local_map + .iter() + .filter(|(k, _)| *k != &peer_id) + .map(|(_, v)| v.tx.clone()) + .collect(); + } + } + } + + for peer_tx in peers_tx { + let _ = peer_tx + .send(WsServerMessage::Update { + peer: response_info.clone(), + }) + .await; + } +} + +enum WsClientSdpMessageWrapper { + Offer(WsClientSdpMessage), + Answer(WsClientSdpMessage), +} + +async fn send_to_peer_with_lock( + tx_map: &TxMap, + ip_group: &str, + origin_peer: ClientInfo, + message: WsClientSdpMessageWrapper, +) { + let target = match &message { + WsClientSdpMessageWrapper::Offer(inner) => inner.target, + WsClientSdpMessageWrapper::Answer(inner) => inner.target, + }; + + let mut target_peer_tx: Option> = None; + { + let tx_map = tx_map.lock().await; + if let Some(tx_local_map) = tx_map.get(ip_group) { + if let Some(peer_state) = tx_local_map.get(&target) { + target_peer_tx = Some(peer_state.tx.clone()); + } + } + } + + if let Some(tx) = target_peer_tx { + let server_message = match message { + WsClientSdpMessageWrapper::Offer(inner) => { + let sdp_message = WsServerSdpMessage { + peer: origin_peer, + session_id: inner.session_id, + sdp: inner.sdp, + }; + WsServerMessage::Offer(sdp_message) + } + WsClientSdpMessageWrapper::Answer(inner) => { + let sdp_message = WsServerSdpMessage { + peer: origin_peer, + session_id: inner.session_id, + sdp: inner.sdp, + }; + WsServerMessage::Answer(sdp_message) + } + }; + + let _ = tx.send(server_message).await; + } +} + +async fn protect_ddos_request_count( + request_count_map: &IpRequestCountMap, + ip_group: &str, +) -> Result<(), AppError> { + let mut request_count_map = request_count_map.lock().await; + let count = request_count_map.entry(ip_group.to_string()).or_insert(0); + if *count >= *MAX_REQUESTS { + return Err(AppError::status(StatusCode::TOO_MANY_REQUESTS, None)); + } + *count += 1; + Ok(()) +} diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 00000000..3efb369d --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,33 @@ +use crate::controller::ws_controller; +use axum::routing::get; +use axum::Router; +use std::net::SocketAddr; + +mod config; +mod controller; +mod util; + +#[tokio::main] +async fn main() { + let app_state = config::init::init().await; + + let app = configure_routes() + .with_state(app_state) + .into_make_service_with_connect_info::(); + + let server_ip = std::env::var("SERVER_IP").unwrap_or_else(|_| "0.0.0.0".to_string()); + let server_port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "3000".to_string()); + let bind_address = format!("{server_ip}:{server_port}"); + + let listener = tokio::net::TcpListener::bind(bind_address.clone()) + .await + .unwrap(); + tracing::info!("Listening on http://{bind_address}"); + axum::serve(listener, app).await.unwrap(); +} + +#[rustfmt::skip] +fn configure_routes() -> Router { + Router::new() + .route("/v1/ws", get(ws_controller::ws_handler)) +} diff --git a/server/src/util/base64.rs b/server/src/util/base64.rs new file mode 100644 index 00000000..87eff340 --- /dev/null +++ b/server/src/util/base64.rs @@ -0,0 +1,9 @@ +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::engine::GeneralPurpose; +use base64::{DecodeError, Engine}; + +const BASE_64_ENGINE: GeneralPurpose = URL_SAFE_NO_PAD; + +pub(crate) fn decode(data: &str) -> Result, DecodeError> { + BASE_64_ENGINE.decode(data) +} diff --git a/server/src/util/ip.rs b/server/src/util/ip.rs new file mode 100644 index 00000000..72e23207 --- /dev/null +++ b/server/src/util/ip.rs @@ -0,0 +1,40 @@ +use std::net::IpAddr; + +pub(crate) fn get_ip_group(ip: IpAddr) -> String { + match ip { + IpAddr::V4(ip) => { + // IPv4: Each IP address is already a group. + ip.to_string() + } + IpAddr::V6(ip) => { + // IPv6: We treat /64 as a group. + let segments = ip.segments(); + format!( + "{:x}:{:x}:{:x}:{:x}", + segments[0], segments[1], segments[2], segments[3] + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_get_ip_group() { + assert_eq!( + get_ip_group(IpAddr::from_str("1.2.3.4").unwrap()), + "1.2.3.4" + ); + assert_eq!( + get_ip_group(IpAddr::from_str("1:2:3:4:5:6:7:8").unwrap()), + "1:2:3:4" + ); + assert_eq!( + get_ip_group(IpAddr::from_str("a:b:c:d:e:f:0:1").unwrap()), + "a:b:c:d" + ); + } +} diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs new file mode 100644 index 00000000..979cac30 --- /dev/null +++ b/server/src/util/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod base64; +pub(crate) mod ip;