From 4dbc369003dd650dde9edae058cb5f80bf90b6e5 Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 12 May 2026 21:24:49 +0200 Subject: [PATCH] implement new backup mechanism --- CHANGELOG.md | 6 + lib/app.dart | 2 - lib/core/backup/backup_password.dart | 29 + lib/core/bridge/wrapper/backup.dart | 87 ++ lib/core/bridge/wrapper/key_manager.dart | 52 +- lib/core/frb_generated.dart | 1068 ++++++++++++- lib/core/frb_generated.io.dart | 181 ++- lib/core/frb_generated.web.dart | 181 ++- lib/core/keys/backup_password_keys.dart | 29 + lib/core/lib.dart | 20 + lib/globals.dart | 19 +- lib/main.dart | 43 +- .../callbacks/user_discovery.callbacks.dart | 26 +- lib/src/constants/keyvalue.keys.dart | 2 + lib/src/constants/routes.keys.dart | 1 - lib/src/constants/secure_storage.keys.dart | 10 +- .../database/daos/key_verification.dao.dart | 13 +- .../signal/signal_signed_pre_key_store.dart | 65 +- .../generated/app_localizations.dart | 126 +- .../generated/app_localizations_de.dart | 72 +- .../generated/app_localizations_en.dart | 68 +- lib/src/localization/translations | 2 +- lib/src/model/json/backup.model.dart | 51 + lib/src/model/json/backup.model.g.dart | 65 + lib/src/model/json/userdata.model.dart | 25 +- lib/src/model/json/userdata.model.g.dart | 32 +- .../api/websocket/client_to_server.pb.dart | 82 +- .../websocket/client_to_server.pbjson.dart | 59 +- lib/src/providers/routing.provider.dart | 5 - lib/src/services/api.service.dart | 30 +- .../api/client2client/contact.c2c.dart | 6 +- .../api/client2client/groups.c2c.dart | 2 +- .../api/mediafiles/media_background.api.dart | 9 +- lib/src/services/api/messages.api.dart | 2 + lib/src/services/api/server_messages.api.dart | 2 +- lib/src/services/api/utils.api.dart | 2 +- lib/src/services/backup.service.dart | 361 +++++ lib/src/services/backup/common.backup.dart | 89 -- lib/src/services/backup/create.backup.dart | 238 --- lib/src/services/backup/restore.backup.dart | 117 -- ...group.services.dart => group.service.dart} | 0 .../notifications/fcm.notifications.dart | 56 +- lib/src/services/signal/identity.signal.dart | 127 +- lib/src/services/user.service.dart | 2 +- lib/src/utils/keyvalue.dart | 50 +- lib/src/utils/misc.dart | 4 +- .../select_chat_deletion_time.comp.dart | 2 +- lib/src/visual/components/snackbar.dart | 258 ++++ .../camera_scanned_overlay.dart | 20 +- .../camera_preview_controller_view.dart | 25 +- .../main_camera_controller.dart | 19 +- .../visual/views/chats/chat_list.view.dart | 2 + .../chat_group_action.dart | 38 - .../contact/add_contact_via_qr_link.view.dart | 9 +- .../visual/views/contact/contact.view.dart | 17 +- lib/src/visual/views/groups/group.view.dart | 11 +- .../group_create_select_group_name.view.dart | 2 +- .../group_create_select_members.view.dart | 8 +- .../views/groups/group_member.context.dart | 12 +- .../visual/views/onboarding/recover.view.dart | 70 +- .../views/onboarding/setup/backup.setup.dart | 47 +- .../visual/views/settings/account.view.dart | 11 +- .../settings/backup/backup_server.view.dart | 182 --- .../settings/backup/backup_settings.view.dart | 206 ++- .../settings/backup/backup_setup.view.dart | 70 +- .../components/missing_backup_setup.comp.dart | 139 ++ .../settings/chat/chat_reactions.view.dart | 8 +- .../views/settings/help/contact_us.view.dart | 5 +- .../help/contact_us/submit_message.view.dart | 20 +- .../views/settings/help/diagnostics.view.dart | 30 +- .../views/settings/notification.view.dart | 8 +- .../views/settings/profile/profile.view.dart | 21 +- .../subscription/additional_users.view.dart | 38 +- .../views/shared/memory_item_slider.view.dart | 13 +- .../user_study_questionnaire.view.dart | 8 +- rust/Cargo.lock | 1372 +---------------- rust/Cargo.toml | 18 +- rust/src/backup/backup_archive.rs | 99 +- rust/src/backup/backup_identity.rs | 83 + rust/src/backup/backup_password.rs | 127 -- rust/src/backup/backup_passwordless/types.rs | 1 + rust/src/backup/mod.rs | 6 +- rust/src/bridge/mod.rs | 4 + rust/src/bridge/wrapper/backup.rs | 90 ++ rust/src/bridge/wrapper/key_manager.rs | 92 +- rust/src/bridge/wrapper/mod.rs | 1 + rust/src/context.rs | 31 +- rust/src/database/mod.rs | 1 - rust/src/database/tables/received_messages.rs | 1 + rust/src/error.rs | 6 + rust/src/frb_generated.rs | 607 +++++++- rust/src/keys/backup_password_keys.rs | 38 + rust/src/keys/identity_key.rs | 8 - rust/src/keys/identity_key/mod.rs | 1 + .../keys/identity_key/signal_identity_key.rs | 33 + rust/src/keys/main_key.rs | 154 +- rust/src/keys/mod.rs | 10 +- rust/src/secure_storage.rs | 22 +- rust/src/standalone.rs | 6 + test/services/backup_service_test.dart | 233 +++ ...ices_test.dart => group_service_test.dart} | 2 +- test/utils/key_value_test.dart | 86 ++ 102 files changed, 4668 insertions(+), 3281 deletions(-) create mode 100644 lib/core/backup/backup_password.dart create mode 100644 lib/core/bridge/wrapper/backup.dart create mode 100644 lib/core/keys/backup_password_keys.dart create mode 100644 lib/core/lib.dart create mode 100644 lib/src/model/json/backup.model.dart create mode 100644 lib/src/model/json/backup.model.g.dart create mode 100644 lib/src/services/backup.service.dart delete mode 100644 lib/src/services/backup/common.backup.dart delete mode 100644 lib/src/services/backup/create.backup.dart delete mode 100644 lib/src/services/backup/restore.backup.dart rename lib/src/services/{group.services.dart => group.service.dart} (100%) create mode 100644 lib/src/visual/components/snackbar.dart delete mode 100644 lib/src/visual/views/settings/backup/backup_server.view.dart create mode 100644 lib/src/visual/views/settings/backup/components/missing_backup_setup.comp.dart create mode 100644 rust/src/backup/backup_identity.rs delete mode 100644 rust/src/backup/backup_password.rs create mode 100644 rust/src/bridge/wrapper/backup.rs create mode 100644 rust/src/keys/backup_password_keys.rs delete mode 100644 rust/src/keys/identity_key.rs create mode 100644 rust/src/keys/identity_key/mod.rs create mode 100644 rust/src/keys/identity_key/signal_identity_key.rs create mode 100644 test/services/backup_service_test.dart rename test/services/{group_services_test.dart => group_service_test.dart} (98%) create mode 100644 test/utils/key_value_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c214313..3246dbbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.11 + +- Improved: Redesigned snackbar notifications +- Improved: New backup mechanism to allow larger backup files +- Improved: Move keys into a centralized Rust-owned structure stored in secure storage + ## 0.2.10 - Fix: Issue with push notifications on Android diff --git a/lib/app.dart b/lib/app.dart index 02be717f..f24a3471 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -77,7 +77,6 @@ class _AppState extends State with WidgetsBindingObserver { if (widget.storageError) { return MaterialApp( - scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey, localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, @@ -91,7 +90,6 @@ class _AppState extends State with WidgetsBindingObserver { return MaterialApp.router( routerConfig: routerProvider, - scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey, localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, diff --git a/lib/core/backup/backup_password.dart b/lib/core/backup/backup_password.dart new file mode 100644 index 00000000..eacdead5 --- /dev/null +++ b/lib/core/backup/backup_password.dart @@ -0,0 +1,29 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import '../lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class BackupPasswordKeys { + final U8Array32 backupId; + final U8Array32 encryptionKey; + + const BackupPasswordKeys({ + required this.backupId, + required this.encryptionKey, + }); + + @override + int get hashCode => backupId.hashCode ^ encryptionKey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BackupPasswordKeys && + runtimeType == other.runtimeType && + backupId == other.backupId && + encryptionKey == other.encryptionKey; +} diff --git a/lib/core/bridge/wrapper/backup.dart b/lib/core/bridge/wrapper/backup.dart new file mode 100644 index 00000000..454ed238 --- /dev/null +++ b/lib/core/bridge/wrapper/backup.dart @@ -0,0 +1,87 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../../frb_generated.dart'; +import '../../keys/backup_password_keys.dart'; +import '../../lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class RustBackupArchive { + const RustBackupArchive(); + + static Future<(String, String)> createBackupArchive() => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupArchiveCreateBackupArchive(); + + static Future getBackupDownloadToken() => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadToken(); + + static Future restoreBackupArchive({required String filePath}) => + RustLib.instance.api + .crateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchive( + filePath: filePath, + ); + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RustBackupArchive && runtimeType == other.runtimeType; +} + +class RustBackupIdentity { + const RustBackupIdentity(); + + static Future getBackupId() => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentityGetBackupId(); + + static Future getBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }) => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeys( + userId: userId, + password: password, + ); + + static Future getIdentityBackupBytes() => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytes(); + + static Future importBackupPasswordKeys({ + required List backupId, + required List encryptionKey, + }) => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeys( + backupId: backupId, + encryptionKey: encryptionKey, + ); + + static Future restoreIdentityBackup({ + required BackupPasswordKeys keys, + required List encryptedBytes, + }) => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackup( + keys: keys, + encryptedBytes: encryptedBytes, + ); + + static Future setBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }) => RustLib.instance.api + .crateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeys( + userId: userId, + password: password, + ); + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RustBackupIdentity && runtimeType == other.runtimeType; +} diff --git a/lib/core/bridge/wrapper/key_manager.dart b/lib/core/bridge/wrapper/key_manager.dart index 628fc0c1..5cf8bc95 100644 --- a/lib/core/bridge/wrapper/key_manager.dart +++ b/lib/core/bridge/wrapper/key_manager.dart @@ -6,11 +6,55 @@ import '../../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; -class FlutterKeyManager { - const FlutterKeyManager(); +class RustKeyManager { + const RustKeyManager(); static Future getLoginToken() => RustLib.instance.api - .crateBridgeWrapperKeyManagerFlutterKeyManagerGetLoginToken(); + .crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken(); + + static Future<(Uint8List, PlatformInt64)> getSignalIdentity() => RustLib + .instance + .api + .crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity(); + + static Future importSignalIdentity({ + required List identityKeyPairStructure, + required PlatformInt64 registrationId, + required Map signedPreKeyStore, + }) => RustLib.instance.api + .crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity( + identityKeyPairStructure: identityKeyPairStructure, + registrationId: registrationId, + signedPreKeyStore: signedPreKeyStore, + ); + + static Future loadSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }) => RustLib.instance.api + .crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekey( + signedPreKeyId: signedPreKeyId, + ); + + static Future> loadSignedPrekeys() => RustLib + .instance + .api + .crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys(); + + static Future removeSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }) => RustLib.instance.api + .crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey( + signedPreKeyId: signedPreKeyId, + ); + + static Future storeSignedPrekey({ + required PlatformInt64 signedPreKeyId, + required List record, + }) => RustLib.instance.api + .crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey( + signedPreKeyId: signedPreKeyId, + record: record, + ); @override int get hashCode => 0; @@ -18,5 +62,5 @@ class FlutterKeyManager { @override bool operator ==(Object other) => identical(this, other) || - other is FlutterKeyManager && runtimeType == other.runtimeType; + other is RustKeyManager && runtimeType == other.runtimeType; } diff --git a/lib/core/frb_generated.dart b/lib/core/frb_generated.dart index 0b769958..277cc034 100644 --- a/lib/core/frb_generated.dart +++ b/lib/core/frb_generated.dart @@ -5,6 +5,7 @@ import 'bridge.dart'; import 'bridge/callbacks.dart'; +import 'bridge/wrapper/backup.dart'; import 'bridge/wrapper/key_manager.dart'; import 'bridge/wrapper/user_discovery.dart'; import 'dart:async'; @@ -12,6 +13,8 @@ import 'dart:convert'; import 'frb_generated.dart'; import 'frb_generated.io.dart' if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'keys/backup_password_keys.dart'; +import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; /// Main entrypoint of the Rust API @@ -71,7 +74,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => 1007286393; + int get rustContentHash => 1215442517; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -83,9 +86,6 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { - Future - crateBridgeWrapperKeyManagerFlutterKeyManagerGetLoginToken(); - Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion(); @@ -157,6 +157,71 @@ abstract class RustLibApi extends BaseApi { }); Future crateBridgeInitializeTwonlyFlutter({required InitConfig config}); + + Future<(String, String)> + crateBridgeWrapperBackupRustBackupArchiveCreateBackupArchive(); + + Future + crateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadToken(); + + Future crateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchive({ + required String filePath, + }); + + Future crateBridgeWrapperBackupRustBackupIdentityGetBackupId(); + + Future + crateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }); + + Future + crateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytes(); + + Future + crateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeys({ + required List backupId, + required List encryptionKey, + }); + + Future crateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackup({ + required BackupPasswordKeys keys, + required List encryptedBytes, + }); + + Future crateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }); + + Future crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken(); + + Future<(Uint8List, PlatformInt64)> + crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity(); + + Future crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({ + required List identityKeyPairStructure, + required PlatformInt64 registrationId, + required Map signedPreKeyStore, + }); + + Future + crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }); + + Future> + crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys(); + + Future crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }); + + Future crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({ + required PlatformInt64 signedPreKeyId, + required List record, + }); } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -167,39 +232,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); - @override - Future - crateBridgeWrapperKeyManagerFlutterKeyManagerGetLoginToken() { - return handler.executeNormal( - NormalTask( - callFfi: (port_) { - final serializer = SseSerializer(generalizedFrbRustBinding); - pdeCallFfi( - generalizedFrbRustBinding, - serializer, - funcId: 1, - port: port_, - ); - }, - codec: SseCodec( - decodeSuccessData: sse_decode_list_prim_u_8_strict, - decodeErrorData: sse_decode_AnyhowException, - ), - constMeta: - kCrateBridgeWrapperKeyManagerFlutterKeyManagerGetLoginTokenConstMeta, - argValues: [], - apiImpl: this, - ), - ); - } - - TaskConstMeta - get kCrateBridgeWrapperKeyManagerFlutterKeyManagerGetLoginTokenConstMeta => - const TaskConstMeta( - debugName: "flutter_key_manager_get_login_token", - argNames: [], - ); - @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion() { @@ -210,7 +242,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 2, + funcId: 1, port: port_, ); }, @@ -248,7 +280,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 3, + funcId: 2, port: port_, ); }, @@ -291,7 +323,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 4, + funcId: 3, port: port_, ); }, @@ -333,7 +365,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 5, + funcId: 4, port: port_, ); }, @@ -371,7 +403,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 6, + funcId: 5, port: port_, ); }, @@ -412,7 +444,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 7, + funcId: 6, port: port_, ); }, @@ -536,7 +568,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 8, + funcId: 7, port: port_, ); }, @@ -601,7 +633,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 9, + funcId: 8, port: port_, ); }, @@ -622,6 +654,578 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["config"], ); + @override + Future<(String, String)> + crateBridgeWrapperBackupRustBackupArchiveCreateBackupArchive() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 9, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_record_string_string, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupArchiveCreateBackupArchiveConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupArchiveCreateBackupArchiveConstMeta => + const TaskConstMeta( + debugName: "rust_backup_archive_create_backup_archive", + argNames: [], + ); + + @override + Future + crateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadToken() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 10, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_opt_String, + decodeErrorData: null, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadTokenConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadTokenConstMeta => + const TaskConstMeta( + debugName: "rust_backup_archive_get_backup_download_token", + argNames: [], + ); + + @override + Future crateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchive({ + required String filePath, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(filePath, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 11, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchiveConstMeta, + argValues: [filePath], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchiveConstMeta => + const TaskConstMeta( + debugName: "rust_backup_archive_restore_backup_archive", + argNames: ["filePath"], + ); + + @override + Future crateBridgeWrapperBackupRustBackupIdentityGetBackupId() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 12, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_opt_String, + decodeErrorData: null, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentityGetBackupIdConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentityGetBackupIdConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_get_backup_id", + argNames: [], + ); + + @override + Future + crateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + sse_encode_String(password, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 13, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_backup_password_keys, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeysConstMeta, + argValues: [userId, password], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeysConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_get_backup_password_keys", + argNames: ["userId", "password"], + ); + + @override + Future + crateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytes() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 14, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytesConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytesConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_get_identity_backup_bytes", + argNames: [], + ); + + @override + Future + crateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeys({ + required List backupId, + required List encryptionKey, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(backupId, serializer); + sse_encode_list_prim_u_8_loose(encryptionKey, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 15, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeysConstMeta, + argValues: [backupId, encryptionKey], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeysConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_import_backup_password_keys", + argNames: ["backupId", "encryptionKey"], + ); + + @override + Future crateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackup({ + required BackupPasswordKeys keys, + required List encryptedBytes, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_box_autoadd_backup_password_keys(keys, serializer); + sse_encode_list_prim_u_8_loose(encryptedBytes, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 16, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackupConstMeta, + argValues: [keys, encryptedBytes], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackupConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_restore_identity_backup", + argNames: ["keys", "encryptedBytes"], + ); + + @override + Future crateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeys({ + required PlatformInt64 userId, + required String password, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(userId, serializer); + sse_encode_String(password, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 17, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeysConstMeta, + argValues: [userId, password], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeysConstMeta => + const TaskConstMeta( + debugName: "rust_backup_identity_set_backup_password_keys", + argNames: ["userId", "password"], + ); + + @override + Future crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 18, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerGetLoginTokenConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerGetLoginTokenConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_get_login_token", + argNames: [], + ); + + @override + Future<(Uint8List, PlatformInt64)> + crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 19, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_record_list_prim_u_8_strict_i_64, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentityConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentityConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_get_signal_identity", + argNames: [], + ); + + @override + Future crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({ + required List identityKeyPairStructure, + required PlatformInt64 registrationId, + required Map signedPreKeyStore, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(identityKeyPairStructure, serializer); + sse_encode_i_64(registrationId, serializer); + sse_encode_Map_i_64_list_prim_u_8_strict_None( + signedPreKeyStore, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 20, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentityConstMeta, + argValues: [ + identityKeyPairStructure, + registrationId, + signedPreKeyStore, + ], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentityConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_import_signal_identity", + argNames: [ + "identityKeyPairStructure", + "registrationId", + "signedPreKeyStore", + ], + ); + + @override + Future + crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(signedPreKeyId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 21, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_opt_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeyConstMeta, + argValues: [signedPreKeyId], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeyConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_load_signed_prekey", + argNames: ["signedPreKeyId"], + ); + + @override + Future> + crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 22, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_Map_i_64_list_prim_u_8_strict_None, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeysConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeysConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_load_signed_prekeys", + argNames: [], + ); + + @override + Future crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({ + required PlatformInt64 signedPreKeyId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(signedPreKeyId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 23, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekeyConstMeta, + argValues: [signedPreKeyId], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekeyConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_remove_signed_prekey", + argNames: ["signedPreKeyId"], + ); + + @override + Future crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({ + required PlatformInt64 signedPreKeyId, + required List record, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_i_64(signedPreKeyId, serializer); + sse_encode_list_prim_u_8_loose(record, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 24, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: + kCrateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekeyConstMeta, + argValues: [signedPreKeyId, record], + apiImpl: this, + ), + ); + } + + TaskConstMeta + get kCrateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekeyConstMeta => + const TaskConstMeta( + debugName: "rust_key_manager_store_signed_prekey", + argNames: ["signedPreKeyId", "record"], + ); + Future Function( int, ) @@ -1171,6 +1775,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return decodeDartOpaque(raw, generalizedFrbRustBinding); } + @protected + Map dco_decode_Map_i_64_list_prim_u_8_strict_None( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return Map.fromEntries( + dco_decode_list_record_i_64_list_prim_u_8_strict( + raw, + ).map((e) => MapEntry(e.$1, e.$2)), + ); + } + @protected RustStreamSink dco_decode_StreamSink_String_Sse(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1196,6 +1812,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + BackupPasswordKeys dco_decode_backup_password_keys(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 BackupPasswordKeys( + backupId: dco_decode_u_8_array_32(arr[0]), + encryptionKey: dco_decode_u_8_array_32(arr[1]), + ); + } + @protected bool dco_decode_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1208,6 +1836,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_announced_user(raw); } + @protected + BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_backup_password_keys(raw); + } + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1220,15 +1854,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_init_config(raw); } - @protected - FlutterKeyManager dco_decode_flutter_key_manager(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - final arr = raw as List; - if (arr.length != 0) - throw Exception('unexpected arr length: expect 0 but see ${arr.length}'); - return FlutterKeyManager(); - } - @protected FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1286,6 +1911,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as Uint8List; } + @protected + List<(PlatformInt64, Uint8List)> + dco_decode_list_record_i_64_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List) + .map(dco_decode_record_i_64_list_prim_u_8_strict) + .toList(); + } + + @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 AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1332,6 +1972,76 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) { + throw Exception('Expected 2 elements, got ${arr.length}'); + } + return ( + dco_decode_i_64(arr[0]), + dco_decode_list_prim_u_8_strict(arr[1]), + ); + } + + @protected + (Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) { + throw Exception('Expected 2 elements, got ${arr.length}'); + } + return ( + dco_decode_list_prim_u_8_strict(arr[0]), + dco_decode_i_64(arr[1]), + ); + } + + @protected + (String, String) dco_decode_record_string_string(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) { + throw Exception('Expected 2 elements, got ${arr.length}'); + } + return ( + dco_decode_String(arr[0]), + dco_decode_String(arr[1]), + ); + } + + @protected + RustBackupArchive dco_decode_rust_backup_archive(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 0) + throw Exception('unexpected arr length: expect 0 but see ${arr.length}'); + return RustBackupArchive(); + } + + @protected + RustBackupIdentity dco_decode_rust_backup_identity(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 0) + throw Exception('unexpected arr length: expect 0 but see ${arr.length}'); + return RustBackupIdentity(); + } + + @protected + RustKeyManager dco_decode_rust_key_manager(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 0) + throw Exception('unexpected arr length: expect 0 but see ${arr.length}'); + return RustKeyManager(); + } + @protected int dco_decode_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1344,6 +2054,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as int; } + @protected + U8Array32 dco_decode_u_8_array_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return U8Array32(dco_decode_list_prim_u_8_strict(raw)); + } + @protected void dco_decode_unit(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1370,6 +2086,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return decodeDartOpaque(inner, generalizedFrbRustBinding); } + @protected + Map sse_decode_Map_i_64_list_prim_u_8_strict_None( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_record_i_64_list_prim_u_8_strict(deserializer); + return Map.fromEntries(inner.map((e) => MapEntry(e.$1, e.$2))); + } + @protected RustStreamSink sse_decode_StreamSink_String_Sse( SseDeserializer deserializer, @@ -1398,6 +2123,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + BackupPasswordKeys sse_decode_backup_password_keys( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_backupId = sse_decode_u_8_array_32(deserializer); + var var_encryptionKey = sse_decode_u_8_array_32(deserializer); + return BackupPasswordKeys( + backupId: var_backupId, + encryptionKey: var_encryptionKey, + ); + } + @protected bool sse_decode_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1412,6 +2150,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_announced_user(deserializer)); } + @protected + BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_backup_password_keys(deserializer)); + } + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1424,14 +2170,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_init_config(deserializer)); } - @protected - FlutterKeyManager sse_decode_flutter_key_manager( - SseDeserializer deserializer, - ) { - // Codec=Sse (Serialization based), see doc to use other codecs - return FlutterKeyManager(); - } - @protected FlutterUserDiscovery sse_decode_flutter_user_discovery( SseDeserializer deserializer, @@ -1502,6 +2240,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8List(len_); } + @protected + List<(PlatformInt64, Uint8List)> + sse_decode_list_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = <(PlatformInt64, Uint8List)>[]; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_record_i_64_list_prim_u_8_strict(deserializer)); + } + return ans_; + } + + @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 AnnouncedUser? sse_decode_opt_box_autoadd_announced_user( SseDeserializer deserializer, @@ -1584,6 +2348,58 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_i_64(deserializer); + var var_field1 = sse_decode_list_prim_u_8_strict(deserializer); + return (var_field0, var_field1); + } + + @protected + (Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_list_prim_u_8_strict(deserializer); + var var_field1 = sse_decode_i_64(deserializer); + return (var_field0, var_field1); + } + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_String(deserializer); + var var_field1 = sse_decode_String(deserializer); + return (var_field0, var_field1); + } + + @protected + RustBackupArchive sse_decode_rust_backup_archive( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RustBackupArchive(); + } + + @protected + RustBackupIdentity sse_decode_rust_backup_identity( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RustBackupIdentity(); + } + + @protected + RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return RustKeyManager(); + } + @protected int sse_decode_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1596,6 +2412,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8(); } + @protected + U8Array32 sse_decode_u_8_array_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return U8Array32(inner); + } + @protected void sse_decode_unit(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1811,6 +2634,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void sse_encode_Map_i_64_list_prim_u_8_strict_None( + Map self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_record_i_64_list_prim_u_8_strict( + self.entries.map((e) => (e.key, e.value)).toList(), + serializer, + ); + } + @protected void sse_encode_StreamSink_String_Sse( RustStreamSink self, @@ -1842,6 +2677,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_i_64(self.publicId, serializer); } + @protected + void sse_encode_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_8_array_32(self.backupId, serializer); + sse_encode_u_8_array_32(self.encryptionKey, serializer); + } + @protected void sse_encode_bool(bool self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1857,6 +2702,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_announced_user(self, serializer); } + @protected + void sse_encode_box_autoadd_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_backup_password_keys(self, serializer); + } + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -1875,14 +2729,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_init_config(self, serializer); } - @protected - void sse_encode_flutter_key_manager( - FlutterKeyManager self, - SseSerializer serializer, - ) { - // Codec=Sse (Serialization based), see doc to use other codecs - } - @protected void sse_encode_flutter_user_discovery( FlutterUserDiscovery self, @@ -1956,6 +2802,28 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8List(self); } + @protected + void sse_encode_list_record_i_64_list_prim_u_8_strict( + List<(PlatformInt64, Uint8List)> 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_record_i_64_list_prim_u_8_strict(item, serializer); + } + } + + @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_announced_user( AnnouncedUser? self, @@ -2038,6 +2906,60 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void sse_encode_record_i_64_list_prim_u_8_strict( + (PlatformInt64, Uint8List) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_64(self.$1, serializer); + sse_encode_list_prim_u_8_strict(self.$2, serializer); + } + + @protected + void sse_encode_record_list_prim_u_8_strict_i_64( + (Uint8List, PlatformInt64) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(self.$1, serializer); + sse_encode_i_64(self.$2, serializer); + } + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.$1, serializer); + sse_encode_String(self.$2, serializer); + } + + @protected + void sse_encode_rust_backup_archive( + RustBackupArchive self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_rust_backup_identity( + RustBackupIdentity self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_rust_key_manager( + RustKeyManager self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + @protected void sse_encode_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -2050,6 +2972,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8(self); } + @protected + void sse_encode_u_8_array_32(U8Array32 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(self.inner, serializer); + } + @protected void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/core/frb_generated.io.dart b/lib/core/frb_generated.io.dart index 482e21c8..938d45c5 100644 --- a/lib/core/frb_generated.io.dart +++ b/lib/core/frb_generated.io.dart @@ -5,12 +5,15 @@ import 'bridge.dart'; import 'bridge/callbacks.dart'; +import 'bridge/wrapper/backup.dart'; import 'bridge/wrapper/key_manager.dart'; import 'bridge/wrapper/user_discovery.dart'; import 'dart:async'; import 'dart:convert'; import 'dart:ffi' as ffi; import 'frb_generated.dart'; +import 'keys/backup_password_keys.dart'; +import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; abstract class RustLibApiImplPlatform extends BaseApiImpl { @@ -99,6 +102,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Object dco_decode_DartOpaque(dynamic raw); + @protected + Map dco_decode_Map_i_64_list_prim_u_8_strict_None( + dynamic raw, + ); + @protected RustStreamSink dco_decode_StreamSink_String_Sse(dynamic raw); @@ -108,21 +116,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnnouncedUser dco_decode_announced_user(dynamic raw); + @protected + BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw); + @protected bool dco_decode_bool(dynamic raw); @protected AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw); + @protected + BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw); + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected InitConfig dco_decode_box_autoadd_init_config(dynamic raw); - @protected - FlutterKeyManager dco_decode_flutter_key_manager(dynamic raw); - @protected FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw); @@ -147,6 +158,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + List<(PlatformInt64, Uint8List)> + dco_decode_list_record_i_64_list_prim_u_8_strict(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + @protected AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw); @@ -165,12 +183,37 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion dco_decode_other_promotion(dynamic raw); + @protected + (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( + dynamic raw, + ); + + @protected + (Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64( + dynamic raw, + ); + + @protected + (String, String) dco_decode_record_string_string(dynamic raw); + + @protected + RustBackupArchive dco_decode_rust_backup_archive(dynamic raw); + + @protected + RustBackupIdentity dco_decode_rust_backup_identity(dynamic raw); + + @protected + RustKeyManager dco_decode_rust_key_manager(dynamic raw); + @protected int dco_decode_u_32(dynamic raw); @protected int dco_decode_u_8(dynamic raw); + @protected + U8Array32 dco_decode_u_8_array_32(dynamic raw); + @protected void dco_decode_unit(dynamic raw); @@ -183,6 +226,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Object sse_decode_DartOpaque(SseDeserializer deserializer); + @protected + Map sse_decode_Map_i_64_list_prim_u_8_strict_None( + SseDeserializer deserializer, + ); + @protected RustStreamSink sse_decode_StreamSink_String_Sse( SseDeserializer deserializer, @@ -194,6 +242,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer); + @protected + BackupPasswordKeys sse_decode_backup_password_keys( + SseDeserializer deserializer, + ); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -202,17 +255,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( + SseDeserializer deserializer, + ); + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); - @protected - FlutterKeyManager sse_decode_flutter_key_manager( - SseDeserializer deserializer, - ); - @protected FlutterUserDiscovery sse_decode_flutter_user_discovery( SseDeserializer deserializer, @@ -243,6 +296,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + List<(PlatformInt64, Uint8List)> + sse_decode_list_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + @protected AnnouncedUser? sse_decode_opt_box_autoadd_announced_user( SseDeserializer deserializer, @@ -267,12 +329,43 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer); + @protected + (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ); + + @protected + (Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64( + SseDeserializer deserializer, + ); + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ); + + @protected + RustBackupArchive sse_decode_rust_backup_archive( + SseDeserializer deserializer, + ); + + @protected + RustBackupIdentity sse_decode_rust_backup_identity( + SseDeserializer deserializer, + ); + + @protected + RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer); + @protected int sse_decode_u_32(SseDeserializer deserializer); @protected int sse_decode_u_8(SseDeserializer deserializer); + @protected + U8Array32 sse_decode_u_8_array_32(SseDeserializer deserializer); + @protected void sse_decode_unit(SseDeserializer deserializer); @@ -373,6 +466,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_DartOpaque(Object self, SseSerializer serializer); + @protected + void sse_encode_Map_i_64_list_prim_u_8_strict_None( + Map self, + SseSerializer serializer, + ); + @protected void sse_encode_StreamSink_String_Sse( RustStreamSink self, @@ -385,6 +484,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer); + @protected + void sse_encode_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -394,6 +499,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -406,12 +517,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); - @protected - void sse_encode_flutter_key_manager( - FlutterKeyManager self, - SseSerializer serializer, - ); - @protected void sse_encode_flutter_user_discovery( FlutterUserDiscovery self, @@ -448,6 +553,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_record_i_64_list_prim_u_8_strict( + List<(PlatformInt64, Uint8List)> self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_announced_user( AnnouncedUser? self, @@ -484,12 +598,51 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_record_i_64_list_prim_u_8_strict( + (PlatformInt64, Uint8List) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_record_list_prim_u_8_strict_i_64( + (Uint8List, PlatformInt64) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_backup_archive( + RustBackupArchive self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_backup_identity( + RustBackupIdentity self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_key_manager( + RustKeyManager self, + SseSerializer serializer, + ); + @protected void sse_encode_u_32(int self, SseSerializer serializer); @protected void sse_encode_u_8(int self, SseSerializer serializer); + @protected + void sse_encode_u_8_array_32(U8Array32 self, SseSerializer serializer); + @protected void sse_encode_unit(void self, SseSerializer serializer); diff --git a/lib/core/frb_generated.web.dart b/lib/core/frb_generated.web.dart index 868baea7..23c61b25 100644 --- a/lib/core/frb_generated.web.dart +++ b/lib/core/frb_generated.web.dart @@ -8,11 +8,14 @@ import 'bridge.dart'; import 'bridge/callbacks.dart'; +import 'bridge/wrapper/backup.dart'; import 'bridge/wrapper/key_manager.dart'; import 'bridge/wrapper/user_discovery.dart'; import 'dart:async'; import 'dart:convert'; import 'frb_generated.dart'; +import 'keys/backup_password_keys.dart'; +import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; abstract class RustLibApiImplPlatform extends BaseApiImpl { @@ -101,6 +104,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Object dco_decode_DartOpaque(dynamic raw); + @protected + Map dco_decode_Map_i_64_list_prim_u_8_strict_None( + dynamic raw, + ); + @protected RustStreamSink dco_decode_StreamSink_String_Sse(dynamic raw); @@ -110,21 +118,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnnouncedUser dco_decode_announced_user(dynamic raw); + @protected + BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw); + @protected bool dco_decode_bool(dynamic raw); @protected AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw); + @protected + BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw); + @protected PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected InitConfig dco_decode_box_autoadd_init_config(dynamic raw); - @protected - FlutterKeyManager dco_decode_flutter_key_manager(dynamic raw); - @protected FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw); @@ -149,6 +160,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + List<(PlatformInt64, Uint8List)> + dco_decode_list_record_i_64_list_prim_u_8_strict(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + @protected AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw); @@ -167,12 +185,37 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion dco_decode_other_promotion(dynamic raw); + @protected + (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( + dynamic raw, + ); + + @protected + (Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64( + dynamic raw, + ); + + @protected + (String, String) dco_decode_record_string_string(dynamic raw); + + @protected + RustBackupArchive dco_decode_rust_backup_archive(dynamic raw); + + @protected + RustBackupIdentity dco_decode_rust_backup_identity(dynamic raw); + + @protected + RustKeyManager dco_decode_rust_key_manager(dynamic raw); + @protected int dco_decode_u_32(dynamic raw); @protected int dco_decode_u_8(dynamic raw); + @protected + U8Array32 dco_decode_u_8_array_32(dynamic raw); + @protected void dco_decode_unit(dynamic raw); @@ -185,6 +228,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Object sse_decode_DartOpaque(SseDeserializer deserializer); + @protected + Map sse_decode_Map_i_64_list_prim_u_8_strict_None( + SseDeserializer deserializer, + ); + @protected RustStreamSink sse_decode_StreamSink_String_Sse( SseDeserializer deserializer, @@ -196,6 +244,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer); + @protected + BackupPasswordKeys sse_decode_backup_password_keys( + SseDeserializer deserializer, + ); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -204,17 +257,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys( + SseDeserializer deserializer, + ); + @protected PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); - @protected - FlutterKeyManager sse_decode_flutter_key_manager( - SseDeserializer deserializer, - ); - @protected FlutterUserDiscovery sse_decode_flutter_user_discovery( SseDeserializer deserializer, @@ -245,6 +298,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + List<(PlatformInt64, Uint8List)> + sse_decode_list_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + @protected AnnouncedUser? sse_decode_opt_box_autoadd_announced_user( SseDeserializer deserializer, @@ -269,12 +331,43 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer); + @protected + (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( + SseDeserializer deserializer, + ); + + @protected + (Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64( + SseDeserializer deserializer, + ); + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ); + + @protected + RustBackupArchive sse_decode_rust_backup_archive( + SseDeserializer deserializer, + ); + + @protected + RustBackupIdentity sse_decode_rust_backup_identity( + SseDeserializer deserializer, + ); + + @protected + RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer); + @protected int sse_decode_u_32(SseDeserializer deserializer); @protected int sse_decode_u_8(SseDeserializer deserializer); + @protected + U8Array32 sse_decode_u_8_array_32(SseDeserializer deserializer); + @protected void sse_decode_unit(SseDeserializer deserializer); @@ -375,6 +468,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_DartOpaque(Object self, SseSerializer serializer); + @protected + void sse_encode_Map_i_64_list_prim_u_8_strict_None( + Map self, + SseSerializer serializer, + ); + @protected void sse_encode_StreamSink_String_Sse( RustStreamSink self, @@ -387,6 +486,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer); + @protected + void sse_encode_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -396,6 +501,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_box_autoadd_backup_password_keys( + BackupPasswordKeys self, + SseSerializer serializer, + ); + @protected void sse_encode_box_autoadd_i_64( PlatformInt64 self, @@ -408,12 +519,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); - @protected - void sse_encode_flutter_key_manager( - FlutterKeyManager self, - SseSerializer serializer, - ); - @protected void sse_encode_flutter_user_discovery( FlutterUserDiscovery self, @@ -450,6 +555,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_record_i_64_list_prim_u_8_strict( + List<(PlatformInt64, Uint8List)> self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_announced_user( AnnouncedUser? self, @@ -486,12 +600,51 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_record_i_64_list_prim_u_8_strict( + (PlatformInt64, Uint8List) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_record_list_prim_u_8_strict_i_64( + (Uint8List, PlatformInt64) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_backup_archive( + RustBackupArchive self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_backup_identity( + RustBackupIdentity self, + SseSerializer serializer, + ); + + @protected + void sse_encode_rust_key_manager( + RustKeyManager self, + SseSerializer serializer, + ); + @protected void sse_encode_u_32(int self, SseSerializer serializer); @protected void sse_encode_u_8(int self, SseSerializer serializer); + @protected + void sse_encode_u_8_array_32(U8Array32 self, SseSerializer serializer); + @protected void sse_encode_unit(void self, SseSerializer serializer); diff --git a/lib/core/keys/backup_password_keys.dart b/lib/core/keys/backup_password_keys.dart new file mode 100644 index 00000000..eacdead5 --- /dev/null +++ b/lib/core/keys/backup_password_keys.dart @@ -0,0 +1,29 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import '../lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class BackupPasswordKeys { + final U8Array32 backupId; + final U8Array32 encryptionKey; + + const BackupPasswordKeys({ + required this.backupId, + required this.encryptionKey, + }); + + @override + int get hashCode => backupId.hashCode ^ encryptionKey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BackupPasswordKeys && + runtimeType == other.runtimeType && + backupId == other.backupId && + encryptionKey == other.encryptionKey; +} diff --git a/lib/core/lib.dart b/lib/core/lib.dart new file mode 100644 index 00000000..a0d0fa1d --- /dev/null +++ b/lib/core/lib.dart @@ -0,0 +1,20 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class U8Array32 extends NonGrowableListView { + static const arraySize = 32; + + @internal + Uint8List get inner => _inner; + final Uint8List _inner; + + U8Array32(this._inner) : assert(_inner.length == arraySize), super(_inner); + + U8Array32.init() : this(Uint8List(arraySize)); +} diff --git a/lib/globals.dart b/lib/globals.dart index 752bc1d6..c1d7a93a 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,13 +1,11 @@ import 'dart:async'; - import 'package:camera/camera.dart'; -import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/utils/log.dart'; class AppEnvironment { - static late final String cacheDir; - static late final String supportDir; + static late String cacheDir; + static late String supportDir; static bool _isInitialized = false; @@ -22,10 +20,9 @@ class AppEnvironment { _isInitialized = true; } - static void initTesting() { - if (_isInitialized) return; - cacheDir = '/tmp/twonly_cache'; - supportDir = '/tmp/twonly_support'; + static void initTesting({String? customCacheDir, String? customSupportDir}) { + cacheDir = customCacheDir ?? '/tmp/twonly_cache'; + supportDir = customSupportDir ?? '/tmp/twonly_support'; _isInitialized = true; } } @@ -35,9 +32,5 @@ class AppState { static bool isInBackgroundTask = false; static bool allowErrorTrackingViaSentry = false; static bool gotMessageFromServer = false; - static int latestAppVersionId = 111; -} - -class AppGlobalKeys { - static final scaffoldMessengerKey = GlobalKey(); + static int latestAppVersionId = 112; } diff --git a/lib/main.dart b/lib/main.dart index 507051a3..45c99a06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -8,11 +9,16 @@ import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/app.dart'; import 'package:twonly/core/bridge.dart' as bridge; +import 'package:twonly/core/bridge/wrapper/key_manager.dart'; import 'package:twonly/core/frb_generated.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/callbacks/callbacks.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; +import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart' + show getSignalSignedPreKeyStoreOld; import 'package:twonly/src/database/tables/contacts.table.dart'; +import 'package:twonly/src/model/json/signal_identity.model.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/image_editor.provider.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; @@ -21,7 +27,7 @@ import 'package:twonly/src/services/api/mediafiles/download.api.dart'; import 'package:twonly/src/services/api/mediafiles/media_background.api.dart'; import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; +import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; @@ -185,11 +191,38 @@ Future runMigrations() async { } }); } - if (userService.currentUser.appVersion < 111) { + if (userService.currentUser.appVersion < 113) { + final signalIdentity = await SecureStorage.instance.read( + // ignore: deprecated_member_use_from_same_package + key: SecureStorageKeys.signalIdentity, + ); + + if (signalIdentity != null) { + final decoded = jsonDecode(signalIdentity); + final identity = SignalIdentity.fromJson(decoded as Map); + + try { + await RustKeyManager.importSignalIdentity( + identityKeyPairStructure: identity.identityKeyPairU8List, + registrationId: identity.registrationId, + signedPreKeyStore: await getSignalSignedPreKeyStoreOld(), + ); + Log.info('Importing signal identiy to the rust key manager'); + } catch (e) { + Log.error(e); + } + } + await UserService.update((u) { u - ..appVersion = 111 - ..canUseLoginTokenForAuth = false; + ..appVersion = 113 + ..canUseLoginTokenForAuth = false + // As usernames changes where not considered in the old version force users + // to reenter there passwords. + // ignore: deprecated_member_use_from_same_package + ..twonlySafeBackup?.encryptionKey = [] + // ignore: deprecated_member_use_from_same_package + ..twonlySafeBackup?.backupId = []; }); } } @@ -226,6 +259,6 @@ Future postStartupTasks() async { unawaited(initializeBackgroundTaskManager()); // 3. Delayed tasks (Wait for app to settle) await Future.delayed(const Duration(minutes: 2)); - unawaited(performTwonlySafeBackup()); + unawaited(BackupService.makeBackup()); unawaited(cleanLogFile()); } diff --git a/lib/src/callbacks/user_discovery.callbacks.dart b/lib/src/callbacks/user_discovery.callbacks.dart index 3b72cc66..86bfc4df 100644 --- a/lib/src/callbacks/user_discovery.callbacks.dart +++ b/lib/src/callbacks/user_discovery.callbacks.dart @@ -38,21 +38,29 @@ class UserDiscoveryCallbacks { Uint8List pubKey, Uint8List signature, ) async { - return Curve.verifySignature( - IdentityKey.fromBytes(pubKey, 0).publicKey, - inputData, - signature, - ); + try { + return Curve.verifySignature( + IdentityKey.fromBytes(pubKey, 0).publicKey, + inputData, + signature, + ); + } catch (_) { + return false; + } } static Future verifyStoredPubKey( int contactId, Uint8List pubKey, ) async { - final storedPublicKey = await getPublicKeyFromContact(contactId); - if (storedPublicKey != null) { - return storedPublicKey.equals(pubKey); - } else { + try { + final storedPublicKey = await getPublicKeyFromContact(contactId); + if (storedPublicKey != null) { + return storedPublicKey.equals(pubKey); + } else { + return false; + } + } catch (_) { return false; } } diff --git a/lib/src/constants/keyvalue.keys.dart b/lib/src/constants/keyvalue.keys.dart index 4b5e1cfe..57db9890 100644 --- a/lib/src/constants/keyvalue.keys.dart +++ b/lib/src/constants/keyvalue.keys.dart @@ -1,4 +1,6 @@ class KeyValueKeys { static const String lastPeriodicTaskExecution = 'last_periodic_task_execution'; + static const String currentBackupState = 'current_backup_state'; + static const String backupRecoveryState = 'backup_recovery_state'; } diff --git a/lib/src/constants/routes.keys.dart b/lib/src/constants/routes.keys.dart index 66a1fabb..d6690619 100644 --- a/lib/src/constants/routes.keys.dart +++ b/lib/src/constants/routes.keys.dart @@ -25,7 +25,6 @@ class Routes { static const String settingsAccount = '/settings/account'; static const String settingsSubscription = '/settings/subscription'; static const String settingsBackup = '/settings/backup'; - static const String settingsBackupServer = '/settings/backup/server'; static const String settingsBackupRecovery = '/settings/backup/recovery'; static const String settingsBackupSetup = '/settings/backup/setup'; static const String settingsAppearance = '/settings/appearance'; diff --git a/lib/src/constants/secure_storage.keys.dart b/lib/src/constants/secure_storage.keys.dart index 43da0690..37aebba0 100644 --- a/lib/src/constants/secure_storage.keys.dart +++ b/lib/src/constants/secure_storage.keys.dart @@ -1,11 +1,15 @@ class SecureStorageKeys { + @Deprecated('Use the secure storage in rust') static const String signalIdentity = 'signal_identity'; + @Deprecated('Use the secure storage in rust') static const String signalSignedPreKey = 'signed_pre_key_store'; + @Deprecated('Use the login token') static const String apiAuthToken = 'api_auth_token'; - static const String googleFcm = 'google_fcm'; - static const String userData = 'userData'; - static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash'; + @Deprecated('Use user.json file') + static const String userData = 'userData'; + + // Not required for backup... static const String receivingPushKeys = 'push_keys_receiving'; static const String sendingPushKeys = 'push_keys_sending'; } diff --git a/lib/src/database/daos/key_verification.dao.dart b/lib/src/database/daos/key_verification.dao.dart index cfcbaf84..a45cf014 100644 --- a/lib/src/database/daos/key_verification.dao.dart +++ b/lib/src/database/daos/key_verification.dao.dart @@ -89,10 +89,12 @@ class KeyVerificationDao extends DatabaseAccessor ), innerJoin(kv, kv.contactId.equalsExp(ur.fromContactId)), ], - )..where( - ur.announcedUserId.equals(contactId) & - ur.publicKeyVerifiedTimestamp.isNotNull(), - ); + ) + ..where( + ur.announcedUserId.equals(contactId) & + ur.publicKeyVerifiedTimestamp.isNotNull(), + ) + ..groupBy([contacts.userId]); return query.watch().map((rows) { return rows.map((row) { @@ -116,7 +118,8 @@ class KeyVerificationDao extends DatabaseAccessor ..where( ur.publicKeyVerifiedTimestamp.isNotNull() & ur.announcedUserId.equalsExp(ur.fromContactId).not(), - ); + ) + ..groupBy([ur.announcedUserId]); final rows = await query.get(); return rows.length; diff --git a/lib/src/database/signal/signal_signed_pre_key_store.dart b/lib/src/database/signal/signal_signed_pre_key_store.dart index 5017230c..dbdcf970 100644 --- a/lib/src/database/signal/signal_signed_pre_key_store.dart +++ b/lib/src/database/signal/signal_signed_pre_key_store.dart @@ -3,52 +3,43 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:twonly/core/bridge/wrapper/key_manager.dart'; import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/utils/secure_storage.dart'; -class SignalSignedPreKeyStore extends SignedPreKeyStore { - Future> getStore() async { - final storeSerialized = await SecureStorage.instance.read( - key: SecureStorageKeys.signalSignedPreKey, - ); - final store = HashMap(); - if (storeSerialized == null) { - return store; - } - final storeHashMap = json.decode(storeSerialized) as List; - for (final item in storeHashMap) { - // ignore: avoid_dynamic_calls - store[item[0] as int] = base64Decode(item[1] as String); - } +Future> getSignalSignedPreKeyStoreOld() async { + final storeSerialized = await SecureStorage.instance.read( + key: SecureStorageKeys.signalSignedPreKey, + ); + final store = HashMap(); + if (storeSerialized == null) { return store; } - - Future safeStore(HashMap store) async { - final storeHashMap = >[]; - for (final item in store.entries) { - storeHashMap.add([item.key, base64Encode(item.value)]); - } - final storeSerialized = json.encode(storeHashMap); - await SecureStorage.instance.write( - key: SecureStorageKeys.signalSignedPreKey, - value: storeSerialized, - ); + final storeHashMap = json.decode(storeSerialized) as List; + for (final item in storeHashMap) { + // ignore: avoid_dynamic_calls + store[item[0] as int] = base64Decode(item[1] as String); } + return store; +} +class SignalSignedPreKeyStore extends SignedPreKeyStore { @override Future loadSignedPreKey(int signedPreKeyId) async { - final store = await getStore(); - if (!store.containsKey(signedPreKeyId)) { + final store = await RustKeyManager.loadSignedPrekey( + signedPreKeyId: signedPreKeyId, + ); + if (store == null) { throw InvalidKeyIdException( 'No such signed prekey record! $signedPreKeyId', ); } - return SignedPreKeyRecord.fromSerialized(store[signedPreKeyId]!); + return SignedPreKeyRecord.fromSerialized(store); } @override Future> loadSignedPreKeys() async { - final store = await getStore(); + final store = await RustKeyManager.loadSignedPrekeys(); final results = []; for (final serialized in store.values) { results.add(SignedPreKeyRecord.fromSerialized(serialized)); @@ -61,19 +52,21 @@ class SignalSignedPreKeyStore extends SignedPreKeyStore { int signedPreKeyId, SignedPreKeyRecord record, ) async { - final store = await getStore(); - store[signedPreKeyId] = record.serialize(); - await safeStore(store); + await RustKeyManager.storeSignedPrekey( + signedPreKeyId: signedPreKeyId, + record: record.serialize(), + ); } @override Future containsSignedPreKey(int signedPreKeyId) async => - (await getStore()).containsKey(signedPreKeyId); + await RustKeyManager.loadSignedPrekey( + signedPreKeyId: signedPreKeyId, + ) != + null; @override Future removeSignedPreKey(int signedPreKeyId) async { - final store = await getStore(); - store.remove(signedPreKeyId); - await safeStore(store); + await RustKeyManager.removeSignedPrekey(signedPreKeyId: signedPreKeyId); } } diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 1ad3b059..bc7f7860 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1286,18 +1286,6 @@ abstract class AppLocalizations { /// **'Open'** String get open; - /// No description provided for @createVoucher. - /// - /// In en, this message translates to: - /// **'Buy voucher'** - String get createVoucher; - - /// No description provided for @redeemVoucher. - /// - /// In en, this message translates to: - /// **'Redeem voucher'** - String get redeemVoucher; - /// No description provided for @buy. /// /// In en, this message translates to: @@ -1412,23 +1400,17 @@ abstract class AppLocalizations { /// **'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.'** String get backupNoPasswordRecovery; - /// No description provided for @backupServer. + /// No description provided for @backupIdentityHeader. /// /// In en, this message translates to: - /// **'Server'** - String get backupServer; + /// **'Identity'** + String get backupIdentityHeader; - /// No description provided for @backupMaxBackupSize. + /// No description provided for @backupArchiveHeader. /// /// In en, this message translates to: - /// **'max. backup size'** - String get backupMaxBackupSize; - - /// No description provided for @backupStorageRetention. - /// - /// In en, this message translates to: - /// **'Storage retention'** - String get backupStorageRetention; + /// **'Contacts, Settings and Messages'** + String get backupArchiveHeader; /// No description provided for @backupLastBackupDate. /// @@ -1448,12 +1430,6 @@ abstract class AppLocalizations { /// **'Result'** String get backupLastBackupResult; - /// No description provided for @backupData. - /// - /// In en, this message translates to: - /// **'Data-Backup'** - String get backupData; - /// No description provided for @backupInsecurePassword. /// /// In en, this message translates to: @@ -1514,36 +1490,12 @@ abstract class AppLocalizations { /// **'Password must be at least 10 characters long.'** String get backupPasswordRequirement; - /// No description provided for @backupExpertSettings. - /// - /// In en, this message translates to: - /// **'Expert settings'** - String get backupExpertSettings; - /// No description provided for @backupEnableBackup. /// /// In en, this message translates to: /// **'Activate automatic backup'** String get backupEnableBackup; - /// No description provided for @backupOwnServerDesc. - /// - /// In en, this message translates to: - /// **'Save your twonly Backup at twonly or on any server of your choice.'** - String get backupOwnServerDesc; - - /// No description provided for @backupUseOwnServer. - /// - /// In en, this message translates to: - /// **'Use server'** - String get backupUseOwnServer; - - /// No description provided for @backupResetServer. - /// - /// In en, this message translates to: - /// **'Use standard server'** - String get backupResetServer; - /// No description provided for @backupTwonlySaveNow. /// /// In en, this message translates to: @@ -2330,12 +2282,6 @@ abstract class AppLocalizations { /// **'Open your own QR code'** String get openYourOwnQRcode; - /// No description provided for @skipForNow. - /// - /// In en, this message translates to: - /// **'Skip for now'** - String get skipForNow; - /// No description provided for @finishSetupCardTitle. /// /// In en, this message translates to: @@ -2354,6 +2300,24 @@ abstract class AppLocalizations { /// **'Resume Setup'** String get finishSetupCardAction; + /// No description provided for @missingBackupCardTitle. + /// + /// In en, this message translates to: + /// **'Setup backup'** + String get missingBackupCardTitle; + + /// No description provided for @missingBackupCardDesc. + /// + /// In en, this message translates to: + /// **'We have improved the backup mechanism, which requires you to set it up again.'** + String get missingBackupCardDesc; + + /// No description provided for @missingBackupCardAction. + /// + /// In en, this message translates to: + /// **'Set up now'** + String get missingBackupCardAction; + /// No description provided for @onboardingFinishLater. /// /// In en, this message translates to: @@ -3061,6 +3025,48 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'{maker} changed their display name from {oldName} to {newName}.'** String makerChangedDisplayName(Object maker, Object oldName, Object newName); + + /// No description provided for @recoverErrorNoInternet. + /// + /// In en, this message translates to: + /// **'No internet connection. Please check your network and try again.'** + String get recoverErrorNoInternet; + + /// No description provided for @recoverErrorUsernameNotValid. + /// + /// In en, this message translates to: + /// **'The username provided is not valid or does not exist.'** + String get recoverErrorUsernameNotValid; + + /// No description provided for @recoverErrorPasswordInvalid. + /// + /// In en, this message translates to: + /// **'The password provided is incorrect.'** + String get recoverErrorPasswordInvalid; + + /// No description provided for @recoverErrorTryAgainLater. + /// + /// In en, this message translates to: + /// **'The server is currently unavailable. Please try again later.'** + String get recoverErrorTryAgainLater; + + /// No description provided for @recoverErrorUnknown. + /// + /// In en, this message translates to: + /// **'An unknown error occurred. Please try again.'** + String get recoverErrorUnknown; + + /// No description provided for @recoverSuccessTitle. + /// + /// In en, this message translates to: + /// **'Backup successfully recovered.'** + String get recoverSuccessTitle; + + /// No description provided for @recoverSuccessBody. + /// + /// In en, this message translates to: + /// **'Click here to open the app again'** + String get recoverSuccessBody; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 17e86ea2..e9f531d3 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -658,12 +658,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get open => 'Offene'; - @override - String get createVoucher => 'Gutschein kaufen'; - - @override - String get redeemVoucher => 'Gutschein einlösen'; - @override String get buy => 'Kaufen'; @@ -725,13 +719,10 @@ class AppLocalizationsDe extends AppLocalizations { 'Aufgrund des Sicherheitssystems von twonly gibt es (derzeit) keine Funktion zur Wiederherstellung des Passworts. Daher musst du dir dein Passwort merken oder, besser noch, aufschreiben.'; @override - String get backupServer => 'Server'; + String get backupIdentityHeader => 'Identität'; @override - String get backupMaxBackupSize => 'max. Backup-Größe'; - - @override - String get backupStorageRetention => 'Speicheraufbewahrung'; + String get backupArchiveHeader => 'Kontakte, Einstellungen und Nachrichten'; @override String get backupLastBackupDate => 'Letztes Backup'; @@ -742,9 +733,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get backupLastBackupResult => 'Ergebnis'; - @override - String get backupData => 'Daten-Backup'; - @override String get backupInsecurePassword => 'Unsicheres Passwort'; @@ -779,22 +767,9 @@ class AppLocalizationsDe extends AppLocalizations { String get backupPasswordRequirement => 'Das Passwort muss mindestens 10 Zeichen lang sein.'; - @override - String get backupExpertSettings => 'Experteneinstellungen'; - @override String get backupEnableBackup => 'Automatische Sicherung aktivieren'; - @override - String get backupOwnServerDesc => - 'Speichere dein twonly Backup auf einem Server deiner Wahl.'; - - @override - String get backupUseOwnServer => 'Server verwenden'; - - @override - String get backupResetServer => 'Standardserver verwenden'; - @override String get backupTwonlySaveNow => 'Jetzt speichern'; @@ -1271,9 +1246,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get openYourOwnQRcode => 'Eigenen QR-Code öffnen'; - @override - String get skipForNow => 'Vorerst überspringen'; - @override String get finishSetupCardTitle => 'Profil vervollständigen'; @@ -1284,6 +1256,16 @@ class AppLocalizationsDe extends AppLocalizations { @override String get finishSetupCardAction => 'Setup fortsetzen'; + @override + String get missingBackupCardTitle => 'Backup einrichten'; + + @override + String get missingBackupCardDesc => + 'Wir haben den Backup-Mechanismus verbessert, weshalb du ihn erneut einrichten musst.'; + + @override + String get missingBackupCardAction => 'Jetzt einrichten'; + @override String get onboardingFinishLater => 'Später abschließen'; @@ -1714,11 +1696,37 @@ class AppLocalizationsDe extends AppLocalizations { @override String makerChangedUsername(Object maker, Object oldName, Object newName) { - return '$maker hat seinen Benutzernamen von $oldName zu $newName geändert.'; + return '$maker hat den Benutzernamen von $oldName zu $newName geändert.'; } @override String makerChangedDisplayName(Object maker, Object oldName, Object newName) { - return '$maker hat seinen Anzeigenamen von $oldName zu $newName geändert.'; + return '$maker hat den Anzeigenamen von $oldName zu $newName geändert.'; } + + @override + String get recoverErrorNoInternet => + 'Keine Internetverbindung. Bitte überprüfe deine Netzwerkverbindung und versuche es erneut.'; + + @override + String get recoverErrorUsernameNotValid => + 'Der eingegebene Benutzername ist ungültig oder existiert nicht.'; + + @override + String get recoverErrorPasswordInvalid => + 'Das eingegebene Passwort ist falsch.'; + + @override + String get recoverErrorTryAgainLater => + 'Der Server ist derzeit nicht erreichbar. Bitte versuche es später erneut.'; + + @override + String get recoverErrorUnknown => + 'Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.'; + + @override + String get recoverSuccessTitle => 'Backup erfolgreich wiederhergestellt.'; + + @override + String get recoverSuccessBody => 'Klicke hier, um die App wieder zu öffnen'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index df0a57f8..2c27966d 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -652,12 +652,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get open => 'Open'; - @override - String get createVoucher => 'Buy voucher'; - - @override - String get redeemVoucher => 'Redeem voucher'; - @override String get buy => 'Buy'; @@ -719,13 +713,10 @@ class AppLocalizationsEn extends AppLocalizations { 'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.'; @override - String get backupServer => 'Server'; + String get backupIdentityHeader => 'Identity'; @override - String get backupMaxBackupSize => 'max. backup size'; - - @override - String get backupStorageRetention => 'Storage retention'; + String get backupArchiveHeader => 'Contacts, Settings and Messages'; @override String get backupLastBackupDate => 'Last backup'; @@ -736,9 +727,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get backupLastBackupResult => 'Result'; - @override - String get backupData => 'Data-Backup'; - @override String get backupInsecurePassword => 'Insecure password'; @@ -773,22 +761,9 @@ class AppLocalizationsEn extends AppLocalizations { String get backupPasswordRequirement => 'Password must be at least 10 characters long.'; - @override - String get backupExpertSettings => 'Expert settings'; - @override String get backupEnableBackup => 'Activate automatic backup'; - @override - String get backupOwnServerDesc => - 'Save your twonly Backup at twonly or on any server of your choice.'; - - @override - String get backupUseOwnServer => 'Use server'; - - @override - String get backupResetServer => 'Use standard server'; - @override String get backupTwonlySaveNow => 'Save now'; @@ -1262,9 +1237,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get openYourOwnQRcode => 'Open your own QR code'; - @override - String get skipForNow => 'Skip for now'; - @override String get finishSetupCardTitle => 'Complete your profile'; @@ -1275,6 +1247,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get finishSetupCardAction => 'Resume Setup'; + @override + String get missingBackupCardTitle => 'Setup backup'; + + @override + String get missingBackupCardDesc => + 'We have improved the backup mechanism, which requires you to set it up again.'; + + @override + String get missingBackupCardAction => 'Set up now'; + @override String get onboardingFinishLater => 'Finish later'; @@ -1706,4 +1688,30 @@ class AppLocalizationsEn extends AppLocalizations { String makerChangedDisplayName(Object maker, Object oldName, Object newName) { return '$maker changed their display name from $oldName to $newName.'; } + + @override + String get recoverErrorNoInternet => + 'No internet connection. Please check your network and try again.'; + + @override + String get recoverErrorUsernameNotValid => + 'The username provided is not valid or does not exist.'; + + @override + String get recoverErrorPasswordInvalid => + 'The password provided is incorrect.'; + + @override + String get recoverErrorTryAgainLater => + 'The server is currently unavailable. Please try again later.'; + + @override + String get recoverErrorUnknown => + 'An unknown error occurred. Please try again.'; + + @override + String get recoverSuccessTitle => 'Backup successfully recovered.'; + + @override + String get recoverSuccessBody => 'Click here to open the app again'; } diff --git a/lib/src/localization/translations b/lib/src/localization/translations index fccd366e..9eeb6b5c 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit fccd366e119671b96730cb09d8bb8aa1057bd1c5 +Subproject commit 9eeb6b5cb46410a1616c0dbd63ce74143dfdfbbc diff --git a/lib/src/model/json/backup.model.dart b/lib/src/model/json/backup.model.dart new file mode 100644 index 00000000..e40f0738 --- /dev/null +++ b/lib/src/model/json/backup.model.dart @@ -0,0 +1,51 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'backup.model.g.dart'; + +enum LastBackupUploadState { none, pending, failed, success } + +@JsonSerializable() +class CurrentBackupStatus { + CurrentBackupStatus(); + factory CurrentBackupStatus.fromJson(Map json) => + _$CurrentBackupStatusFromJson(json); + + LastBackupUploadState identityState = LastBackupUploadState.none; + DateTime? identityLastSuccessFull; + int? identitySize; + + LastBackupUploadState archiveState = LastBackupUploadState.none; + + DateTime? archiveLastSuccessFull; + int? archiveSize; + + Map toJson() => _$CurrentBackupStatusToJson(this); +} + +enum BackupRecoveryState { + // The userId was loaded from the server and the user is asked to enter his password. + identityBackupStarted, + // -> Download identity, replace keymanager + + // Identity was downloaded and Keymanager was updated + archiveBackupStarted, + // -> Download archive, replace files, restart app +} + +@JsonSerializable() +class BackupRecovery { + BackupRecovery({ + required this.username, + required this.password, + required this.userId, + }); + + factory BackupRecovery.fromJson(Map json) => + _$BackupRecoveryFromJson(json); + + String username; + String password; + int userId; + BackupRecoveryState state = BackupRecoveryState.identityBackupStarted; + + Map toJson() => _$BackupRecoveryToJson(this); +} diff --git a/lib/src/model/json/backup.model.g.dart b/lib/src/model/json/backup.model.g.dart new file mode 100644 index 00000000..7693b0e3 --- /dev/null +++ b/lib/src/model/json/backup.model.g.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'backup.model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CurrentBackupStatus _$CurrentBackupStatusFromJson(Map json) => + CurrentBackupStatus() + ..identityState = $enumDecode( + _$LastBackupUploadStateEnumMap, + json['identityState'], + ) + ..identityLastSuccessFull = json['identityLastSuccessFull'] == null + ? null + : DateTime.parse(json['identityLastSuccessFull'] as String) + ..identitySize = (json['identitySize'] as num?)?.toInt() + ..archiveState = $enumDecode( + _$LastBackupUploadStateEnumMap, + json['archiveState'], + ) + ..archiveLastSuccessFull = json['archiveLastSuccessFull'] == null + ? null + : DateTime.parse(json['archiveLastSuccessFull'] as String) + ..archiveSize = (json['archiveSize'] as num?)?.toInt(); + +Map _$CurrentBackupStatusToJson( + CurrentBackupStatus instance, +) => { + 'identityState': _$LastBackupUploadStateEnumMap[instance.identityState]!, + 'identityLastSuccessFull': instance.identityLastSuccessFull + ?.toIso8601String(), + 'identitySize': instance.identitySize, + 'archiveState': _$LastBackupUploadStateEnumMap[instance.archiveState]!, + 'archiveLastSuccessFull': instance.archiveLastSuccessFull?.toIso8601String(), + 'archiveSize': instance.archiveSize, +}; + +const _$LastBackupUploadStateEnumMap = { + LastBackupUploadState.none: 'none', + LastBackupUploadState.pending: 'pending', + LastBackupUploadState.failed: 'failed', + LastBackupUploadState.success: 'success', +}; + +BackupRecovery _$BackupRecoveryFromJson(Map json) => + BackupRecovery( + username: json['username'] as String, + password: json['password'] as String, + userId: (json['userId'] as num).toInt(), + )..state = $enumDecode(_$BackupRecoveryStateEnumMap, json['state']); + +Map _$BackupRecoveryToJson(BackupRecovery instance) => + { + 'username': instance.username, + 'password': instance.password, + 'userId': instance.userId, + 'state': _$BackupRecoveryStateEnumMap[instance.state]!, + }; + +const _$BackupRecoveryStateEnumMap = { + BackupRecoveryState.identityBackupStarted: 'identityBackupStarted', + BackupRecoveryState.archiveBackupStarted: 'archiveBackupStarted', +}; diff --git a/lib/src/model/json/userdata.model.dart b/lib/src/model/json/userdata.model.dart index bf1babaa..e39fac5e 100644 --- a/lib/src/model/json/userdata.model.dart +++ b/lib/src/model/json/userdata.model.dart @@ -133,10 +133,15 @@ class UserData { // --- BACKUP --- - DateTime? nextTimeToShowBackupNotice; - BackupServer? backupServer; + @Deprecated('Use the secure storage in rust') TwonlySafeBackup? twonlySafeBackup; + @JsonKey(defaultValue: false) + bool isBackupEnabled = false; + + // Used for push notifcation via FCM. + String? fcmToken; + // For my master thesis I want to create a anonymous user study: // - users in the "Tester" Plan can, if they want, take part of the user study @@ -178,19 +183,3 @@ class TwonlySafeBackup { List encryptionKey; Map toJson() => _$TwonlySafeBackupToJson(this); } - -@JsonSerializable() -class BackupServer { - BackupServer({ - required this.serverUrl, - required this.retentionDays, - required this.maxBackupBytes, - }); - factory BackupServer.fromJson(Map json) => - _$BackupServerFromJson(json); - - String serverUrl; - int retentionDays; - int maxBackupBytes; - Map toJson() => _$BackupServerToJson(this); -} diff --git a/lib/src/model/json/userdata.model.g.dart b/lib/src/model/json/userdata.model.g.dart index 3b028b00..10a70f94 100644 --- a/lib/src/model/json/userdata.model.g.dart +++ b/lib/src/model/json/userdata.model.g.dart @@ -71,6 +71,8 @@ UserData _$UserDataFromJson(Map json) => json['userDiscoveryRequiresManualApproval'] as bool? ?? false ..userDiscoverySharePromotion = json['userDiscoverySharePromotion'] as bool? ?? true + ..userDiscoveryInitializationError = + json['userDiscoveryInitializationError'] as bool? ?? false ..currentPreKeyIndexStart = (json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000 ..currentSignedPreKeyIndexStart = @@ -80,17 +82,15 @@ UserData _$UserDataFromJson(Map json) => .toList() ..hideChangeLog = json['hideChangeLog'] as bool? ?? true ..updateFCMToken = json['updateFCMToken'] as bool? ?? true - ..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null - ? null - : DateTime.parse(json['nextTimeToShowBackupNotice'] as String) - ..backupServer = json['backupServer'] == null - ? null - : BackupServer.fromJson(json['backupServer'] as Map) + ..canUseLoginTokenForAuth = + json['canUseLoginTokenForAuth'] as bool? ?? true ..twonlySafeBackup = json['twonlySafeBackup'] == null ? null : TwonlySafeBackup.fromJson( json['twonlySafeBackup'] as Map, ) + ..isBackupEnabled = json['isBackupEnabled'] as bool? ?? false + ..fcmToken = json['fcmToken'] as String? ..askedForUserStudyPermission = json['askedForUserStudyPermission'] as bool? ?? false ..userStudyParticipantsToken = @@ -142,15 +142,16 @@ Map _$UserDataToJson(UserData instance) => { 'userDiscoveryRequiresManualApproval': instance.userDiscoveryRequiresManualApproval, 'userDiscoverySharePromotion': instance.userDiscoverySharePromotion, + 'userDiscoveryInitializationError': instance.userDiscoveryInitializationError, 'currentPreKeyIndexStart': instance.currentPreKeyIndexStart, 'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart, 'lastChangeLogHash': instance.lastChangeLogHash, 'hideChangeLog': instance.hideChangeLog, 'updateFCMToken': instance.updateFCMToken, - 'nextTimeToShowBackupNotice': instance.nextTimeToShowBackupNotice - ?.toIso8601String(), - 'backupServer': instance.backupServer, + 'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth, 'twonlySafeBackup': instance.twonlySafeBackup, + 'isBackupEnabled': instance.isBackupEnabled, + 'fcmToken': instance.fcmToken, 'askedForUserStudyPermission': instance.askedForUserStudyPermission, 'userStudyParticipantsToken': instance.userStudyParticipantsToken, 'userStudyCountNewFriendsViaSuggestion': @@ -201,16 +202,3 @@ const _$LastBackupUploadStateEnumMap = { LastBackupUploadState.failed: 'failed', LastBackupUploadState.success: 'success', }; - -BackupServer _$BackupServerFromJson(Map json) => BackupServer( - serverUrl: json['serverUrl'] as String, - retentionDays: (json['retentionDays'] as num).toInt(), - maxBackupBytes: (json['maxBackupBytes'] as num).toInt(), -); - -Map _$BackupServerToJson(BackupServer instance) => - { - 'serverUrl': instance.serverUrl, - 'retentionDays': instance.retentionDays, - 'maxBackupBytes': instance.maxBackupBytes, - }; diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart index 9d470c28..dc424110 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart @@ -468,6 +468,64 @@ class Handshake_GetAuthChallenge extends $pb.GeneratedMessage { static Handshake_GetAuthChallenge? _defaultInstance; } +class Handshake_GetUserIdByUsername extends $pb.GeneratedMessage { + factory Handshake_GetUserIdByUsername({ + $core.String? username, + }) { + final result = create(); + if (username != null) result.username = username; + return result; + } + + Handshake_GetUserIdByUsername._(); + + factory Handshake_GetUserIdByUsername.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory Handshake_GetUserIdByUsername.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Handshake.GetUserIdByUsername', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'username') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Handshake_GetUserIdByUsername clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Handshake_GetUserIdByUsername copyWith( + void Function(Handshake_GetUserIdByUsername) updates) => + super.copyWith( + (message) => updates(message as Handshake_GetUserIdByUsername)) + as Handshake_GetUserIdByUsername; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Handshake_GetUserIdByUsername create() => + Handshake_GetUserIdByUsername._(); + @$core.override + Handshake_GetUserIdByUsername createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static Handshake_GetUserIdByUsername getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static Handshake_GetUserIdByUsername? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get username => $_getSZ(0); + @$pb.TagNumber(1) + set username($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasUsername() => $_has(0); + @$pb.TagNumber(1) + void clearUsername() => $_clearField(1); +} + class Handshake_GetAuthToken extends $pb.GeneratedMessage { factory Handshake_GetAuthToken({ $fixnum.Int64? userId, @@ -758,6 +816,7 @@ enum Handshake_Handshake { authenticate, requestPOW, authenticateWithLoginToken, + getUseridByUsername, notSet } @@ -769,6 +828,7 @@ class Handshake extends $pb.GeneratedMessage { Handshake_Authenticate? authenticate, Handshake_RequestPOW? requestPOW, Handshake_AuthenticateWithLoginToken? authenticateWithLoginToken, + Handshake_GetUserIdByUsername? getUseridByUsername, }) { final result = create(); if (register != null) result.register = register; @@ -778,6 +838,8 @@ class Handshake extends $pb.GeneratedMessage { if (requestPOW != null) result.requestPOW = requestPOW; if (authenticateWithLoginToken != null) result.authenticateWithLoginToken = authenticateWithLoginToken; + if (getUseridByUsername != null) + result.getUseridByUsername = getUseridByUsername; return result; } @@ -798,6 +860,7 @@ class Handshake extends $pb.GeneratedMessage { 4: Handshake_Handshake.authenticate, 5: Handshake_Handshake.requestPOW, 6: Handshake_Handshake.authenticateWithLoginToken, + 7: Handshake_Handshake.getUseridByUsername, 0: Handshake_Handshake.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -805,7 +868,7 @@ class Handshake extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4, 5, 6]) + ..oo(0, [1, 2, 3, 4, 5, 6, 7]) ..aOM(1, _omitFieldNames ? '' : 'register', subBuilder: Handshake_Register.create) ..aOM( @@ -821,6 +884,9 @@ class Handshake extends $pb.GeneratedMessage { ..aOM( 6, _omitFieldNames ? '' : 'authenticateWithLoginToken', subBuilder: Handshake_AuthenticateWithLoginToken.create) + ..aOM( + 7, _omitFieldNames ? '' : 'getUseridByUsername', + subBuilder: Handshake_GetUserIdByUsername.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -847,6 +913,7 @@ class Handshake extends $pb.GeneratedMessage { @$pb.TagNumber(4) @$pb.TagNumber(5) @$pb.TagNumber(6) + @$pb.TagNumber(7) Handshake_Handshake whichHandshake() => _Handshake_HandshakeByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @@ -855,6 +922,7 @@ class Handshake extends $pb.GeneratedMessage { @$pb.TagNumber(4) @$pb.TagNumber(5) @$pb.TagNumber(6) + @$pb.TagNumber(7) void clearHandshake() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -926,6 +994,18 @@ class Handshake extends $pb.GeneratedMessage { @$pb.TagNumber(6) Handshake_AuthenticateWithLoginToken ensureAuthenticateWithLoginToken() => $_ensure(5); + + @$pb.TagNumber(7) + Handshake_GetUserIdByUsername get getUseridByUsername => $_getN(6); + @$pb.TagNumber(7) + set getUseridByUsername(Handshake_GetUserIdByUsername value) => + $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasGetUseridByUsername() => $_has(6); + @$pb.TagNumber(7) + void clearGetUseridByUsername() => $_clearField(7); + @$pb.TagNumber(7) + Handshake_GetUserIdByUsername ensureGetUseridByUsername() => $_ensure(6); } class ApplicationData_TextMessage extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart index c51f8c2b..9ea3c1f3 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart @@ -143,11 +143,21 @@ const Handshake$json = { '9': 0, '10': 'authenticateWithLoginToken' }, + { + '1': 'get_userid_by_username', + '3': 7, + '4': 1, + '5': 11, + '6': '.client_to_server.Handshake.GetUserIdByUsername', + '9': 0, + '10': 'getUseridByUsername' + }, ], '3': [ Handshake_RequestPOW$json, Handshake_Register$json, Handshake_GetAuthChallenge$json, + Handshake_GetUserIdByUsername$json, Handshake_GetAuthToken$json, Handshake_Authenticate$json, Handshake_AuthenticateWithLoginToken$json @@ -217,6 +227,14 @@ const Handshake_GetAuthChallenge$json = { '1': 'GetAuthChallenge', }; +@$core.Deprecated('Use handshakeDescriptor instead') +const Handshake_GetUserIdByUsername$json = { + '1': 'GetUserIdByUsername', + '2': [ + {'1': 'username', '3': 1, '4': 1, '5': 9, '10': 'username'}, + ], +}; + @$core.Deprecated('Use handshakeDescriptor instead') const Handshake_GetAuthToken$json = { '1': 'GetAuthToken', @@ -296,25 +314,28 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode( 'CgpyZXF1ZXN0UE9XGAUgASgLMiYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuUmVxdWVzdF' 'BPV0gAUgpyZXF1ZXN0UE9XEnsKHWF1dGhlbnRpY2F0ZV93aXRoX2xvZ2luX3Rva2VuGAYgASgL' 'MjYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW' - '5IAFIaYXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW4aDAoKUmVxdWVzdFBPVxrKAwoIUmVnaXN0' - 'ZXISGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGAIgASgJSABSCm' - 'ludml0ZUNvZGWIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVibGljSWRlbnRp' - 'dHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcm' - 'VrZXlfc2lnbmF0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQc2lnbmVkX3By' - 'ZWtleV9pZBgGIAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2lkGAcgASgDUg' - '5yZWdpc3RyYXRpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdfY29kZRgJIAEo' - 'CVIIbGFuZ0NvZGUSIgoNcHJvb2Zfb2Zfd29yaxgKIAEoA1ILcHJvb2ZPZldvcmsSJAoLbG9naW' - '5fdG9rZW4YCyABKAxIAVIKbG9naW5Ub2tlbogBAUIOCgxfaW52aXRlX2NvZGVCDgoMX2xvZ2lu' - 'X3Rva2VuGhIKEEdldEF1dGhDaGFsbGVuZ2UaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYAS' - 'ABKANSBnVzZXJJZBIaCghyZXNwb25zZRgCIAEoDFIIcmVzcG9uc2Ua6AEKDEF1dGhlbnRpY2F0' - 'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2' - 'VuEiQKC2FwcF92ZXJzaW9uGAMgASgJSABSCmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQg' - 'ASgDSAFSCGRldmljZUlkiAEBEigKDWluX2JhY2tncm91bmQYBSABKAhIAlIMaW5CYWNrZ3JvdW' - '5kiAEBQg4KDF9hcHBfdmVyc2lvbkIMCgpfZGV2aWNlX2lkQhAKDl9pbl9iYWNrZ3JvdW5kGsYB' - 'ChpBdXRoZW50aWNhdGVXaXRoTG9naW5Ub2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSLA' - 'oSc2VjcmV0X2xvZ2luX3Rva2VuGAIgASgMUhBzZWNyZXRMb2dpblRva2VuEh8KC2FwcF92ZXJz' - 'aW9uGAMgASgJUgphcHBWZXJzaW9uEhsKCWRldmljZV9pZBgEIAEoA1IIZGV2aWNlSWQSIwoNaW' - '5fYmFja2dyb3VuZBgFIAEoCFIMaW5CYWNrZ3JvdW5kQgsKCUhhbmRzaGFrZQ=='); + '5IAFIaYXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW4SZgoWZ2V0X3VzZXJpZF9ieV91c2VybmFt' + 'ZRgHIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFVzZXJJZEJ5VXNlcm5hbW' + 'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRoMCgpSZXF1ZXN0UE9XGsoDCghSZWdpc3RlchIaCgh1' + 'c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUSJAoLaW52aXRlX2NvZGUYAiABKAlIAFIKaW52aXRlQ2' + '9kZYgBARIuChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgASgMUhFwdWJsaWNJZGVudGl0eUtleRIj' + 'Cg1zaWduZWRfcHJla2V5GAQgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaW' + 'duYXR1cmUYBSABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lk' + 'GAYgASgDUg5zaWduZWRQcmVrZXlJZBInCg9yZWdpc3RyYXRpb25faWQYByABKANSDnJlZ2lzdH' + 'JhdGlvbklkEhUKBmlzX2lvcxgIIAEoCFIFaXNJb3MSGwoJbGFuZ19jb2RlGAkgASgJUghsYW5n' + 'Q29kZRIiCg1wcm9vZl9vZl93b3JrGAogASgDUgtwcm9vZk9mV29yaxIkCgtsb2dpbl90b2tlbh' + 'gLIAEoDEgBUgpsb2dpblRva2VuiAEBQg4KDF9pbnZpdGVfY29kZUIOCgxfbG9naW5fdG9rZW4a' + 'EgoQR2V0QXV0aENoYWxsZW5nZRoxChNHZXRVc2VySWRCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA' + 'EgASgJUgh1c2VybmFtZRpDCgxHZXRBdXRoVG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklk' + 'EhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25zZRroAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaW' + 'QYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3Zl' + 'cnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogBARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aW' + 'NlSWSIAQESKAoNaW5fYmFja2dyb3VuZBgFIAEoCEgCUgxpbkJhY2tncm91bmSIAQFCDgoMX2Fw' + 'cF92ZXJzaW9uQgwKCl9kZXZpY2VfaWRCEAoOX2luX2JhY2tncm91bmQaxgEKGkF1dGhlbnRpY2' + 'F0ZVdpdGhMb2dpblRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIsChJzZWNyZXRfbG9n' + 'aW5fdG9rZW4YAiABKAxSEHNlY3JldExvZ2luVG9rZW4SHwoLYXBwX3ZlcnNpb24YAyABKAlSCm' + 'FwcFZlcnNpb24SGwoJZGV2aWNlX2lkGAQgASgDUghkZXZpY2VJZBIjCg1pbl9iYWNrZ3JvdW5k' + 'GAUgASgIUgxpbkJhY2tncm91bmRCCwoJSGFuZHNoYWtl'); @$core.Deprecated('Use applicationDataDescriptor instead') const ApplicationData$json = { diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart index cccb4c4c..648ba97f 100644 --- a/lib/src/providers/routing.provider.dart +++ b/lib/src/providers/routing.provider.dart @@ -16,7 +16,6 @@ import 'package:twonly/src/visual/views/onboarding/recover.view.dart'; import 'package:twonly/src/visual/views/public_profile.view.dart'; import 'package:twonly/src/visual/views/settings/account.view.dart'; import 'package:twonly/src/visual/views/settings/appearance.view.dart'; -import 'package:twonly/src/visual/views/settings/backup/backup_server.view.dart'; import 'package:twonly/src/visual/views/settings/backup/backup_settings.view.dart'; import 'package:twonly/src/visual/views/settings/backup/backup_setup.view.dart'; import 'package:twonly/src/visual/views/settings/chat/chat_reactions.view.dart'; @@ -165,10 +164,6 @@ final routerProvider = GoRouter( path: 'backup', builder: (context, state) => const BackupView(), routes: [ - GoRoute( - path: 'server', - builder: (context, state) => const BackupServerView(), - ), GoRoute( path: 'recovery', builder: (context, state) => const BackupRecoveryView(), diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index a53e2c05..035a6e02 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -32,7 +32,7 @@ import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/api/server_messages.api.dart'; import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/flame.service.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; @@ -61,6 +61,8 @@ class ApiService { // final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu'; final String apiSecure = kReleaseMode ? 's' : 's'; + String get apiEndpoint => 'http$apiSecure://$apiHost/api/'; + final _planUpdateController = StreamController.broadcast(); Stream get onPlanUpdated => _planUpdateController.stream; @@ -123,7 +125,7 @@ class ApiService { twonlyDB.markUpdated(); unawaited(syncFlameCounters()); unawaited(setupNotificationWithUsers()); - unawaited(signalHandleNewServerConnection()); + unawaited(SignalIdentityService.onAuthenticated()); resetResyncedUsers(); resetUserDiscoveryRequestUpdates(); unawaited(fetchGroupStatesForUnjoinedGroups()); @@ -454,7 +456,7 @@ class ApiService { try { Log.info('Switching authentication to login token'); - final loginToken = await FlutterKeyManager.getLoginToken(); + final loginToken = await RustKeyManager.getLoginToken(); final res = await _setLoginToken(loginToken); if (res.isSuccess) { Log.info('Switch was successfully.'); @@ -484,7 +486,7 @@ class ApiService { Future tryAuthenticateWithLoginToken() async { try { - final loginToken = await FlutterKeyManager.getLoginToken(); + final loginToken = await RustKeyManager.getLoginToken(); final authenticate = Handshake_AuthenticateWithLoginToken() ..userId = Int64(userService.currentUser.userId) @@ -527,8 +529,7 @@ class ApiService { return lockAuthentication.protect(() async { if (isAuthenticated) return; - if (await getSignalIdentity() == null) { - Log.error('Signal identity not found.'); + if (!userService.isUserCreated) { return; } @@ -605,7 +606,7 @@ class ApiService { final signedPreKey = (await signalStore.loadSignedPreKeys())[0]; - final loginToken = await FlutterKeyManager.getLoginToken(); + final loginToken = await RustKeyManager.getLoginToken(); final register = Handshake_Register() ..username = username @@ -690,6 +691,21 @@ class ApiService { return sendRequestSync(req); } + Future getUserIdFromUsername(String username) async { + final appData = Handshake( + getUseridByUsername: Handshake_GetUserIdByUsername(username: username), + ); + final req = createClientToServerFromHandshake(appData); + final res = await sendRequestSync(req); + if (res.isSuccess) { + final ok = res.value as server.Response_Ok; + if (ok.hasUserid()) { + return ok.userid.toInt(); + } + } + return null; + } + Future getUserData(String username) async { final get = ApplicationData_GetUserByUsername()..username = username; final appData = ApplicationData()..getUserByUsername = get; diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart index 2d300bcd..abef0df7 100644 --- a/lib/src/services/api/client2client/contact.c2c.dart +++ b/lib/src/services/api/client2client/contact.c2c.dart @@ -143,8 +143,8 @@ Future handleContactUpdate( groupId: Value(group.groupId), type: const Value(GroupActionType.updatedContactUsername), contactId: Value(fromUserId), - oldGroupName: Value('@${contact.username}'), - newGroupName: Value('@${contactUpdate.username}'), + oldGroupName: Value(contact.username), + newGroupName: Value(contactUpdate.username), ), ); } @@ -157,7 +157,7 @@ Future handleContactUpdate( groupId: Value(group.groupId), type: const Value(GroupActionType.updatedContactDisplayName), contactId: Value(fromUserId), - oldGroupName: Value(contact.displayName ?? ''), + oldGroupName: Value(contact.displayName ?? contact.username), newGroupName: Value(contactUpdate.displayName), ), ); diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index cf3be4a0..f9fb9a1e 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -8,7 +8,7 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/api/utils.api.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/utils/log.dart'; Future handleGroupCreate( diff --git a/lib/src/services/api/mediafiles/media_background.api.dart b/lib/src/services/api/mediafiles/media_background.api.dart index 7de58a96..d736e2d1 100644 --- a/lib/src/services/api/mediafiles/media_background.api.dart +++ b/lib/src/services/api/mediafiles/media_background.api.dart @@ -8,7 +8,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/download.api.dart'; import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; +import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -22,8 +22,11 @@ Future initFileDownloader() async { if (update.task.taskId.contains('download_')) { await handleDownloadStatusUpdate(update); } - if (update.task.taskId.contains('backup')) { - await handleBackupStatusUpdate(update); + if (update.task.taskId.contains('backup_')) { + await BackupService.handleBackupStatusUpdate( + update.task.taskId, + update, + ); } case TaskProgressUpdate(): Log.info( diff --git a/lib/src/services/api/messages.api.dart b/lib/src/services/api/messages.api.dart index 93885682..4024635e 100644 --- a/lib/src/services/api/messages.api.dart +++ b/lib/src/services/api/messages.api.dart @@ -69,6 +69,8 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ bool blocking = true, bool useLock = true, }) async { + if (apiService.appIsOutdated) return null; + try { if (receiptId == null && receipt == null) return null; if (receipt == null) { diff --git a/lib/src/services/api/server_messages.api.dart b/lib/src/services/api/server_messages.api.dart index b3f68a11..a68ab401 100644 --- a/lib/src/services/api/server_messages.api.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -28,7 +28,7 @@ import 'package:twonly/src/services/api/client2client/reaction.c2c.dart'; import 'package:twonly/src/services/api/client2client/text_message.c2c.dart'; import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart'; import 'package:twonly/src/services/api/messages.api.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/services/key_verification.service.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart'; diff --git a/lib/src/services/api/utils.api.dart b/lib/src/services/api/utils.api.dart index ad68abd2..c3f9a459 100644 --- a/lib/src/services/api/utils.api.dart +++ b/lib/src/services/api/utils.api.dart @@ -118,7 +118,7 @@ Future?> getAuthenticationHeader() async { var headers = {}; if (userService.currentUser.canUseLoginTokenForAuth) { - final loginToken = await FlutterKeyManager.getLoginToken(); + final loginToken = await RustKeyManager.getLoginToken(); headers = { 'x-twonly-user-id': userService.currentUser.userId diff --git a/lib/src/services/backup.service.dart b/lib/src/services/backup.service.dart new file mode 100644 index 00000000..b84a66d0 --- /dev/null +++ b/lib/src/services/backup.service.dart @@ -0,0 +1,361 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:clock/clock.dart' as clock; +import 'package:http/http.dart' as http; +import 'package:mutex/mutex.dart'; +import 'package:twonly/core/bridge.dart' as bridge; +import 'package:twonly/core/bridge/wrapper/backup.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/keyvalue.keys.dart'; +import 'package:twonly/src/model/json/backup.model.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; +import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/utils/storage.dart'; + +class BackupService { + static final Mutex _protected = Mutex(); + + static String _getIdentityBackupUrl(String backupId) => + '${apiService.apiEndpoint}/backup/identity/$backupId'; + + static String _getArchiveBackupUrl(String backupDownloadToken, int? userId) => + '${apiService.apiEndpoint}/backup/archive/${userId == null ? '' : '${userId.toRadixString(16).padLeft(16, '0').toUpperCase()}/'}$backupDownloadToken'; + + static final _backupUpdateController = StreamController.broadcast(); + static Stream get onBackupUpdated => _backupUpdateController.stream; + + static Future getData() async { + return CurrentBackupStatus.fromJson( + (await KeyValueStore.get(KeyValueKeys.currentBackupState)) ?? + CurrentBackupStatus().toJson(), + ); + } + + static Future updateBackupPassword(String password) async { + // Set or reset the backup data... + await KeyValueStore.put( + KeyValueKeys.currentBackupState, + CurrentBackupStatus().toJson(), + ); + _backupUpdateController.add(null); + + await RustBackupIdentity.setBackupPasswordKeys( + password: password, + // Using the userId is this will never change in a users lifecycle + userId: userService.currentUser.userId, + ); + + await UserService.update((u) => u.isBackupEnabled = true); + + unawaited(makeBackup(force: true)); + } + + static Future handleBackupStatusUpdate( + String taskId, + TaskStatusUpdate update, + ) async { + var status = LastBackupUploadState.success; + + if (update.status == TaskStatus.failed || + update.status == TaskStatus.canceled) { + status = LastBackupUploadState.failed; + } else if (update.status != TaskStatus.complete) { + Log.info('Backup is in state: ${update.status}'); + return; + } + await _protected.protect(() async { + final backup = await getData(); + if (taskId == 'backup_identity') { + backup + ..identityLastSuccessFull = clock.clock.now() + ..identityState = status; + } else { + backup + ..archiveLastSuccessFull = clock.clock.now() + ..archiveState = status; + } + await KeyValueStore.put( + KeyValueKeys.currentBackupState, + backup.toJson(), + ); + _backupUpdateController.add(null); + }); + } + + static Future makeBackup({bool force = false}) async { + await _protected.protect(() async { + final backup = await getData(); + + final lastDay = clock.clock.now().subtract(const Duration(days: 1)); + final lastWeek = clock.clock.now().subtract(const Duration(days: 7)); + + if (force || + backup.identityLastSuccessFull == null || + (backup.identityState != LastBackupUploadState.pending && + backup.identityLastSuccessFull!.isBefore(lastWeek) || + backup.identityLastSuccessFull!.isBefore( + lastWeek.subtract(const Duration(days: 1)), + ))) { + Log.info('Performing a identity backup.'); + final encryptedBackup = + await RustBackupIdentity.getIdentityBackupBytes(); + + final backupTempFile = File( + '${AppEnvironment.cacheDir}/identity_backup.bin', + )..writeAsBytesSync(encryptedBackup); + + Log.info( + 'Identity backup has a size of ${backupTempFile.statSync().size}.', + ); + + final backupId = await RustBackupIdentity.getBackupId(); + if (backupId == null) { + Log.error('Got empty backup id.'); + backup.identityState = LastBackupUploadState.failed; + } else { + final task = UploadTask.fromFile( + taskId: 'backup_identity', + httpRequestMethod: 'PUT', + file: backupTempFile, + url: _getIdentityBackupUrl(backupId), + post: 'binary', + retries: 2, + headers: { + 'Content-Type': 'application/octet-stream', + }, + ); + if (await FileDownloader().enqueue(task)) { + Log.info('Starting upload from backup identity.'); + backup + ..identityState = LastBackupUploadState.pending + ..identityLastSuccessFull = clock.clock.now() + ..identitySize = encryptedBackup.length; + await KeyValueStore.put( + KeyValueKeys.currentBackupState, + backup.toJson(), + ); + _backupUpdateController.add(null); + } else { + Log.error('Error starting upload task for backup identity.'); + } + } + } + + if (force || + backup.archiveLastSuccessFull == null || + (backup.archiveState != LastBackupUploadState.pending && + backup.archiveLastSuccessFull!.isBefore(lastDay) || + backup.archiveLastSuccessFull!.isBefore( + lastDay.subtract(const Duration(days: 1)), + ))) { + Log.info('Creating a archive backup.'); + late final String backupArchive; + late final String backupDownloadToken; + try { + (backupDownloadToken, backupArchive) = + await RustBackupArchive.createBackupArchive(); + } catch (e) { + Log.error(e); + return; + } + Log.info( + 'Archive backup has a size of ${File(backupArchive).statSync().size}.', + ); + + final headers = await getAuthenticationHeader(); + if (headers == null) { + Log.error('Auth headers are empty. Returning'); + return; + } + + final task = UploadTask.fromFile( + taskId: 'backup_archive', + file: File(backupArchive), + url: _getArchiveBackupUrl(backupDownloadToken, null), + priority: 0, + retries: 10, + headers: headers, + ); + if (await FileDownloader().enqueue(task)) { + Log.info('Uploading backup archive.'); + backup + ..archiveState = LastBackupUploadState.pending + ..archiveLastSuccessFull = clock.clock.now() + ..archiveSize = File(backupArchive).statSync().size; + await KeyValueStore.put( + KeyValueKeys.currentBackupState, + backup.toJson(), + ); + _backupUpdateController.add(null); + } else { + Log.error('Error starting upload task for backup archive.'); + } + } + }); + } + + static Future getBackupRecoveryData() async { + final stateJson = await KeyValueStore.get(KeyValueKeys.backupRecoveryState); + if (stateJson == null) return null; + return BackupRecovery.fromJson(stateJson); + } + + static Future _nextBackupStage() async { + return _protected.protect(() async { + final recoveryData = await getBackupRecoveryData(); + if (recoveryData == null) return null; + + if (recoveryData.state == BackupRecoveryState.identityBackupStarted) { + // First start to download the identity to restore the KeyManager + final backupKeys = await RustBackupIdentity.getBackupPasswordKeys( + userId: recoveryData.userId, + password: recoveryData.password, + ); + final backupId = uint8ListToHex(backupKeys.backupId); + final backupServerUrl = _getIdentityBackupUrl(backupId); + final (encryptedBytes, error) = await _downloadBackup(backupServerUrl); + if (error != null || encryptedBytes == null) { + Log.error(error); + return error; + } + + Log.info('Restored identity.'); + + try { + await RustBackupIdentity.restoreIdentityBackup( + keys: backupKeys, + encryptedBytes: encryptedBytes, + ); + recoveryData.state = BackupRecoveryState.archiveBackupStarted; + await KeyValueStore.put( + KeyValueKeys.backupRecoveryState, + recoveryData.toJson(), + ); + _backupUpdateController.add(null); + } catch (e) { + Log.error(e); + return RecoveryError.unkownError; + } + } + + if (recoveryData.state == BackupRecoveryState.archiveBackupStarted) { + // The KeyManager was restored sucessfully, restore the archive now. + try { + final downloadToken = + await RustBackupArchive.getBackupDownloadToken(); + if (downloadToken == null) { + // identity was not restored correctly try this again. + recoveryData.state = BackupRecoveryState.identityBackupStarted; + await KeyValueStore.put( + KeyValueKeys.backupRecoveryState, + recoveryData.toJson(), + ); + return RecoveryError.tryAgainLater; + } + + final backupServerUrl = _getArchiveBackupUrl( + downloadToken, + recoveryData.userId, + ); + final backupArchive = await _downloadBackup(backupServerUrl); + if (backupArchive.$2 != null || backupArchive.$1 == null) { + return backupArchive.$2; + } + + final archiveFile = File('${AppEnvironment.cacheDir}/archive.bin') + ..writeAsBytesSync(backupArchive.$1!); + + await RustBackupArchive.restoreBackupArchive( + filePath: archiveFile.path, + ); + await UserService.update((u) { + u.deviceId += 1; + }); + await KeyValueStore.delete( + KeyValueKeys.backupRecoveryState, + ); + } catch (e) { + Log.error(e); + return RecoveryError.unkownError; + } + } + + return null; + }); + } + + static Future startFullBackupRecovery( + String username, + String password, + ) async { + final userId = await apiService.getUserIdFromUsername(username); + if (userId == null) { + return RecoveryError.usernameNotValid; + } + + final state = BackupRecovery( + username: username, + userId: userId, + password: password, + ); + + await deleteLocalUserData(); + try { + await bridge.initializeTwonlyFlutter( + config: bridge.InitConfig( + databaseDir: AppEnvironment.supportDir, + dataDir: AppEnvironment.supportDir, + ), + ); + } catch (e) { + Log.error(e); + return RecoveryError.unkownError; + } + await KeyValueStore.put(KeyValueKeys.backupRecoveryState, state.toJson()); + return _nextBackupStage(); + } + + static Future<(Uint8List?, RecoveryError?)> _downloadBackup( + String backupServerUrl, + ) async { + late http.Response response; + + try { + response = await http.get( + Uri.parse(backupServerUrl), + headers: { + HttpHeaders.acceptHeader: 'application/octet-stream', + }, + ); + } catch (e) { + Log.error('Error fetching backup: $e'); + return (null, RecoveryError.noInternet); + } + + Log.warn('Backup downlaod status: ${response.statusCode}'); + + switch (response.statusCode) { + case 200: + return (response.bodyBytes, null); + case 404: + return (null, RecoveryError.passwordInvalid); + default: + return (null, RecoveryError.tryAgainLater); + } + } +} + +enum RecoveryError { + usernameNotValid, + passwordInvalid, + tryAgainLater, + noInternet, + unkownError, +} diff --git a/lib/src/services/backup/common.backup.dart b/lib/src/services/backup/common.backup.dart deleted file mode 100644 index 0751a0c1..00000000 --- a/lib/src/services/backup/common.backup.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:drift/drift.dart'; -import 'package:hashlib/hashlib.dart'; -import 'package:http/http.dart' as http; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; -import 'package:twonly/src/services/user.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; - -Future enableTwonlySafe(String password) async { - final (backupId, encryptionKey) = await getMasterKey( - password, - userService.currentUser.username, - ); - - await UserService.update((user) { - user.twonlySafeBackup = TwonlySafeBackup( - encryptionKey: encryptionKey, - backupId: backupId, - ); - }); - unawaited(performTwonlySafeBackup(force: true)); -} - -Future removeTwonlySafeFromServer() async { - final serverUrl = getTwonlySafeBackupUrl(); - if (serverUrl == null) { - Log.error('Could not remove twonly safe as serverUrl is null'); - return; - } - try { - final response = await http.delete( - Uri.parse(serverUrl), - headers: { - 'Content-Type': 'application/json', // Set the content type if needed - // Add any other headers if required - }, - ); - Log.info('Download deleted with: ${response.statusCode}'); - } catch (e) { - Log.error('Could not connect upload the backup.'); - } -} - -Future<(Uint8List, Uint8List)> getMasterKey( - String password, - String username, -) async { - final List passwordBytes = utf8.encode(password); - final List saltBytes = utf8.encode(username); - - // Values are derived from the Threema Whitepaper - // https://threema.com/assets/documents/cryptography_whitepaper.pdf - - final scrypt = Scrypt( - cost: 65536, - salt: saltBytes, - ); - - final key = scrypt.convert(passwordBytes).bytes; - return (key.sublist(0, 32), key.sublist(32, 64)); -} - -String? getTwonlySafeBackupUrl() { - if (userService.currentUser.twonlySafeBackup == null) return null; - return getTwonlySafeBackupUrlFromServer( - userService.currentUser.twonlySafeBackup!.backupId, - userService.currentUser.backupServer, - ); -} - -String? getTwonlySafeBackupUrlFromServer( - List backupId, - BackupServer? backupServer, -) { - var backupServerUrl = 'https://safe.twonly.eu/'; - - if (backupServer != null) { - backupServerUrl = backupServer.serverUrl; - } - - final backupIdHex = uint8ListToHex(backupId).toLowerCase(); - - return '${backupServerUrl}backups/$backupIdHex'; -} diff --git a/lib/src/services/backup/create.backup.dart b/lib/src/services/backup/create.backup.dart deleted file mode 100644 index f633677c..00000000 --- a/lib/src/services/backup/create.backup.dart +++ /dev/null @@ -1,238 +0,0 @@ -// ignore_for_file: parameter_assignments - -import 'dart:convert'; -import 'dart:io'; - -import 'package:background_downloader/background_downloader.dart'; -import 'package:clock/clock.dart'; -import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; -import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; -import 'package:path/path.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/constants/secure_storage.keys.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart'; -import 'package:twonly/src/services/backup/common.backup.dart'; -import 'package:twonly/src/services/user.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/secure_storage.dart'; - -Future performTwonlySafeBackup({bool force = false}) async { - if (userService.currentUser.twonlySafeBackup == null) { - return; - } - - if (userService.currentUser.twonlySafeBackup!.backupUploadState == - LastBackupUploadState.pending) { - Log.warn('Backup upload is already pending.'); - return; - } - - final lastUpdateTime = - userService.currentUser.twonlySafeBackup!.lastBackupDone; - if (!force && lastUpdateTime != null) { - if (lastUpdateTime.isAfter(clock.now().subtract(const Duration(days: 1)))) { - return; - } - } - - Log.info('Starting new twonly Backup!'); - - final backupDir = Directory( - join(AppEnvironment.supportDir, 'backup_twonly_safe/'), - ); - await backupDir.create(recursive: true); - - final backupDatabaseFile = File(join(backupDir.path, 'twonly.backup.sqlite')); - - final backupDatabaseFileCleaned = File( - join(backupDir.path, 'twonly.backup.cleaned.sqlite'), - ); - - // copy database - final originalDatabase = File( - join(AppEnvironment.supportDir, 'twonly.sqlite'), - ); - await originalDatabase.copy(backupDatabaseFile.path); - - driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; - final backupDB = TwonlyDB( - driftDatabase( - name: 'twonly.backup', - native: DriftNativeOptions( - databaseDirectory: () async { - return backupDir; - }, - ), - ), - ); - - await backupDB.deleteDataForTwonlySafe(); - - await backupDB.customStatement('VACUUM INTO ?', [ - backupDatabaseFileCleaned.path, - ]); - - await backupDB.printTableSizes(); - - await backupDB.close(); - - // ignore: inference_failure_on_collection_literal - final secureStorageBackup = {}; - secureStorageBackup[SecureStorageKeys.signalIdentity] = await SecureStorage - .instance - .read( - key: SecureStorageKeys.signalIdentity, - ); - secureStorageBackup[SecureStorageKeys.signalSignedPreKey] = - await SecureStorage.instance.read( - key: SecureStorageKeys.signalSignedPreKey, - ); - - final userBackup = await UserService.getUser(); - if (userBackup == null) return; - // FILTER settings which should not be in the backup - userBackup - ..twonlySafeBackup = null - ..lastImageSend = null - ..todaysImageCounter = null - ..lastPlanBallance = '' - ..additionalUserInvites = '' - ..signalLastSignedPreKeyUpdated = null; - - secureStorageBackup[SecureStorageKeys.userData] = jsonEncode(userBackup); - - // Compress and convert backup data - - final twonlyDatabaseBytes = await backupDatabaseFileCleaned.readAsBytes(); - await backupDatabaseFile.delete(); - await backupDatabaseFileCleaned.delete(); - - Log.info('twonlyDatabaseLength = ${twonlyDatabaseBytes.lengthInBytes}'); - Log.info('secureStorageLength = ${jsonEncode(secureStorageBackup).length}'); - - final backupProto = TwonlySafeBackupContent( - secureStorageJson: jsonEncode(secureStorageBackup), - twonlyDatabase: twonlyDatabaseBytes, - ); - - final backupBytes = gzip.encode(backupProto.writeToBuffer()); - - final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes); - - if (userService.currentUser.twonlySafeBackup!.lastBackupDone == null || - userService.currentUser.twonlySafeBackup!.lastBackupDone!.isAfter( - clock.now().subtract(const Duration(days: 90)), - )) { - force = true; - } - - final lastHash = await SecureStorage.instance.read( - key: SecureStorageKeys.twonlySafeLastBackupHash, - ); - - if (lastHash != null && !force) { - if (backupHash == lastHash) { - Log.info('Since last backup nothing has changed.'); - return; - } - } - - await SecureStorage.instance.write( - key: SecureStorageKeys.twonlySafeLastBackupHash, - value: backupHash, - ); - - // Encrypt backup data - - final chacha20 = FlutterChacha20.poly1305Aead(); - final nonce = chacha20.newNonce(); - - final secretBox = await chacha20.encrypt( - backupBytes, - secretKey: SecretKey( - userService.currentUser.twonlySafeBackup!.encryptionKey, - ), - nonce: nonce, - ); - - final encryptedBackupBytes = TwonlySafeBackupEncrypted( - mac: secretBox.mac.bytes, - nonce: nonce, - cipherText: secretBox.cipherText, - ).writeToBuffer(); - - Log.info('Backup files created.'); - - final encryptedBackupBytesFile = File( - join(backupDir.path, 'twonly_safe.backup'), - ); - - await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes); - - Log.info( - 'Create twonly Backup with a size of ${encryptedBackupBytes.length} bytes.', - ); - - if (userService.currentUser.backupServer != null) { - if (encryptedBackupBytes.length > - userService.currentUser.backupServer!.maxBackupBytes) { - Log.error('Backup is to big for the alternative backup server.'); - await UserService.update((user) { - user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; - }); - return; - } - } - - final task = UploadTask.fromFile( - taskId: 'backup', - file: encryptedBackupBytesFile, - httpRequestMethod: 'PUT', - url: getTwonlySafeBackupUrl()!, - post: 'binary', - retries: 2, - headers: { - 'Content-Type': 'application/octet-stream', - }, - ); - if (await FileDownloader().enqueue(task)) { - Log.info('Starting upload from twonly Backup.'); - await UserService.update((user) { - user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending; - user.twonlySafeBackup!.lastBackupDone = clock.now(); - user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length; - }); - } else { - Log.error('Error starting UploadTask for twonly Backup.'); - } -} - -Future handleBackupStatusUpdate(TaskStatusUpdate update) async { - if (update.status == TaskStatus.failed || - update.status == TaskStatus.canceled) { - await UserService.update((user) { - if (user.twonlySafeBackup != null) { - user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; - } - }); - } else if (update.status == TaskStatus.complete) { - Log.info( - 'twonly Backup uploaded with status code ${update.responseStatusCode}', - ); - await UserService.update((user) { - if (user.twonlySafeBackup != null) { - user.twonlySafeBackup!.backupUploadState = - LastBackupUploadState.success; - } - }); - } else { - Log.info('Backup is in state: ${update.status}'); - return; - } -} diff --git a/lib/src/services/backup/restore.backup.dart b/lib/src/services/backup/restore.backup.dart deleted file mode 100644 index b06c2fe6..00000000 --- a/lib/src/services/backup/restore.backup.dart +++ /dev/null @@ -1,117 +0,0 @@ -// ignore_for_file: avoid_dynamic_calls - -import 'dart:convert'; -import 'dart:io'; - -import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; -import 'package:drift/drift.dart'; -import 'package:http/http.dart' as http; -import 'package:path/path.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage.keys.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart'; -import 'package:twonly/src/services/backup/common.backup.dart'; -import 'package:twonly/src/services/user.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/secure_storage.dart'; - -Future recoverBackup( - String username, - String password, - BackupServer? server, -) async { - final (backupId, encryptionKey) = await getMasterKey(password, username); - - final backupServerUrl = getTwonlySafeBackupUrlFromServer(backupId, server); - - if (backupServerUrl == null) { - Log.error('Could not create backup url'); - throw Exception('Could not create backup server url'); - } - - late Uint8List backupData; - late http.Response response; - - try { - response = await http.get( - Uri.parse(backupServerUrl), - headers: { - HttpHeaders.acceptHeader: 'application/octet-stream', - }, - ); - } catch (e) { - Log.error('Error fetching backup: $e'); - throw Exception('Backup server could not be reached. ($e)'); - } - - switch (response.statusCode) { - case 200: - backupData = response.bodyBytes; - case 400: - throw Exception('Bad Request: Validation failed.'); - case 404: - throw Exception('No backup was found.'); - case 429: - throw Exception('Too Many Requests: Rate limit reached.'); - default: - throw Exception('Unexpected error: ${response.statusCode}'); - } - - return handleBackupData(encryptionKey, backupData); -} - -Future handleBackupData( - Uint8List encryptionKey, - Uint8List backupData, -) async { - final encryptedBackup = TwonlySafeBackupEncrypted.fromBuffer( - backupData, - ); - - final secretBox = SecretBox( - encryptedBackup.cipherText, - nonce: encryptedBackup.nonce, - mac: Mac(encryptedBackup.mac), - ); - - final compressedBytes = await FlutterChacha20.poly1305Aead().decrypt( - secretBox, - secretKey: SecretKeyData(encryptionKey), - ); - - final plaintextBytes = gzip.decode(compressedBytes); - - final backupContent = TwonlySafeBackupContent.fromBuffer( - plaintextBytes, - ); - - final originalDatabase = File( - join(AppEnvironment.supportDir, 'twonly.sqlite'), - ); - - // in case there was only a secure storage error, do not replace the original database - if (!originalDatabase.existsSync()) { - await originalDatabase.writeAsBytes(backupContent.twonlyDatabase); - } - - const storage = SecureStorage.instance; - - final secureStorage = jsonDecode(backupContent.secureStorageJson); - - await storage.write( - key: SecureStorageKeys.signalIdentity, - value: secureStorage[SecureStorageKeys.signalIdentity] as String, - ); - await storage.write( - key: SecureStorageKeys.signalSignedPreKey, - value: secureStorage[SecureStorageKeys.signalSignedPreKey] as String, - ); - final userDataMap = jsonDecode(secureStorage[SecureStorageKeys.userData] as String) as Map; - final userData = UserData.fromJson(userDataMap); - await UserService.save(userData); - await UserService.update((u) { - u.deviceId += 1; - }); -} diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.service.dart similarity index 100% rename from lib/src/services/group.services.dart rename to lib/src/services/group.service.dart diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index 59618688..c41c4cce 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -6,11 +6,9 @@ import 'dart:io' show Platform; import 'package:firebase_app_installations/firebase_app_installations.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; -import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/user.service.dart'; @@ -21,11 +19,8 @@ import '../../../firebase_options.dart'; // see more here: https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=de Future checkForTokenUpdates() async { - const storage = FlutterSecureStorage(); - - final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); - try { + if (!userService.isUserCreated) return; if (Platform.isIOS) { var apnsToken = await FirebaseMessaging.instance.getAPNSToken(); for (var i = 0; i < 20; i++) { @@ -47,23 +42,22 @@ Future checkForTokenUpdates() async { Log.info('Loaded FCM token.'); - if (storedToken == null || fcmToken != storedToken) { - Log.info('Got new FCM TOKEN.'); - await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); + if (userService.currentUser.fcmToken == null || + fcmToken != userService.currentUser.fcmToken) { + Log.info('Got new FCM token.'); await UserService.update((u) { - u.updateFCMToken = true; + u + ..updateFCMToken = true + ..fcmToken = fcmToken; }); } FirebaseMessaging.instance.onTokenRefresh .listen((fcmToken) async { - Log.info('Got new FCM TOKEN.'); - await storage.write( - key: SecureStorageKeys.googleFcm, - value: fcmToken, - ); await UserService.update((u) { - u.updateFCMToken = true; + u + ..updateFCMToken = true + ..fcmToken = fcmToken; }); }) .onError((err) { @@ -75,21 +69,23 @@ Future checkForTokenUpdates() async { } Future initFCMAfterAuthenticated({bool force = false}) async { + final fcmToken = userService.currentUser.fcmToken; if (userService.currentUser.updateFCMToken || force) { - const storage = FlutterSecureStorage(); - final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); - if (storedToken != null) { - final res = await apiService.updateFCMToken(storedToken); - if (res.isSuccess) { - Log.info('Uploaded new FCM token!'); - await UserService.update((u) { - u.updateFCMToken = false; - }); - } else { - Log.error('Could not update FCM token!'); - } + if (fcmToken == null) { + Log.error('FCM token could not be updated as it is empty'); + await checkForTokenUpdates(); + return; + } + final res = await apiService.updateFCMToken( + fcmToken, + ); + if (res.isSuccess) { + Log.info('Uploaded new FCM token!'); + await UserService.update((u) { + u.updateFCMToken = false; + }); } else { - Log.error('Could not send FCM update to server as token is empty.'); + Log.error('Could not update FCM token!'); } } } @@ -99,7 +95,7 @@ Future resetFCMTokens() async { Log.info('Firebase Installation successfully deleted.'); await FirebaseMessaging.instance.deleteToken(); Log.info('Old FCM deleted.'); - await const FlutterSecureStorage().delete(key: SecureStorageKeys.googleFcm); + await UserService.update((u) => u.fcmToken = null); await checkForTokenUpdates(); await initFCMAfterAuthenticated(force: true); } diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index 6aadec4c..8c7846db 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -1,59 +1,51 @@ -import 'dart:convert'; import 'dart:typed_data'; - import 'package:clock/clock.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:twonly/core/bridge/wrapper/key_manager.dart'; import 'package:twonly/locator.dart'; -import 'package:twonly/src/constants/secure_storage.keys.dart'; -import 'package:twonly/src/database/signal/signal_protocol_store.dart'; import 'package:twonly/src/model/json/signal_identity.model.dart'; import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/protocol_state.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/secure_storage.dart'; -Future getSignalIdentityKeyPair() async { - final signalIdentity = await getSignalIdentity(); - if (signalIdentity == null) return null; - return IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List); -} - -// This function runs after the clients authenticated with the server. -// It then checks if it should update a new session key -Future signalHandleNewServerConnection() async { - if (userService.currentUser.signalLastSignedPreKeyUpdated != null) { - final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48)); - final isYoungerThan48Hours = - (userService.currentUser.signalLastSignedPreKeyUpdated!).isAfter( - fortyEightHoursAgo, - ); - if (isYoungerThan48Hours) { - // The key does live for 48 hours then it expires and a new key is generated. +class SignalIdentityService { + static Future onAuthenticated() async { + if (userService.currentUser.signalLastSignedPreKeyUpdated != null) { + final fortyEightHoursAgo = clock.now().subtract( + const Duration(hours: 48), + ); + final isYoungerThan48Hours = + (userService.currentUser.signalLastSignedPreKeyUpdated!).isAfter( + fortyEightHoursAgo, + ); + if (isYoungerThan48Hours) { + // The key does live for 48 hours then it expires and a new key is generated. + return; + } + } + final signedPreKey = await _getNewSignalSignedPreKey(); + if (signedPreKey == null) { + Log.error('could not generate a new signed pre key!'); return; } - } - final signedPreKey = await _getNewSignalSignedPreKey(); - if (signedPreKey == null) { - Log.error('could not generate a new signed pre key!'); - return; - } - await UserService.update((user) { - user.signalLastSignedPreKeyUpdated = clock.now(); - }); - final res = await apiService.updateSignedPreKey( - signedPreKey.id, - signedPreKey.getKeyPair().publicKey.serialize(), - signedPreKey.signature, - ); - if (res.isError) { - Log.error('could not update the signed pre key: ${res.error}'); await UserService.update((user) { - user.signalLastSignedPreKeyUpdated = null; + user.signalLastSignedPreKeyUpdated = clock.now(); }); - } else { - Log.info('updated signed pre key'); + final res = await apiService.updateSignedPreKey( + signedPreKey.id, + signedPreKey.getKeyPair().publicKey.serialize(), + signedPreKey.signature, + ); + if (res.isError) { + Log.error('could not update the signed pre key: ${res.error}'); + await UserService.update((user) { + user.signalLastSignedPreKeyUpdated = null; + }); + } else { + Log.info('updated signed pre key'); + } } } @@ -75,64 +67,45 @@ Future> signalGetPreKeys() async { Future getSignalIdentity() async { try { - var signalIdentityJson = await SecureStorage.instance.read( - key: SecureStorageKeys.signalIdentity, + final identity = await RustKeyManager.getSignalIdentity(); + return SignalIdentity( + identityKeyPairU8List: identity.$1, + registrationId: identity.$2, ); - if (signalIdentityJson == null) { - return null; - } - final decoded = jsonDecode(signalIdentityJson); - signalIdentityJson = null; - return SignalIdentity.fromJson(decoded as Map); } catch (e) { Log.error('could not load signal identity: $e'); return null; } } +Future getSignalIdentityKeyPair() async { + final signalIdentity = await getSignalIdentity(); + if (signalIdentity == null) return null; + return IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List); +} + Future getUserPublicKey() async { - Log.info('getUserPublicKey: getting identity'); final signalIdentity = (await getSignalIdentity())!; - Log.info('getUserPublicKey: getting signal store'); final signalStore = await getSignalStoreFromIdentity(signalIdentity); - Log.info('getUserPublicKey: getting key pair'); final keyPair = await signalStore.getIdentityKeyPair(); - Log.info('getUserPublicKey: serializing public key'); return keyPair.getPublicKey().serialize(); } Future createIfNotExistsSignalIdentity() async { - final signalIdentity = await SecureStorage.instance.read( - key: SecureStorageKeys.signalIdentity, - ); - - if (signalIdentity != null) { - return; - } + // check if identity already exists + if (await getSignalIdentity() != null) return; final identityKeyPair = generateIdentityKeyPair(); final registrationId = generateRegistrationId(true); - final signalStore = SignalSignalProtocolStore( - identityKeyPair, - registrationId, - ); - final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId); + final signedPreKeyStore = {}; + signedPreKeyStore[signedPreKey.id] = signedPreKey.serialize(); - await signalStore.signedPreKeyStore.storeSignedPreKey( - signedPreKey.id, - signedPreKey, - ); - - final storedSignalIdentity = SignalIdentity( - identityKeyPairU8List: identityKeyPair.serialize(), + await RustKeyManager.importSignalIdentity( + identityKeyPairStructure: identityKeyPair.serialize(), registrationId: registrationId, - ); - - await SecureStorage.instance.write( - key: SecureStorageKeys.signalIdentity, - value: jsonEncode(storedSignalIdentity), + signedPreKeyStore: signedPreKeyStore, ); } diff --git a/lib/src/services/user.service.dart b/lib/src/services/user.service.dart index 232c9813..4c551463 100644 --- a/lib/src/services/user.service.dart +++ b/lib/src/services/user.service.dart @@ -5,9 +5,9 @@ import 'package:mutex/mutex.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/model/json/userdata.model.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/secure_storage.dart'; -import 'package:twonly/src/utils/keyvalue.dart'; class UserService { late UserData currentUser; diff --git a/lib/src/utils/keyvalue.dart b/lib/src/utils/keyvalue.dart index b8f5ce5d..25b0617d 100644 --- a/lib/src/utils/keyvalue.dart +++ b/lib/src/utils/keyvalue.dart @@ -32,31 +32,33 @@ class KeyValueStore { } }); - static Future?> get(String key) => - _exclusive(key, () async { - final file = await _getFilePath(key); - try { - if (file.existsSync()) { - final contents = await file.readAsString(); - return jsonDecode(contents) as Map; - } else { - return null; - } - } catch (e) { - Log.warn('Error reading file. Deleting it.: $e'); - file.deleteSync(); + static Future?> get(String key) async { + return _exclusive(key, () async { + final file = await _getFilePath(key); + try { + if (file.existsSync()) { + final contents = await file.readAsString(); + return jsonDecode(contents) as Map; + } else { return null; } - }); + } catch (e) { + Log.warn('Error reading file. Deleting it.: $e'); + file.deleteSync(); + return null; + } + }); + } - static Future put(String key, Map value) => - _exclusive(key, () async { - try { - final file = await _getFilePath(key); - await file.parent.create(recursive: true); - await file.writeAsString(jsonEncode(value)); - } catch (e) { - Log.error('Error writing file: $e'); - } - }); + static Future put(String key, Map value) async { + return _exclusive(key, () async { + try { + final file = await _getFilePath(key); + await file.parent.create(recursive: true); + await file.writeAsString(jsonEncode(value)); + } catch (e) { + Log.error('Error writing file: $e'); + } + }); + } } diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 33874fc9..873bfae3 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -197,12 +197,12 @@ String formatDateTime(BuildContext context, DateTime? dateTime) { } } -String formatBytes(int bytes, {int decimalPlaces = 2}) { +String formatBytes(int bytes) { if (bytes <= 0) return '0 Bytes'; const units = ['Bytes', 'KB', 'MB', 'GB', 'TB']; final unitIndex = (log(bytes) / log(1000)).floor(); final formattedSize = bytes / pow(1000, unitIndex); - return '${formattedSize.toStringAsFixed(decimalPlaces)} ${units[unitIndex]}'; + return '${formattedSize.ceil()} ${units[unitIndex]}'; } bool isUUIDNewer(String uuid1, String uuid2) { diff --git a/lib/src/visual/components/select_chat_deletion_time.comp.dart b/lib/src/visual/components/select_chat_deletion_time.comp.dart index bae7ec3e..4151344f 100644 --- a/lib/src/visual/components/select_chat_deletion_time.comp.dart +++ b/lib/src/visual/components/select_chat_deletion_time.comp.dart @@ -9,7 +9,7 @@ import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.api.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; diff --git a/lib/src/visual/components/snackbar.dart b/lib/src/visual/components/snackbar.dart new file mode 100644 index 00000000..e182fa9a --- /dev/null +++ b/lib/src/visual/components/snackbar.dart @@ -0,0 +1,258 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +enum SnackbarLevel { + info, + success, + warning, + error, +} + +void showSnackbar( + BuildContext context, + String message, { + SnackbarLevel level = SnackbarLevel.error, +}) { + Color backgroundColor; + IconData iconData; + + switch (level) { + case SnackbarLevel.info: + backgroundColor = Colors.blue.shade700; + iconData = Icons.info_outline; + case SnackbarLevel.success: + backgroundColor = Colors.green.shade700; + iconData = Icons.check_circle_outline; + case SnackbarLevel.warning: + backgroundColor = Colors.orange.shade800; + iconData = Icons.warning_amber_rounded; + case SnackbarLevel.error: + backgroundColor = Colors.red.shade700; + iconData = Icons.error_outline; + } + + AnimationController? localAnimationController; + + _showOverlay( + context: context, + animationDuration: const Duration(milliseconds: 1000), + reverseAnimationDuration: const Duration(milliseconds: 350), + displayDuration: const Duration(milliseconds: 3000), + onAnimationControllerInit: (controller) => + localAnimationController = controller, + child: _SnackbarWidget( + message: message, + backgroundColor: backgroundColor, + icon: Icon(iconData, color: Colors.white, size: 28), + onCloseClick: () { + localAnimationController?.reverse(); + }, + ), + ); +} + +OverlayEntry? _previousEntry; + +void _showOverlay({ + required BuildContext context, + required Widget child, + required Duration animationDuration, + required Duration reverseAnimationDuration, + required Duration displayDuration, + required void Function(AnimationController) onAnimationControllerInit, +}) { + final overlayState = Overlay.maybeOf(context); + if (overlayState == null) return; + + late OverlayEntry overlayEntry; + overlayEntry = OverlayEntry( + builder: (_) => _AnimatedSnackbar( + animationDuration: animationDuration, + reverseAnimationDuration: reverseAnimationDuration, + displayDuration: displayDuration, + onAnimationControllerInit: onAnimationControllerInit, + onDismissed: () { + if (overlayEntry.mounted) { + overlayEntry.remove(); + } + if (_previousEntry == overlayEntry) { + _previousEntry = null; + } + }, + child: child, + ), + ); + + if (_previousEntry != null && _previousEntry!.mounted) { + _previousEntry?.remove(); + } + + overlayState.insert(overlayEntry); + _previousEntry = overlayEntry; +} + +class _SnackbarWidget extends StatelessWidget { + const _SnackbarWidget({ + required this.message, + required this.backgroundColor, + required this.icon, + required this.onCloseClick, + }); + final String message; + final Color backgroundColor; + final Icon icon; + final VoidCallback onCloseClick; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + clipBehavior: Clip.hardEdge, + constraints: const BoxConstraints(minHeight: 70), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 30, + ), + ], + ), + width: double.infinity, + child: Row( + children: [ + const SizedBox(width: 16), + icon, + const SizedBox(width: 12), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + message, + style: theme.textTheme.bodyMedium?.merge( + const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white, + ), + ), + textAlign: TextAlign.start, + ), + ), + ), + GestureDetector( + onTap: onCloseClick, + behavior: HitTestBehavior.opaque, + child: const Padding( + padding: EdgeInsets.all(16), + child: Icon(Icons.close, color: Colors.white70, size: 20), + ), + ), + ], + ), + ); + } +} + +class _AnimatedSnackbar extends StatefulWidget { + const _AnimatedSnackbar({ + required this.child, + required this.onDismissed, + required this.animationDuration, + required this.reverseAnimationDuration, + required this.displayDuration, + required this.onAnimationControllerInit, + }); + final Widget child; + final VoidCallback onDismissed; + final Duration animationDuration; + final Duration reverseAnimationDuration; + final Duration displayDuration; + final void Function(AnimationController) onAnimationControllerInit; + + @override + State<_AnimatedSnackbar> createState() => _AnimatedSnackbarState(); +} + +class _AnimatedSnackbarState extends State<_AnimatedSnackbar> + with SingleTickerProviderStateMixin { + late final AnimationController _animationController; + late final Animation _offsetAnimation; + Timer? _timer; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: widget.animationDuration, + reverseDuration: widget.reverseAnimationDuration, + ); + + _animationController.addStatusListener(_handleAnimationStatus); + widget.onAnimationControllerInit(_animationController); + + _offsetAnimation = + Tween( + begin: const Offset(0, -1), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.elasticOut, + reverseCurve: Curves.linearToEaseOut, + ), + ); + + _animationController.forward(); + } + + void _handleAnimationStatus(AnimationStatus status) { + if (status == AnimationStatus.completed) { + _timer = Timer(widget.displayDuration, () { + if (mounted) { + _animationController.reverse(); + } + }); + } else if (status == AnimationStatus.dismissed) { + _timer?.cancel(); + widget.onDismissed(); + } + } + + @override + void dispose() { + _animationController.dispose(); + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Positioned( + top: 16, + left: 16, + right: 16, + child: SlideTransition( + position: _offsetAnimation, + child: SafeArea( + child: Dismissible( + key: UniqueKey(), + direction: DismissDirection.up, + dismissThresholds: const {DismissDirection.up: 0.2}, + confirmDismiss: (_) async { + if (mounted) { + await _animationController.reverse(); + } + return false; + }, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_scanned_overlay.dart b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_scanned_overlay.dart index 05ee4ff3..30de85cf 100644 --- a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_scanned_overlay.dart +++ b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_scanned_overlay.dart @@ -5,6 +5,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -46,16 +47,19 @@ class CameraScannedOverlay extends StatelessWidget { onTap: () async { c.isLoading = true; mainController.setState(); + + showSnackbar( + context, + context.lang.requestedUserToastText(c.profile.username), + level: SnackbarLevel.success, + ); if (await addNewContactFromPublicProfile(c.profile) && context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.lang.requestedUserToastText(c.profile.username), - ), - duration: const Duration(seconds: 8), - ), - ); + // showSnackbar( + // context, + // context.lang.requestedUserToastText(c.profile.username), + // level: SnackbarLevel.success, + // ); } }, child: Container( diff --git a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart index 6b7a8a1c..a878303f 100644 --- a/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart +++ b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart @@ -19,6 +19,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; @@ -254,14 +255,12 @@ class _CameraPreviewViewState extends State { await File(picture.path).delete(); return imageBytes; } catch (e) { - if (context.mounted) { - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error loading picture: $e'), - duration: const Duration(seconds: 3), - ), + if (mounted) { + showSnackbar( + context, + 'Error loading picture: $e', ); + Log.error(e); } return null; } @@ -606,17 +605,7 @@ class _CameraPreviewViewState extends State { void _showCameraException(dynamic e) { Log.error('$e'); - try { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error: $e'), - duration: const Duration(seconds: 3), - ), - ); - } - // ignore: empty_catches - } catch (e) {} + if (mounted) showSnackbar(context, 'Error: $e'); } @override diff --git a/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart index d0d0672a..e11760ce 100644 --- a/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart +++ b/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart @@ -17,6 +17,7 @@ import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart'; @@ -373,18 +374,14 @@ class MainCameraController { ); await HapticFeedback.heavyImpact(); - if (verificationOk) { - AppGlobalKeys.scaffoldMessengerKey.currentState?.showSnackBar( - SnackBar( - content: Text( - AppGlobalKeys.scaffoldMessengerKey.currentContext?.lang - .verifiedPublicKey( - getContactDisplayName(contact), - ) ?? - '', - ), - duration: const Duration(seconds: 6), + final context = cameraPreviewKey.currentContext; + if (verificationOk && context != null && context.mounted) { + showSnackbar( + context, + context.lang.verifiedPublicKey( + getContactDisplayName(contact), ), + level: SnackbarLevel.success, ); } } diff --git a/lib/src/visual/views/chats/chat_list.view.dart b/lib/src/visual/views/chats/chat_list.view.dart index 64aba01d..8a889073 100644 --- a/lib/src/visual/views/chats/chat_list.view.dart +++ b/lib/src/visual/views/chats/chat_list.view.dart @@ -21,6 +21,7 @@ import 'package:twonly/src/visual/themes/light.dart'; import 'package:twonly/src/visual/views/chats/chat_list_components/feedback_btn.comp.dart'; import 'package:twonly/src/visual/views/chats/chat_list_components/group_list_item.comp.dart'; import 'package:twonly/src/visual/views/onboarding/setup/components/finish_setup.comp.dart'; +import 'package:twonly/src/visual/views/settings/backup/components/missing_backup_setup.comp.dart'; class ChatListView extends StatefulWidget { const ChatListView({super.key}); @@ -215,6 +216,7 @@ class _ChatListViewState extends State { child: Column( children: [ const FinishSetupComp(), + const MissingBackupComp(), if (_groupsNotPinned.isEmpty && _groupsPinned.isEmpty && _groupsArchived.isEmpty) diff --git a/lib/src/visual/views/chats/chat_messages_components/chat_group_action.dart b/lib/src/visual/views/chats/chat_messages_components/chat_group_action.dart index 9ed5d44a..eeb14636 100644 --- a/lib/src/visual/views/chats/chat_messages_components/chat_group_action.dart +++ b/lib/src/visual/views/chats/chat_messages_components/chat_group_action.dart @@ -125,44 +125,6 @@ class _ChatGroupActionState extends State { } } - // switch (widget.action.type) { - // case GroupActionType.updatedGroupName: - // text = (contact == null) - // ? 'You have changed the group name to "${widget.action.newGroupName}".' - // : '$maker has changed the group name to "${widget.action.newGroupName}".'; - // icon = FontAwesomeIcons.pencil; - // case GroupActionType.createdGroup: - // icon = FontAwesomeIcons.penToSquare; - // text = (contact == null) - // ? 'You have created the group.' - // : '$maker has created the group.'; - // case GroupActionType.removedMember: - // icon = FontAwesomeIcons.userMinus; - // text = (contact == null) - // ? 'You have removed $affected from the group.' - // : '$maker has removed $affected from the group.'; - // case GroupActionType.addMember: - // icon = FontAwesomeIcons.userPlus; - // text = (contact == null) - // ? 'You have added $affected to the group.' - // : '$maker has added $affected to the group.'; - // case GroupActionType.promoteToAdmin: - // icon = FontAwesomeIcons.key; - // text = (contact == null) - // ? 'You made $affected an admin.' - // : '$maker made $affected an admin.'; - // case GroupActionType.demoteToMember: - // icon = FontAwesomeIcons.key; - // text = (contact == null) - // ? 'You revoked $affectedR admin rights.' - // : '$maker revoked $affectedR admin rights.'; - // case GroupActionType.leftGroup: - // icon = FontAwesomeIcons.userMinus; - // text = (contact == null) - // ? 'You have left the group.' - // : '$maker has left the group.'; - // } - return Padding( padding: const EdgeInsets.all(8), child: Center( diff --git a/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart b/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart index cd23bc7e..50ce2c6a 100644 --- a/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart +++ b/lib/src/visual/views/contact/add_contact_via_qr_link.view.dart @@ -10,8 +10,10 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart as server; import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; import 'package:twonly/src/services/api/utils.api.dart'; +import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; class AddContactViaQrLinkView extends StatefulWidget { const AddContactViaQrLinkView({ @@ -69,11 +71,8 @@ class _AddContactViaQrLinkViewState extends State { context.pop(); } } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $e')), - ); - } + if (mounted) showSnackbar(context, 'Error: $e'); + Log.error(e); } finally { if (mounted) { setState(() { diff --git a/lib/src/visual/views/contact/contact.view.dart b/lib/src/visual/views/contact/contact.view.dart index 61633e1f..4d6b5ad1 100644 --- a/lib/src/visual/views/contact/contact.view.dart +++ b/lib/src/visual/views/contact/contact.view.dart @@ -16,6 +16,7 @@ import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/flame_counter.comp.dart'; import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/verification_badge.comp.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart'; @@ -102,12 +103,7 @@ class _ContactViewState extends State { if (!mounted) return; if (!delete) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.deleteUserErrorMessage), - duration: const Duration(seconds: 8), - ), - ); + showSnackbar(context, context.lang.deleteUserErrorMessage); return; } @@ -157,11 +153,10 @@ class _ContactViewState extends State { final res = await apiService.reportUser(contact.userId, reason); if (!mounted) return; if (res.isSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.userGotReported), - duration: const Duration(seconds: 3), - ), + showSnackbar( + context, + context.lang.userGotReported, + level: SnackbarLevel.info, ); } else { showNetworkIssue(context); diff --git a/lib/src/visual/views/groups/group.view.dart b/lib/src/visual/views/groups/group.view.dart index 228a23a3..8aef9679 100644 --- a/lib/src/visual/views/groups/group.view.dart +++ b/lib/src/visual/views/groups/group.view.dart @@ -7,12 +7,13 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/flame_counter.comp.dart'; import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/verification_badge.comp.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/views/contact/contact.view.dart'; @@ -343,10 +344,8 @@ Future showGroupNameChangeDialog( } void showNetworkIssue(BuildContext context) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.groupNetworkIssue), - duration: const Duration(seconds: 3), - ), + showSnackbar( + context, + context.lang.groupNetworkIssue, ); } diff --git a/lib/src/visual/views/groups/group_create_select_group_name.view.dart b/lib/src/visual/views/groups/group_create_select_group_name.view.dart index 310da8c8..974e6fc8 100644 --- a/lib/src/visual/views/groups/group_create_select_group_name.view.dart +++ b/lib/src/visual/views/groups/group_create_select_group_name.view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/flame_counter.comp.dart'; diff --git a/lib/src/visual/views/groups/group_create_select_members.view.dart b/lib/src/visual/views/groups/group_create_select_members.view.dart index 1abc5c71..5c6a0d0d 100644 --- a/lib/src/visual/views/groups/group_create_select_members.view.dart +++ b/lib/src/visual/views/groups/group_create_select_members.view.dart @@ -11,6 +11,7 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/flame_counter.comp.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/context_menu/user.context_menu.dart'; import 'package:twonly/src/visual/decorations/input_text.decoration.dart'; import 'package:twonly/src/visual/views/groups/group_create_select_group_name.view.dart'; @@ -88,12 +89,7 @@ class _StartNewChatView extends State { if (alreadyInGroup.contains(userId)) return; if (!selectedUsers.contains(userId)) { if (selectedUsers.length + alreadyInGroup.length > 256) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.groupSizeLimitError(256)), - duration: const Duration(seconds: 3), - ), - ); + showSnackbar(context, context.lang.groupSizeLimitError(256)); return; } selectedUsers.add(userId); diff --git a/lib/src/visual/views/groups/group_member.context.dart b/lib/src/visual/views/groups/group_member.context.dart index 7905871f..a8a820d3 100644 --- a/lib/src/visual/views/groups/group_member.context.dart +++ b/lib/src/visual/views/groups/group_member.context.dart @@ -9,9 +9,10 @@ import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.api.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/context_menu/context_menu.helper.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; @@ -107,11 +108,10 @@ class GroupMemberContextMenu extends StatelessWidget { ), ); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.contactRequestSend), - duration: const Duration(seconds: 3), - ), + showSnackbar( + context, + context.lang.contactRequestSend, + level: SnackbarLevel.success, ); } } diff --git a/lib/src/visual/views/onboarding/recover.view.dart b/lib/src/visual/views/onboarding/recover.view.dart index 6599fe45..7f13ecb2 100644 --- a/lib/src/visual/views/onboarding/recover.view.dart +++ b/lib/src/visual/views/onboarding/recover.view.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:restart_app/restart_app.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/services/backup/restore.backup.dart'; -import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/decorations/input_text.decoration.dart'; -import 'package:twonly/src/visual/views/settings/backup/backup_server.view.dart'; class BackupRecoveryView extends StatefulWidget { const BackupRecoveryView({super.key}); @@ -19,7 +17,6 @@ class BackupRecoveryView extends StatefulWidget { class _BackupRecoveryViewState extends State { bool obscureText = true; bool isLoading = false; - BackupServer? backupServer; final TextEditingController usernameCtrl = TextEditingController(); final TextEditingController passwordCtrl = TextEditingController(); @@ -28,31 +25,38 @@ class _BackupRecoveryViewState extends State { isLoading = true; }); - try { - await recoverBackup( - usernameCtrl.text, - passwordCtrl.text, - backupServer, - ); + final error = await BackupService.startFullBackupRecovery( + usernameCtrl.text, + passwordCtrl.text, + ); + if (!mounted) return; - await Restart.restartApp( - notificationTitle: 'Backup successfully recovered.', - notificationBody: 'Click here to open the app again', - forceKill: true, - ); - } catch (e) { - // in case something was already written from the backup... - Log.error('$e'); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('$e'), - duration: const Duration(seconds: 3), - ), - ); + if (error != null) { + String errorMessage; + switch (error) { + case RecoveryError.noInternet: + errorMessage = context.lang.recoverErrorNoInternet; + case RecoveryError.usernameNotValid: + errorMessage = context.lang.recoverErrorUsernameNotValid; + case RecoveryError.passwordInvalid: + errorMessage = context.lang.recoverErrorPasswordInvalid; + case RecoveryError.tryAgainLater: + errorMessage = context.lang.recoverErrorTryAgainLater; + case RecoveryError.unkownError: + errorMessage = context.lang.recoverErrorUnknown; } + setState(() { + isLoading = false; + }); + return showSnackbar(context, errorMessage); } + await Restart.restartApp( + notificationTitle: context.lang.recoverSuccessTitle, + notificationBody: context.lang.recoverSuccessBody, + forceKill: true, + ); + setState(() { isLoading = false; }); @@ -135,20 +139,6 @@ class _BackupRecoveryViewState extends State { ), ], ), - const SizedBox(height: 30), - Center( - child: OutlinedButton( - onPressed: () async { - backupServer = - await context.navPush( - const BackupServerView(), - ) - as BackupServer?; - setState(() {}); - }, - child: Text(context.lang.backupExpertSettings), - ), - ), const SizedBox(height: 10), Center( child: FilledButton.icon( diff --git a/lib/src/visual/views/onboarding/setup/backup.setup.dart b/lib/src/visual/views/onboarding/setup/backup.setup.dart index 659c0357..aeff4d5c 100644 --- a/lib/src/visual/views/onboarding/setup/backup.setup.dart +++ b/lib/src/visual/views/onboarding/setup/backup.setup.dart @@ -1,9 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:go_router/go_router.dart'; -import 'package:twonly/src/constants/routes.keys.dart'; -import 'package:twonly/src/services/backup/common.backup.dart'; +import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; @@ -19,16 +17,16 @@ class BackupSetupPage extends StatefulWidget { } class _BackupSetupPageState extends State { - bool isLoading = false; - final TextEditingController passwordCtrl = TextEditingController(); - final TextEditingController repeatedPasswordCtrl = TextEditingController(); + bool _isLoading = false; + final TextEditingController _passwordCtrl = TextEditingController(); + final TextEditingController _repeatedPasswordCtrl = TextEditingController(); Future onPressedEnableTwonlySafe() async { setState(() { - isLoading = true; + _isLoading = true; }); - if (!await isSecurePassword(passwordCtrl.text)) { + if (!await isSecurePassword(_passwordCtrl.text)) { if (!mounted) return true; final ignore = await showAlertDialog( context, @@ -40,14 +38,14 @@ class _BackupSetupPageState extends State { if (!mounted) return true; if (ignore) { setState(() { - isLoading = false; + _isLoading = false; }); return true; } } await Future.delayed(const Duration(milliseconds: 100)); - await enableTwonlySafe(passwordCtrl.text); + await BackupService.updateBackupPassword(_passwordCtrl.text); await UserService.update((user) { user.currentSetupPage = SetupPages.backup.next()?.name; @@ -55,25 +53,25 @@ class _BackupSetupPageState extends State { if (!mounted) return true; setState(() { - isLoading = false; + _isLoading = false; }); return false; } @override void dispose() { - passwordCtrl.dispose(); - repeatedPasswordCtrl.dispose(); + _passwordCtrl.dispose(); + _repeatedPasswordCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - final isPasswordValid = passwordCtrl.text.length >= 10; + final isPasswordValid = _passwordCtrl.text.length >= 10; final isRepeatedPasswordValid = - passwordCtrl.text == repeatedPasswordCtrl.text; + _passwordCtrl.text == _repeatedPasswordCtrl.text; final canSubmit = - !isLoading && + !_isLoading && (isPasswordValid && isRepeatedPasswordValid || !kReleaseMode); return Column( @@ -95,24 +93,24 @@ class _BackupSetupPageState extends State { ), const SizedBox(height: 32), BackupPasswordTextField( - controller: passwordCtrl, + controller: _passwordCtrl, labelText: context.lang.password, onChanged: (_) => setState(() {}), ), PasswordRequirementText( text: context.lang.backupPasswordRequirement, - showError: passwordCtrl.text.isNotEmpty && !isPasswordValid, + showError: _passwordCtrl.text.isNotEmpty && !isPasswordValid, ), const SizedBox(height: 8), BackupPasswordTextField( - controller: repeatedPasswordCtrl, + controller: _repeatedPasswordCtrl, labelText: context.lang.passwordRepeated, onChanged: (_) => setState(() {}), ), PasswordRequirementText( text: context.lang.passwordRepeatedNotEqual, showError: - repeatedPasswordCtrl.text.isNotEmpty && !isRepeatedPasswordValid, + _repeatedPasswordCtrl.text.isNotEmpty && !isRepeatedPasswordValid, ), const SizedBox(height: 10), Row( @@ -131,16 +129,9 @@ class _BackupSetupPageState extends State { ), ], ), - const SizedBox(height: 20), - Center( - child: TextButton( - onPressed: () => context.push(Routes.settingsBackupServer), - child: Text(context.lang.backupExpertSettings), - ), - ), const SizedBox(height: 40), NextButtonComp( - isLoading: isLoading, + isLoading: _isLoading, canSubmit: canSubmit, onPressed: onPressedEnableTwonlySafe, ), diff --git a/lib/src/visual/views/settings/account.view.dart b/lib/src/visual/views/settings/account.view.dart index 8f1fc07c..6dd33624 100644 --- a/lib/src/visual/views/settings/account.view.dart +++ b/lib/src/visual/views/settings/account.view.dart @@ -4,6 +4,7 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; class AccountView extends StatelessWidget { const AccountView({super.key}); @@ -58,13 +59,9 @@ class AccountView extends StatelessWidget { final res = await apiService.deleteAccount(); if (res.isError) { if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Could not delete the account. Please ensure you have a internet connection!', - ), - duration: Duration(seconds: 3), - ), + showSnackbar( + context, + 'Could not delete the account. Please ensure you have a internet connection!', ); return; } diff --git a/lib/src/visual/views/settings/backup/backup_server.view.dart b/lib/src/visual/views/settings/backup/backup_server.view.dart deleted file mode 100644 index 161ce65e..00000000 --- a/lib/src/visual/views/settings/backup/backup_server.view.dart +++ /dev/null @@ -1,182 +0,0 @@ -// ignore_for_file: parameter_assignments, avoid_dynamic_calls - -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:http/http.dart' as http; -import 'package:twonly/locator.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/services/user.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; - -class BackupServerView extends StatefulWidget { - const BackupServerView({super.key}); - - @override - State createState() => _BackupServerViewState(); -} - -class _BackupServerViewState extends State { - final TextEditingController _urlController = TextEditingController(); - final TextEditingController _usernameController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); - - @override - void initState() { - super.initState(); - _urlController.text = 'https://'; - unawaited(initAsync()); - } - - Future initAsync() async { - if (userService.currentUser.backupServer != null) { - final uri = Uri.parse(userService.currentUser.backupServer!.serverUrl); - // remove user auth data - final serverUrl = Uri( - scheme: uri.scheme, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - ); - _urlController.text = serverUrl.toString(); - _usernameController.text = serverUrl.userInfo.split(':')[0]; - } - setState(() {}); - } - - Future checkAndUpdateBackupServer() async { - var serverUrl = _urlController.text; - if (!serverUrl.endsWith('/')) { - serverUrl += '/'; - } - - final username = _usernameController.text; - final password = _passwordController.text; - - if (username.isNotEmpty || password.isNotEmpty) { - serverUrl = serverUrl.replaceAll('https://', ''); - serverUrl = 'https://$username@$password$serverUrl'; - } - - try { - final uri = Uri.parse('${serverUrl}config'); - final response = await http.get( - uri, - headers: { - 'User-Agent': 'twonly', - 'Accept': 'application/json', - }, - ); - if (response.statusCode == 200) { - // If the server returns a 200 OK response, parse the JSON. - final data = jsonDecode(response.body); - - final backupServer = BackupServer( - serverUrl: serverUrl, - retentionDays: data['retentionDays']! as int, - maxBackupBytes: data['maxBackupBytes']! as int, - ); - await UserService.update((user) { - user.backupServer = backupServer; - }); - if (mounted) Navigator.pop(context, backupServer); - } else { - // If the server did not return a 200 OK response, throw an exception. - throw Exception( - 'Got invalid status code ${response.statusCode} from server.', - ); - } - } catch (e) { - Log.error('$e'); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('$e'), - duration: const Duration(seconds: 3), - ), - ); - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('twonly Backup Server'), - ), - body: Padding( - padding: const EdgeInsets.all(40), - child: ListView( - children: [ - Text( - context.lang.backupOwnServerDesc, - textAlign: TextAlign.center, - ), - const SizedBox(height: 30), - TextField( - controller: _urlController, - onChanged: (value) { - if (value.length < 8) { - value = ''; - } - value = value.replaceAll('https://', ''); - value = value.replaceAll('http://', ''); - value = 'https://$value'; - _urlController.text = value; - setState(() {}); - }, - decoration: const InputDecoration( - labelText: 'Server URL', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 16), - TextField( - controller: _usernameController, - decoration: const InputDecoration( - labelText: 'Username (optional)', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 16), - TextField( - controller: _passwordController, - decoration: const InputDecoration( - labelText: 'Password (optional)', - border: OutlineInputBorder(), - ), - obscureText: true, - ), - const SizedBox(height: 20), - Center( - child: FilledButton.icon( - onPressed: (_urlController.text.length > 8) - ? checkAndUpdateBackupServer - : null, - icon: const FaIcon(FontAwesomeIcons.server), - label: Text(context.lang.backupUseOwnServer), - ), - ), - const SizedBox(height: 10), - Center( - child: OutlinedButton( - onPressed: () async { - await UserService.update((user) { - user.backupServer = null; - }); - if (context.mounted) Navigator.pop(context); - }, - child: Text(context.lang.backupResetServer), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/src/visual/views/settings/backup/backup_settings.view.dart b/lib/src/visual/views/settings/backup/backup_settings.view.dart index e321debf..2622345e 100644 --- a/lib/src/visual/views/settings/backup/backup_settings.view.dart +++ b/lib/src/visual/views/settings/backup/backup_settings.view.dart @@ -1,9 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; -import 'package:twonly/src/model/json/userdata.model.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; +import 'package:twonly/src/model/json/backup.model.dart'; +import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/utils/misc.dart'; class BackupView extends StatefulWidget { @@ -13,16 +15,37 @@ class BackupView extends StatefulWidget { State createState() => _BackupViewState(); } -BackupServer _defaultBackupServer = BackupServer( - serverUrl: 'Default', - retentionDays: 180, - maxBackupBytes: 2097152, -); - class _BackupViewState extends State { bool _isLoading = false; + CurrentBackupStatus? _backupStatus; + StreamSubscription? _backupUpdateSub; - String _backupStatus(LastBackupUploadState status) { + @override + void initState() { + super.initState(); + _loadBackupStatus(); + _backupUpdateSub = BackupService.onBackupUpdated.listen((_) { + _loadBackupStatus(); + }); + } + + @override + void dispose() { + _backupUpdateSub?.cancel(); + super.dispose(); + } + + Future _loadBackupStatus() async { + setState(() => _isLoading = true); + final status = await BackupService.getData(); + if (!mounted) return; + setState(() { + _backupStatus = status; + _isLoading = false; + }); + } + + String _getBackupStatusString(LastBackupUploadState status) { switch (status) { case LastBackupUploadState.none: return context.lang.backupPending; @@ -35,21 +58,41 @@ class _BackupViewState extends State { } } + List _buildTableRows(List<(String, String)> rows) { + return rows.map((pair) { + return TableRow( + children: [ + TableCell( + child: Text(pair.$1), + ), + TableCell( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 4, + ), + child: Text( + pair.$2, + textAlign: TextAlign.right, + ), + ), + ), + ], + ); + }).toList(); + } + @override Widget build(BuildContext context) { return StreamBuilder( stream: userService.onUserUpdated, builder: (context, _) { - final backupServer = - userService.currentUser.backupServer ?? _defaultBackupServer; return Scaffold( appBar: AppBar( title: Text(context.lang.settingsBackup), ), body: Padding( padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( children: [ const SizedBox(height: 8), Text( @@ -57,81 +100,80 @@ class _BackupViewState extends State { textAlign: TextAlign.center, ), const SizedBox(height: 8), - if (userService.currentUser.twonlySafeBackup != null) + if (userService.currentUser.isBackupEnabled) Column( children: [ const SizedBox(height: 32), + Center( + child: Text( + context.lang.backupIdentityHeader, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(height: 8), Table( defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - ...[ - ( - context.lang.backupServer, - (backupServer.serverUrl.contains('@')) - ? backupServer.serverUrl.split('@')[1] - : backupServer.serverUrl.replaceAll( - 'https://', - '', - ), + children: _buildTableRows([ + ( + context.lang.backupLastBackupDate, + _backupStatus?.identityLastSuccessFull != null + ? formatDateTime( + context, + _backupStatus!.identityLastSuccessFull, + ) + : '-', + ), + ( + context.lang.backupLastBackupSize, + _backupStatus?.identitySize != null + ? formatBytes(_backupStatus!.identitySize!) + : '-', + ), + ( + context.lang.backupLastBackupResult, + _getBackupStatusString( + _backupStatus?.identityState ?? + LastBackupUploadState.none, ), - ( - context.lang.backupMaxBackupSize, - formatBytes(backupServer.maxBackupBytes), + ), + ]), + ), + const SizedBox(height: 24), + Center( + child: Text( + context.lang.backupArchiveHeader, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(height: 8), + Table( + defaultVerticalAlignment: + TableCellVerticalAlignment.middle, + children: _buildTableRows([ + ( + context.lang.backupLastBackupDate, + _backupStatus?.archiveLastSuccessFull != null + ? formatDateTime( + context, + _backupStatus!.archiveLastSuccessFull, + ) + : '-', + ), + ( + context.lang.backupLastBackupSize, + _backupStatus?.archiveSize != null + ? formatBytes(_backupStatus!.archiveSize!) + : '-', + ), + ( + context.lang.backupLastBackupResult, + _getBackupStatusString( + _backupStatus?.archiveState ?? + LastBackupUploadState.none, ), - ( - context.lang.backupStorageRetention, - '${backupServer.retentionDays} Days', - ), - ( - context.lang.backupLastBackupDate, - formatDateTime( - context, - userService - .currentUser - .twonlySafeBackup! - .lastBackupDone, - ), - ), - ( - context.lang.backupLastBackupSize, - formatBytes( - userService - .currentUser - .twonlySafeBackup! - .lastBackupSize, - ), - ), - ( - context.lang.backupLastBackupResult, - _backupStatus( - userService - .currentUser - .twonlySafeBackup! - .backupUploadState, - ), - ), - ].map((pair) { - return TableRow( - children: [ - TableCell( - child: Text(pair.$1), - ), - TableCell( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4, - ), - child: Text( - pair.$2, - textAlign: TextAlign.right, - ), - ), - ), - ], - ); - }), - ], + ), + ]), ), const SizedBox(height: 10), OutlinedButton( @@ -141,7 +183,7 @@ class _BackupViewState extends State { setState(() { _isLoading = true; }); - await performTwonlySafeBackup(force: true); + await BackupService.makeBackup(force: true); setState(() { _isLoading = false; }); @@ -156,7 +198,7 @@ class _BackupViewState extends State { onPressed: () => context.push(Routes.settingsBackupSetup, extra: true), child: Text( - userService.currentUser.twonlySafeBackup == null + !userService.currentUser.isBackupEnabled ? context.lang.backupEnableBackup : context.lang.backupChangePassword, ), diff --git a/lib/src/visual/views/settings/backup/backup_setup.view.dart b/lib/src/visual/views/settings/backup/backup_setup.view.dart index 52968435..4b09a0d9 100644 --- a/lib/src/visual/views/settings/backup/backup_setup.view.dart +++ b/lib/src/visual/views/settings/backup/backup_setup.view.dart @@ -1,10 +1,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:go_router/go_router.dart'; import 'package:twonly/locator.dart'; -import 'package:twonly/src/constants/routes.keys.dart'; -import 'package:twonly/src/services/backup/common.backup.dart'; +import 'package:twonly/src/services/backup.service.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/views/settings/backup/components/backup_setup.comp.dart'; @@ -24,15 +23,16 @@ class SetupBackupView extends StatefulWidget { } class _SetupBackupViewState extends State { - bool isLoading = false; - final TextEditingController passwordCtrl = TextEditingController(); - final TextEditingController repeatedPasswordCtrl = TextEditingController(); + bool _isLoading = false; + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _repeadedController = TextEditingController(); - Future onPressedEnableTwonlySafe() async { + Future _updateBackupPassword() async { setState(() { - isLoading = true; + _isLoading = true; }); - if (!await isSecurePassword(passwordCtrl.text)) { + + if (!await isSecurePassword(_passwordController.text)) { if (!mounted) return; final ignore = await showAlertDialog( context, @@ -44,7 +44,7 @@ class _SetupBackupViewState extends State { if (ignore) { if (mounted) { setState(() { - isLoading = false; + _isLoading = false; }); } return; @@ -52,11 +52,12 @@ class _SetupBackupViewState extends State { } await Future.delayed(const Duration(milliseconds: 100)); - await enableTwonlySafe(passwordCtrl.text); + await BackupService.updateBackupPassword(_passwordController.text); + await UserService.update((u) => u.isBackupEnabled = true); if (!mounted) return; setState(() { - isLoading = false; + _isLoading = false; }); if (widget.callBack != null) { @@ -100,34 +101,27 @@ class _SetupBackupViewState extends State { ), const SizedBox(height: 30), BackupPasswordTextField( - controller: passwordCtrl, + controller: _passwordController, labelText: context.lang.password, onChanged: (value) => setState(() {}), ), PasswordRequirementText( text: context.lang.backupPasswordRequirement, showError: - passwordCtrl.text.length < 8 && - passwordCtrl.text.isNotEmpty, + _passwordController.text.length < 8 && + _passwordController.text.isNotEmpty, ), const SizedBox(height: 5), BackupPasswordTextField( - controller: repeatedPasswordCtrl, + controller: _repeadedController, labelText: context.lang.passwordRepeated, onChanged: (value) => setState(() {}), ), PasswordRequirementText( text: context.lang.passwordRepeatedNotEqual, showError: - passwordCtrl.text != repeatedPasswordCtrl.text && - repeatedPasswordCtrl.text.isNotEmpty, - ), - const SizedBox(height: 10), - Center( - child: OutlinedButton( - onPressed: () => context.push(Routes.settingsBackupServer), - child: Text(context.lang.backupExpertSettings), - ), + _passwordController.text != _repeadedController.text && + _repeadedController.text.isNotEmpty, ), const SizedBox(height: 10), Text( @@ -139,13 +133,14 @@ class _SetupBackupViewState extends State { Center( child: FilledButton.icon( onPressed: - (!isLoading && - (passwordCtrl.text == repeatedPasswordCtrl.text && - passwordCtrl.text.length >= 8 || + (!_isLoading && + (_passwordController.text == + _repeadedController.text && + _passwordController.text.length >= 8 || !kReleaseMode)) - ? onPressedEnableTwonlySafe + ? _updateBackupPassword : null, - icon: isLoading + icon: _isLoading ? const SizedBox( height: 12, width: 12, @@ -159,21 +154,6 @@ class _SetupBackupViewState extends State { ), ), ), - const SizedBox(height: 12), - GestureDetector( - onTap: () { - if (widget.callBack != null) { - widget.callBack!(); - } else { - Navigator.pop(context); - } - }, - child: Text( - context.lang.skipForNow, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 8, color: Colors.grey), - ), - ), ], ), ), diff --git a/lib/src/visual/views/settings/backup/components/missing_backup_setup.comp.dart b/lib/src/visual/views/settings/backup/components/missing_backup_setup.comp.dart new file mode 100644 index 00000000..92402b41 --- /dev/null +++ b/lib/src/visual/views/settings/backup/components/missing_backup_setup.comp.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/views/settings/backup/backup_settings.view.dart'; + +class MissingBackupComp extends StatefulWidget { + const MissingBackupComp({super.key}); + + @override + State createState() => _MissingBackupCompState(); +} + +class _MissingBackupCompState extends State { + Future onTap() async { + await context.navPush(const BackupView()); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: userService.onUserUpdated, + builder: (context, snapshot) { + final user = userService.currentUser; + + if (user.currentSetupPage != null || user.isBackupEnabled) { + return const SizedBox.shrink(); + } + + return Container( + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + colors: [ + context.color.primaryContainer.withValues(alpha: 0.2), + context.color.primaryContainer.withValues(alpha: 0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + border: Border.all( + color: context.color.primary.withValues(alpha: 0.15), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: context.color.shadow.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(24), + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + SizedBox( + width: 68, + height: 68, + child: Container( + decoration: BoxDecoration( + color: context.color.primary.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.shield_rounded, + size: 32, + color: context.color.primary, + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.lang.missingBackupCardTitle, + style: TextStyle( + fontWeight: FontWeight.w900, + fontSize: 17, + color: context.color.onSurface, + letterSpacing: -0.2, + ), + ), + const SizedBox(height: 6), + Text( + context.lang.missingBackupCardDesc, + style: TextStyle( + fontSize: 13, + color: context.color.onSurfaceVariant, + height: 1.3, + ), + ), + const SizedBox(height: 14), + FilledButton.icon( + onPressed: onTap, + icon: const Icon( + Icons.arrow_forward_rounded, + size: 18, + ), + label: Text( + context.lang.missingBackupCardAction, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + style: FilledButton.styleFrom( + backgroundColor: context.color.primary, + foregroundColor: context.color.onPrimary, + minimumSize: const Size(0, 40), + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/visual/views/settings/chat/chat_reactions.view.dart b/lib/src/visual/views/settings/chat/chat_reactions.view.dart index c43dabe6..45719eeb 100644 --- a/lib/src/visual/views/settings/chat/chat_reactions.view.dart +++ b/lib/src/visual/views/settings/chat/chat_reactions.view.dart @@ -3,6 +3,7 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/animate_icon.comp.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; class ChatReactionSelectionView extends StatefulWidget { const ChatReactionSelectionView({super.key}); @@ -39,12 +40,7 @@ class _ChatReactionSelectionView extends State { user.preSelectedEmojies = _selectedEmojis; }); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.settingsPreSelectedReactionsError), - duration: const Duration(seconds: 3), - ), - ); + showSnackbar(context, context.lang.settingsPreSelectedReactionsError); } } setState(() {}); diff --git a/lib/src/visual/views/settings/help/contact_us.view.dart b/lib/src/visual/views/settings/help/contact_us.view.dart index 8f66ecb9..fa8011c6 100644 --- a/lib/src/visual/views/settings/help/contact_us.view.dart +++ b/lib/src/visual/views/settings/help/contact_us.view.dart @@ -13,6 +13,7 @@ import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/secure_storage.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/views/settings/help/contact_us/submit_message.view.dart'; import 'package:twonly/src/visual/views/settings/help/faq.view.dart'; @@ -124,9 +125,7 @@ class _ContactUsState extends State { } if (token == null) { if (!mounted) return null; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Could not upload the debug log!')), - ); + showSnackbar(context, 'Could not upload the debug log!'); setState(() { isLoading = false; }); diff --git a/lib/src/visual/views/settings/help/contact_us/submit_message.view.dart b/lib/src/visual/views/settings/help/contact_us/submit_message.view.dart index 91281ba3..104cc236 100644 --- a/lib/src/visual/views/settings/help/contact_us/submit_message.view.dart +++ b/lib/src/visual/views/settings/help/contact_us/submit_message.view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; class SubmitMessage extends StatefulWidget { const SubmitMessage({required this.fullMessage, super.key}); @@ -28,8 +29,10 @@ class _ContactUsState extends State { }); if (feedback.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please enter your message.')), + showSnackbar( + context, + 'Please enter a message.', + level: SnackbarLevel.info, ); return; } @@ -49,15 +52,16 @@ class _ContactUsState extends State { }); if (response.statusCode == 200) { - // Handle successful response - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.lang.contactUsSuccess)), + showSnackbar( + context, + context.lang.contactUsSuccess, + level: SnackbarLevel.success, ); Navigator.pop(context, true); } else { - // Handle error response - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Failed to submit feedback.')), + showSnackbar( + context, + 'Failed to submit feedback.', ); } } diff --git a/lib/src/visual/views/settings/help/diagnostics.view.dart b/lib/src/visual/views/settings/help/diagnostics.view.dart index f664368f..44645598 100644 --- a/lib/src/visual/views/settings/help/diagnostics.view.dart +++ b/lib/src/visual/views/settings/help/diagnostics.view.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; class DiagnosticsView extends StatefulWidget { @@ -29,21 +30,9 @@ class _DiagnosticsViewState extends State { } Future _deleteDebugLog() async { - if (await deleteLogFile()) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Log file deleted!'), - ), - ); - } else { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Log file does not exist.'), - ), - ); - } + await deleteLogFile(); + if (!mounted) return; + showSnackbar(context, 'Log file deleted!', level: SnackbarLevel.info); } @override @@ -244,8 +233,10 @@ class _LogViewerWidgetState extends State { return InkWell( onLongPress: () { Clipboard.setData(ClipboardData(text: e.line)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Copied line')), + showSnackbar( + context, + 'Copied line', + level: SnackbarLevel.info, ); }, child: Padding( @@ -307,8 +298,9 @@ class _LogEntry { var msg = trimmed; // Try to parse leading timestamp (YYYY-MM-DD HH:MM:SS.mmmmmm) - final tsRegex = - RegExp(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(.*)$'); + final tsRegex = RegExp( + r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(.*)$', + ); final mTs = tsRegex.firstMatch(trimmed); if (mTs != null) { try { diff --git a/lib/src/visual/views/settings/notification.view.dart b/lib/src/visual/views/settings/notification.view.dart index 4c33d9ee..c68eec4c 100644 --- a/lib/src/visual/views/settings/notification.view.dart +++ b/lib/src/visual/views/settings/notification.view.dart @@ -5,12 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hashlib/random.dart'; import 'package:twonly/locator.dart'; -import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/secure_storage.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; class NotificationView extends StatefulWidget { @@ -32,15 +30,11 @@ class _NotificationViewState extends State { await initFCMAfterAuthenticated(force: true); - final storedToken = await SecureStorage.instance.read( - key: SecureStorageKeys.googleFcm, - ); - await setupNotificationWithUsers(force: true); if (!mounted) return; - if (storedToken == null) { + if (userService.currentUser.fcmToken == null) { final platform = Platform.isAndroid ? "Google's" : "Apple's"; await showAlertDialog( context, diff --git a/lib/src/visual/views/settings/profile/profile.view.dart b/lib/src/visual/views/settings/profile/profile.view.dart index 100a9811..18b6781d 100644 --- a/lib/src/visual/views/settings/profile/profile.view.dart +++ b/lib/src/visual/views/settings/profile/profile.view.dart @@ -8,10 +8,9 @@ import 'package:go_router/go_router.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/services/backup/common.backup.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; @@ -71,15 +70,11 @@ class _ProfileViewState extends State { if (result.error == ErrorCode.UsernameAlreadyTaken || result.error == ErrorCode.UsernameNotValid) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - result.error == ErrorCode.UsernameAlreadyTaken - ? context.lang.errorUsernameAlreadyTaken - : context.lang.errorUsernameNotValid, - ), - duration: const Duration(seconds: 3), - ), + showSnackbar( + context, + result.error == ErrorCode.UsernameAlreadyTaken + ? context.lang.errorUsernameAlreadyTaken + : context.lang.errorUsernameNotValid, ); return; } @@ -89,10 +84,6 @@ class _ProfileViewState extends State { return; } - // as the username has changes, remove the old from the server and then upload it again. - await removeTwonlySafeFromServer(); - unawaited(performTwonlySafeBackup(force: true)); - await UserService.update( (u) => u ..username = username diff --git a/lib/src/visual/views/settings/subscription/additional_users.view.dart b/lib/src/visual/views/settings/subscription/additional_users.view.dart index 14cd4353..485bb8ea 100644 --- a/lib/src/visual/views/settings/subscription/additional_users.view.dart +++ b/lib/src/visual/views/settings/subscription/additional_users.view.dart @@ -12,6 +12,7 @@ import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/views/settings/subscription/select_additional_users.view.dart'; class AdditionalUsersView extends StatefulWidget { @@ -80,24 +81,20 @@ class _AdditionalUsersViewState extends State { ); if (contact != null && mounted) { if (res.error == ErrorCode.UserIsNotInFreePlan) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.lang.additionalUserAddErrorNotInFreePlan( - getContactDisplayName(contact), - ), - ), + showSnackbar( + context, + context.lang.additionalUserAddErrorNotInFreePlan( + getContactDisplayName(contact), ), + level: SnackbarLevel.info, ); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.lang.additionalUserAddError( - getContactDisplayName(contact), - ), - ), + showSnackbar( + context, + context.lang.additionalUserAddError( + getContactDisplayName(contact), ), + level: SnackbarLevel.info, ); } } @@ -231,14 +228,11 @@ class _AdditionalAccountState extends State { if (res.isSuccess) { widget.refresh(); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText( - context, - res.error as ErrorCode, - ), - ), + showSnackbar( + context, + errorCodeToText( + context, + res.error as ErrorCode, ), ); } diff --git a/lib/src/visual/views/shared/memory_item_slider.view.dart b/lib/src/visual/views/shared/memory_item_slider.view.dart index 1a13c11f..752dd775 100644 --- a/lib/src/visual/views/shared/memory_item_slider.view.dart +++ b/lib/src/visual/views/shared/memory_item_slider.view.dart @@ -9,6 +9,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; import 'package:twonly/src/visual/helpers/video_player_file.helper.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/save_to_gallery.dart'; @@ -82,13 +83,17 @@ class _MemoriesPhotoSliderViewState extends State { await saveImageToGallery(imageBytes); } if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.lang.galleryExportSuccess)), + showSnackbar( + context, + context.lang.galleryExportSuccess, + level: SnackbarLevel.success, ); } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('$e')), + showSnackbar( + context, + e.toString(), + level: SnackbarLevel.success, ); } } diff --git a/lib/src/visual/views/user_study/user_study_questionnaire.view.dart b/lib/src/visual/views/user_study/user_study_questionnaire.view.dart index 180c58ab..ed86d82e 100644 --- a/lib/src/visual/views/user_study/user_study_questionnaire.view.dart +++ b/lib/src/visual/views/user_study/user_study_questionnaire.view.dart @@ -7,6 +7,7 @@ import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/services/user_study.service.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/snackbar.dart'; class UserStudyQuestionnaireView extends StatefulWidget { const UserStudyQuestionnaireView({super.key}); @@ -60,10 +61,11 @@ class _UserStudyQuestionnaireViewState await handleUserStudyUpload(); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Vielen Dank für deine Teilnahme!')), + showSnackbar( + context, + 'Vielen Dank für deine Teilnahme!', + level: SnackbarLevel.success, ); - context.pop(); } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f9e95d67..bbf942dc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -148,23 +148,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atoi" version = "2.0.0" @@ -210,12 +193,6 @@ dependencies = [ "windows-link", ] -[[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" @@ -228,40 +205,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bech32" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" - -[[package]] -name = "bip39" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" -dependencies = [ - "bitcoin_hashes", - "serde", - "unicode-normalization", -] - -[[package]] -name = "bitcoin-io" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" - -[[package]] -name = "bitcoin_hashes" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" -dependencies = [ - "bitcoin-io", - "hex-conservative", - "serde", -] - [[package]] name = "bitflags" version = "2.11.1" @@ -300,21 +243,6 @@ dependencies = [ "hybrid-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 = "blurhash" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" - [[package]] name = "build-target" version = "0.4.0" @@ -339,27 +267,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "cc" version = "1.2.60" @@ -382,17 +295,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", -] - [[package]] name = "chacha20" version = "0.10.0" @@ -404,19 +306,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.44" @@ -439,7 +328,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", "inout 0.1.4", - "zeroize", ] [[package]] @@ -468,12 +356,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "combine" version = "4.6.7" @@ -531,17 +413,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-models" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4" -dependencies = [ - "hax-lib", - "pastey", - "rand 0.9.4", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -599,25 +470,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -633,18 +485,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[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 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.7" @@ -683,33 +523,6 @@ dependencies = [ "cmov", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest 0.10.7", - "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" @@ -761,7 +574,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -816,45 +628,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "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.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2 0.10.9", - "subtle", - "zeroize", -] - [[package]] name = "either" version = "1.15.0" @@ -864,27 +637,6 @@ dependencies = [ "serde", ] -[[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 0.10.7", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "embedded-io" version = "0.4.0" @@ -958,55 +710,12 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fast-thumbhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5243a22cce29dff488db8ef03d8e0e54dd06cd3e6d98475160dff390fa414de2" - [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1210,7 +919,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1220,22 +928,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", ] [[package]] @@ -1246,7 +940,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", "wasip2", "wasip3", @@ -1262,33 +956,12 @@ dependencies = [ "polyval", ] -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "hash32" version = "0.2.1" @@ -1341,43 +1014,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "hax-lib" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" -dependencies = [ - "hax-lib-macros", - "num-bigint", - "num-traits", -] - -[[package]] -name = "hax-lib-macros" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" -dependencies = [ - "hax-lib-macros-types", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "hax-lib-macros-types" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_json", - "uuid", -] - [[package]] name = "heapless" version = "0.7.17" @@ -1410,15 +1046,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" -dependencies = [ - "arrayvec", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -1455,75 +1082,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "hpke-rs" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ad6a58eb3e0ee30be8bfc7a9770ae98adcfa1d9bc820a5847732ce84f70837" -dependencies = [ - "hpke-rs-crypto", - "hpke-rs-libcrux", - "hpke-rs-rust-crypto", - "libcrux-sha3", - "log", - "rand_core 0.9.5", - "serde", - "subtle", - "tls_codec", - "zeroize", -] - -[[package]] -name = "hpke-rs-crypto" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a73a99d9008010d73289f41335a3f6e14fb8c04eaf60e9111b450463b1bbc7f" -dependencies = [ - "rand_core 0.9.5", - "zeroize", -] - -[[package]] -name = "hpke-rs-libcrux" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ce6b7e54aebe540faee869c67ee253bede44ea6cb67c6e72c7847d6c59f1df" -dependencies = [ - "hpke-rs-crypto", - "libcrux-aead", - "libcrux-ecdh", - "libcrux-hkdf", - "libcrux-kem", - "libcrux-traits", - "rand 0.10.1", - "rand_chacha 0.10.0", - "rand_core 0.10.1", - "zeroize", -] - -[[package]] -name = "hpke-rs-rust-crypto" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b28be6cba9081c7feda2651d51c2a900029798e78b4c1e093e792f4571a870" -dependencies = [ - "aes-gcm", - "chacha20poly1305", - "hkdf", - "hpke-rs-crypto", - "k256", - "p256", - "p384", - "rand 0.8.6", - "rand_chacha 0.3.1", - "rand_core 0.10.1", - "rand_core 0.6.4", - "sha2 0.10.9", - "subtle", - "x25519-dalek", - "zeroize", -] - [[package]] name = "humantime" version = "2.3.0" @@ -1672,34 +1230,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -1718,7 +1248,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array", ] @@ -1731,18 +1260,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -1825,25 +1342,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "elliptic-curve", -] - -[[package]] -name = "kamadak-exif" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" -dependencies = [ - "mutate_once", -] - [[package]] name = "keyring-core" version = "1.0.0" @@ -1874,222 +1372,6 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" -[[package]] -name = "libcrux-aead" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13297ce29869a5c0edab0378837b0fc5f88bf99a843712d9201c3b1150b3b476" -dependencies = [ - "libcrux-aesgcm", - "libcrux-chacha20poly1305", - "libcrux-secrets", - "libcrux-traits", -] - -[[package]] -name = "libcrux-aesgcm" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99f2a019dab4097585a7d4f5b9deebe46cd1e628b16a5bc4cb0ce35e1da334e6" -dependencies = [ - "libcrux-intrinsics", - "libcrux-platform", - "libcrux-secrets", - "libcrux-traits", -] - -[[package]] -name = "libcrux-chacha20poly1305" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc08d044676af21343b32b988411fa98dbb5cf65a03c9df478ced221bbdfdb1b" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", - "libcrux-poly1305", - "libcrux-secrets", - "libcrux-traits", -] - -[[package]] -name = "libcrux-curve25519" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1e5fd8476a6ed609d24ef42aee5ab6f99f7c65d054f92412da9f499e423299" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", - "libcrux-secrets", - "libcrux-traits", -] - -[[package]] -name = "libcrux-ecdh" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65f73ce79337c762eb38bbac91e4c9b9e60cf318e8501b812750c640814d45e" -dependencies = [ - "libcrux-curve25519", - "libcrux-p256", - "rand 0.9.4", -] - -[[package]] -name = "libcrux-hacl-rs" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6" -dependencies = [ - "libcrux-macros", -] - -[[package]] -name = "libcrux-hkdf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1a89ca0c89be3a268a921e47105fb7873badf7267f5e3ebf4ea46baedd73ef" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-hmac", - "libcrux-secrets", -] - -[[package]] -name = "libcrux-hmac" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7a242707d65960770bd7e14e4f18a92bdf0b967777dd404887db8d087a643b" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", - "libcrux-sha2", -] - -[[package]] -name = "libcrux-intrinsics" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5" -dependencies = [ - "core-models", - "hax-lib", -] - -[[package]] -name = "libcrux-kem" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12631592f491d22fd1a176d32b2c6edfb673998fd3987e9d95f8fa79ad2a737b" -dependencies = [ - "libcrux-curve25519", - "libcrux-ecdh", - "libcrux-ml-kem", - "libcrux-p256", - "libcrux-sha3", - "libcrux-traits", - "rand 0.9.4", -] - -[[package]] -name = "libcrux-macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "libcrux-ml-kem" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d" -dependencies = [ - "hax-lib", - "libcrux-intrinsics", - "libcrux-platform", - "libcrux-secrets", - "libcrux-sha3", - "libcrux-traits", - "rand 0.9.4", - "tls_codec", -] - -[[package]] -name = "libcrux-p256" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4778ba25cb08bb8a96bd100e19ed9aecf78337198fd176036e21042b2dd99bc" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", - "libcrux-secrets", - "libcrux-sha2", - "libcrux-traits", -] - -[[package]] -name = "libcrux-platform" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4" -dependencies = [ - "libc", -] - -[[package]] -name = "libcrux-poly1305" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02491808ee5b9db8cb65fad64ae0be812db64beef179d945c00c7787dc7dfcf9" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", -] - -[[package]] -name = "libcrux-secrets" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b" -dependencies = [ - "hax-lib", -] - -[[package]] -name = "libcrux-sha2" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d253473f259fc74a280c43f29c464f7e374abdf28b4942234dc707f529d4b7" -dependencies = [ - "libcrux-hacl-rs", - "libcrux-macros", - "libcrux-traits", -] - -[[package]] -name = "libcrux-sha3" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7" -dependencies = [ - "hax-lib", - "libcrux-intrinsics", - "libcrux-platform", - "libcrux-traits", -] - -[[package]] -name = "libcrux-traits" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f" -dependencies = [ - "libcrux-secrets", - "rand 0.9.4", -] - [[package]] name = "libm" version = "0.2.16" @@ -2166,77 +1448,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "mdk-core" -version = "0.8.0" -source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" -dependencies = [ - "base64", - "blurhash", - "chacha20poly1305", - "fast-thumbhash", - "hex", - "hkdf", - "image", - "kamadak-exif", - "mdk-macros", - "mdk-storage-traits", - "nostr", - "openmls", - "openmls_basic_credential", - "openmls_rust_crypto", - "openmls_traits", - "serde", - "sha2 0.10.9", - "thiserror 2.0.18", - "tls_codec", - "tracing", - "zeroize", -] - -[[package]] -name = "mdk-macros" -version = "0.8.0" -source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" - -[[package]] -name = "mdk-sqlite-storage" -version = "0.8.0" -source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" -dependencies = [ - "base64", - "getrandom 0.4.2", - "hex", - "keyring-core", - "mdk-storage-traits", - "nostr", - "openmls", - "openmls_traits", - "refinery", - "rusqlite", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "mdk-storage-traits" -version = "0.8.0" -source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" -dependencies = [ - "nostr", - "openmls", - "openmls_traits", - "pastey", - "postcard", - "serde", - "serde_json", - "thiserror 2.0.18", - "zeroize", -] - [[package]] name = "memchr" version = "2.8.0" @@ -2264,58 +1475,18 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - [[package]] name = "multimap" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "mutate_once" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" - [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "nostr" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1" -dependencies = [ - "base64", - "bech32", - "bip39", - "bitcoin_hashes", - "cbc", - "chacha20 0.9.1", - "chacha20poly1305", - "getrandom 0.2.17", - "hex", - "instant", - "scrypt 0.11.0", - "secp256k1", - "serde", - "serde_json", - "unicode-normalization", - "url", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2325,16 +1496,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[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.6" @@ -2418,79 +1579,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openmls" -version = "0.8.1" -source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" -dependencies = [ - "log", - "openmls_traits", - "rayon", - "serde", - "serde_bytes", - "thiserror 2.0.18", - "tls_codec", - "zeroize", -] - -[[package]] -name = "openmls_basic_credential" -version = "0.5.0" -source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" -dependencies = [ - "ed25519-dalek", - "openmls_traits", - "p256", - "rand 0.8.6", - "serde", - "tls_codec", -] - -[[package]] -name = "openmls_memory_storage" -version = "0.5.0" -source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" -dependencies = [ - "log", - "openmls_traits", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "openmls_rust_crypto" -version = "0.5.1" -source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" -dependencies = [ - "aes-gcm", - "chacha20poly1305", - "ed25519-dalek", - "hkdf", - "hmac 0.12.1", - "hpke-rs", - "hpke-rs-crypto", - "hpke-rs-rust-crypto", - "openmls_memory_storage", - "openmls_traits", - "p256", - "rand 0.8.6", - "rand_chacha 0.3.1", - "serde", - "sha2 0.10.9", - "thiserror 2.0.18", - "tls_codec", -] - -[[package]] -name = "openmls_traits" -version = "0.5.0" -source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" -dependencies = [ - "serde", - "tls_codec", -] - [[package]] name = "openssl-src" version = "300.5.0+3.5.0" @@ -2524,28 +1612,6 @@ dependencies = [ "log", ] -[[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 0.10.9", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "elliptic-curve", - "primeorder", -] - [[package]] name = "parking" version = "2.2.1" @@ -2575,39 +1641,12 @@ dependencies = [ "windows-link", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pastey" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - [[package]] name = "pbkdf2" version = "0.13.0" @@ -2683,30 +1722,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.2" @@ -2782,37 +1797,6 @@ dependencies = [ "syn", ] -[[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-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2892,18 +1876,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "pxfm" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.45" @@ -2913,12 +1885,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -2932,27 +1898,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20 0.10.0", + "chacha20", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -2967,26 +1923,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" -dependencies = [ - "ppv-lite86", - "rand_core 0.10.1", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -2996,41 +1932,12 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -3049,49 +1956,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "refinery" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee5133e5b207e5703c2a4a9dc9bd8c8f2cc74c4ac04ca5510acaa907012c77ac" -dependencies = [ - "refinery-core", - "refinery-macros", -] - -[[package]] -name = "refinery-core" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023a2a96d959c9b5b5da78e965bfdb1363b365bf5e84531a67d0eee827a702a3" -dependencies = [ - "async-trait", - "cfg-if", - "log", - "regex", - "rusqlite", - "serde", - "siphasher", - "thiserror 2.0.18", - "time", - "toml", - "url", - "walkdir", -] - -[[package]] -name = "refinery-macros" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56c2e960c8e47c7c5c30ad334afea8b5502da796a59e34d640d6239d876d924" -dependencies = [ - "proc-macro2", - "quote", - "refinery-core", - "regex", - "syn", -] - [[package]] name = "regex" version = "1.12.3" @@ -3121,16 +1985,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac 0.12.1", - "subtle", -] - [[package]] name = "rsa" version = "0.9.10" @@ -3151,20 +2005,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rusqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - [[package]] name = "rust_lib_twonly" version = "0.1.0" @@ -3178,16 +2018,13 @@ dependencies = [ "hkdf", "keyring-core", "libsqlite3-sys", - "mdk-core", - "mdk-sqlite-storage", - "mdk-storage-traits", "paste", "postcard", "pretty_env_logger", "prost-build", "protocols", "rand 0.10.1", - "scrypt 0.12.0", + "scrypt", "serde", "sha2 0.10.9", "sqlx", @@ -3242,15 +2079,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "salsa20" version = "0.11.0" @@ -3276,18 +2104,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "password-hash", - "pbkdf2 0.12.2", - "salsa20 0.10.2", - "sha2 0.10.9", -] - [[package]] name = "scrypt" version = "0.12.0" @@ -3295,45 +2111,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", - "pbkdf2 0.13.0", - "salsa20 0.11.0", + "pbkdf2", + "salsa20", "sha2 0.11.0", ] -[[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 = "secp256k1" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" -dependencies = [ - "rand 0.8.6", - "secp256k1-sys", - "serde", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -3373,16 +2155,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -3416,15 +2188,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3511,12 +2274,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - [[package]] name = "slab" version = "0.4.12" @@ -3939,28 +2696,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tls_codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" -dependencies = [ - "serde", - "tls_codec_derive", - "zeroize", -] - -[[package]] -name = "tls_codec_derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio" version = "1.52.1" @@ -4000,47 +2735,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tracing" version = "0.1.44" @@ -4175,7 +2869,6 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", ] [[package]] @@ -4184,17 +2877,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -4352,12 +3034,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - [[package]] name = "whoami" version = "1.6.1" @@ -4584,15 +3260,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4693,18 +3360,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[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 0.6.4", - "serde", - "zeroize", -] - [[package]] name = "yoke" version = "0.8.2" @@ -4856,18 +3511,3 @@ dependencies = [ "log", "simd-adler32", ] - -[[package]] -name = "zune-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index f3ef96ec..26ca7546 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -18,12 +18,18 @@ sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [ "derive", "json", ] } -mdk-core = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c", features = [ - "mip04", - "mip05", -] } -mdk-sqlite-storage = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } -mdk-storage-traits = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } +# mdk-core = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c", features = [ +# "mip04", +# "mip05", +# ] } +# mdk-sqlite-storage = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } +# mdk-storage-traits = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } +# nostr-sdk = { version = "0.44", features = [ +# "nip04", +# "nip44", +# "nip47", +# "nip59", +# ] } libsqlite3-sys = { version = "0.35.0", features = [ "bundled-sqlcipher-vendored-openssl", ] } diff --git a/rust/src/backup/backup_archive.rs b/rust/src/backup/backup_archive.rs index fc0b1f20..5e7e08f3 100644 --- a/rust/src/backup/backup_archive.rs +++ b/rust/src/backup/backup_archive.rs @@ -1,23 +1,25 @@ use crate::context::Context; use crate::database::Database; -use crate::error::{Result, TwonlyError}; -use crate::keys::{DatabaseKey, MainKey}; +use crate::error::Result; +use crate::keys::{DatabaseKey, KeyManager}; use std::fs::{remove_file, File}; use std::io::{copy, Cursor}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use walkdir::WalkDir; use zeroize::Zeroize; use zip::write::SimpleFileOptions; use zip::{CompressionMethod, ZipArchive, ZipWriter}; -struct BackupArchive {} +pub(crate) struct BackupArchive {} impl BackupArchive { - fn get_backup_files(ctx: &Context) -> Result)>> { + fn get_backup_files( + ctx: &Context, + keys: &KeyManager, + ) -> Result)>> { let config = ctx.get_config()?; let database_dir = PathBuf::from(&config.database_dir); let data_dir = PathBuf::from(&config.data_dir); - let keys = ctx.get_key_manager()?; let rust_db_key = keys.main_key.get_database_key(DatabaseKey::RustDb); Ok(vec![ @@ -38,7 +40,11 @@ impl BackupArchive { } std::fs::create_dir_all(&backup_data_dir)?; - for (file_name, source_dir, is_db, mut encryption_key) in Self::get_backup_files(ctx)? { + let keys = ctx.get_key_manager().await?; + + for (file_name, source_dir, is_db, mut encryption_key) in + Self::get_backup_files(ctx, &keys)? + { let file_path = source_dir.join(file_name); if !file_path.exists() { tracing::warn!( @@ -52,7 +58,7 @@ impl BackupArchive { let db = Database::new( &file_path.display().to_string(), encryption_key.as_deref(), - encryption_key.is_none(), + false, ) .await?; let backup_database_file = backup_data_dir.join(file_name).display().to_string(); @@ -65,10 +71,6 @@ impl BackupArchive { encryption_key.zeroize(); } - let mut keys = ctx.get_key_manager()?; - - let keys_serialized = postcard::to_allocvec(&keys)?; - let mut zip_data = Vec::new(); { @@ -76,9 +78,6 @@ impl BackupArchive { let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated); - zip.start_file(".key_manager.bin", options)?; - copy(&mut keys_serialized.as_slice(), &mut zip)?; - for entry in WalkDir::new(&backup_data_dir) { let entry = entry?; let path = entry.path(); @@ -99,26 +98,16 @@ impl BackupArchive { std::fs::write(&zip_path, keys.main_key.encrypt_backup(&zip_data))?; std::fs::remove_dir_all(&backup_data_dir)?; - keys.zeroize(); Ok(zip_path) } - pub(crate) async fn restore_from_backup( - ctx: &Context, - main_key_bytes: &[u8], - file_path: &PathBuf, - ) -> Result<()> { + pub(crate) async fn restore_from_backup(ctx: &Context, file_path: &Path) -> Result<()> { let data_dir = PathBuf::from(&ctx.get_config()?.data_dir); - - let main_key_arr: [u8; 32] = main_key_bytes - .try_into() - .map_err(|_| TwonlyError::Generic("Invalid main key length".to_string()))?; - - let mut main_key = MainKey::from_main_key(main_key_arr); + let key_manager = ctx.get_key_manager().await?; let encrypted_zip = std::fs::read(file_path)?; - let zip_content = main_key.decrypt_backup(&encrypted_zip)?; + let zip_content = key_manager.main_key.decrypt_backup(&encrypted_zip)?; let restore_temp_dir = data_dir.join("restore_temp"); @@ -134,15 +123,6 @@ impl BackupArchive { let mut file = archive.by_index(i)?; if file.is_file() { - let name = file.name().to_string(); - if name == ".key_manager.bin" { - let mut data = Vec::new(); - copy(&mut file, &mut data)?; - let key_manager: crate::keys::KeyManager = postcard::from_bytes(&data)?; - key_manager.store_to_keychain(ctx.get_secure_storage()?)?; - continue; - } - let enclosed_name = file.enclosed_name(); if let Some(name) = enclosed_name.as_ref().and_then(|p| p.file_name()) { let restored_file = restore_temp_dir.join(name); @@ -151,7 +131,7 @@ impl BackupArchive { } } - for (file_name, target_dir, is_db, _) in Self::get_backup_files(ctx)? { + for (file_name, target_dir, is_db, _) in Self::get_backup_files(ctx, &key_manager)? { let src = restore_temp_dir.join(file_name); if src.exists() { let dst = target_dir.join(file_name); @@ -166,7 +146,6 @@ impl BackupArchive { } } - main_key.zeroize(); std::fs::remove_dir_all(&restore_temp_dir)?; Ok(()) @@ -175,7 +154,9 @@ impl BackupArchive { #[cfg(test)] mod tests { - use crate::{database::tables::received_messages::ReceivedMessage, keys::KeyManager}; + use crate::{ + database::tables::received_messages::ReceivedMessage, secure_storage::SecureStorage, + }; use super::*; use tempfile::tempdir; @@ -194,16 +175,12 @@ mod tests { .unwrap(); // 1. Add some data - { + let original_login_token = { + let secure_storage = SecureStorage::new("testing"); let config = ctx.get_config().unwrap(); let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); - let mut key_manager = ctx.get_key_manager().unwrap(); - key_manager - .identity_keys - .push(crate::keys::IdentityKey::Nost()); - key_manager - .store_to_keychain(ctx.get_secure_storage().unwrap()) - .unwrap(); + let key_manager = ctx.get_key_manager().await.unwrap(); + key_manager.store_to_keychain(&secure_storage).unwrap(); let db = Database::new( &rust_db_path.display().to_string(), @@ -220,20 +197,18 @@ mod tests { // Add a file let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json"); std::fs::write(config_file, "original config").unwrap(); - } + key_manager.main_key.get_login_token() + }; // 2. Create backup let backup_path = BackupArchive::create_backup(&ctx).await.unwrap(); assert!(backup_path.exists()); - // Save the original main key bytes - let original_main_key = *ctx.get_key_manager().unwrap().main_key.as_bytes(); - // 3. Modify data (to simulate state before restore) { let config = ctx.get_config().unwrap(); let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); - let key_manager = ctx.get_key_manager().unwrap(); + let key_manager = ctx.get_key_manager().await.unwrap(); let db = Database::new( &rust_db_path.display().to_string(), Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), @@ -248,17 +223,10 @@ mod tests { let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json"); std::fs::write(config_file, "new config").unwrap(); - - // Delete old keys to ensure they will be actually restored - - let key_manager = KeyManager::generate().unwrap(); - key_manager - .store_to_keychain(&ctx.get_secure_storage().unwrap()) - .unwrap(); } // 4. Restore backup - BackupArchive::restore_from_backup(&ctx, &original_main_key, &backup_path) + BackupArchive::restore_from_backup(&ctx, &backup_path) .await .unwrap(); @@ -266,7 +234,7 @@ mod tests { { let config = ctx.get_config().unwrap(); let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); - let key_manager = ctx.get_key_manager().unwrap(); + let key_manager = ctx.get_key_manager().await.unwrap(); let db = Database::new( &rust_db_path.display().to_string(), Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), @@ -285,12 +253,7 @@ mod tests { let config_content = std::fs::read_to_string(config_file).unwrap(); assert_eq!(config_content, "original config"); - let key_manager = ctx.get_key_manager().unwrap(); - assert_eq!(key_manager.identity_keys.len(), 1); - match &key_manager.identity_keys[0] { - crate::keys::IdentityKey::Nost() => {} - _ => panic!("Wrong identity key!"), - } + assert_eq!(key_manager.main_key.get_login_token(), original_login_token); } } } diff --git a/rust/src/backup/backup_identity.rs b/rust/src/backup/backup_identity.rs new file mode 100644 index 00000000..1fa49bd7 --- /dev/null +++ b/rust/src/backup/backup_identity.rs @@ -0,0 +1,83 @@ +use crate::error::{Result, TwonlyError}; +use crate::keys::{BackupPasswordKeys, KeyManager}; +use crate::secure_storage::SecureStorage; +use aes_gcm::aead::rand_core::RngCore; +use aes_gcm::aead::{Aead, KeyInit, OsRng}; +use aes_gcm::{Aes256Gcm, Nonce}; + +pub(crate) struct BackupIdentity(); + +impl BackupIdentity { + pub(crate) fn encrypt_key_manager(key_manager: &KeyManager) -> Result> { + let Some(keys) = &key_manager.backup_password else { + return Err(TwonlyError::Generic("No backup password".into())); + }; + + let serialized_bytes = postcard::to_allocvec(key_manager)?; + + let key = aes_gcm::Key::::from_slice(&keys.encryption_key); + let cipher = Aes256Gcm::new(key); + + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + let ciphertext = cipher.encrypt(nonce, serialized_bytes.as_slice())?; + + let mut encrypted_bytes = vec![]; + encrypted_bytes.extend_from_slice(&nonce_bytes); + encrypted_bytes.extend_from_slice(&ciphertext); + + Ok(encrypted_bytes) + } + + pub(crate) fn restore_key_manager( + secure_storage: &SecureStorage, + backup_password_keys: &BackupPasswordKeys, + encrypted_bytes: &[u8], + ) -> Result<()> { + if encrypted_bytes.len() < 12 { + return Err(TwonlyError::Generic( + "Invalid encrypted backup length".into(), + )); + } + + let (nonce_bytes, ciphertext) = encrypted_bytes.split_at(12); + let nonce = Nonce::from_slice(nonce_bytes); + + let key = aes_gcm::Key::::from_slice(&backup_password_keys.encryption_key); + let cipher = Aes256Gcm::new(key); + + let decrypted_bytes = cipher.decrypt(nonce, ciphertext)?; + + let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?; + + key_manager.store_to_keychain(&secure_storage)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_backup_encryption_decryption() { + let secure_storage = SecureStorage::new("testing"); + let mut key_manager = KeyManager::generate().unwrap(); + let password = "my_secure_password"; + let salt = 10; + + let backup_keys = BackupPasswordKeys::from_password(password, salt).unwrap(); + key_manager.backup_password = Some(backup_keys.clone()); + + let encrypted = BackupIdentity::encrypt_key_manager(&key_manager).unwrap(); + + BackupIdentity::restore_key_manager(&secure_storage, &backup_keys, &encrypted).unwrap(); + + let restored = KeyManager::try_from_keychain(&secure_storage).unwrap(); + + assert_eq!(restored, key_manager); + } +} diff --git a/rust/src/backup/backup_password.rs b/rust/src/backup/backup_password.rs deleted file mode 100644 index 52264603..00000000 --- a/rust/src/backup/backup_password.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::error::{Result, TwonlyError}; -use crate::keys::KeyManager; -use crate::secure_storage::{self, SecureStorage}; -use aes_gcm::aead::rand_core::RngCore; -use aes_gcm::aead::{Aead, KeyInit, OsRng}; -use aes_gcm::{Aes256Gcm, Nonce}; -use mdk_core::key_packages; -use scrypt::{scrypt, Params}; -use serde::{Deserialize, Serialize}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -#[derive(Debug, Clone, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] -pub(crate) struct BackupPasswordKeys { - backup_id: [u8; 32], - encryption_key: [u8; 32], -} - -impl BackupPasswordKeys { - pub(crate) fn new(backup_id: [u8; 32], encryption_key: [u8; 32]) -> Self { - Self { - backup_id, - encryption_key, - } - } - - pub(crate) fn from_password(password: &str, username: &str) -> Result { - let params = Params::new(17, 8, 1)?; - let mut output = [0u8; 64]; - - scrypt( - password.as_bytes(), - username.as_bytes(), - ¶ms, - &mut output, - )?; - - let mut backup_id = [0u8; 32]; - let mut encryption_key = [0u8; 32]; - backup_id.copy_from_slice(&output[0..32]); - encryption_key.copy_from_slice(&output[32..64]); - - Ok(Self::new(backup_id, encryption_key)) - } - - fn encrypt_key_manager(key_manager: KeyManager) -> Result> { - let Some(keys) = &key_manager.backup_password else { - return Err(TwonlyError::Generic("No backup password".into())); - }; - - let serialized_bytes = postcard::to_allocvec(&key_manager)?; - - let key = aes_gcm::Key::::from_slice(&keys.encryption_key); - let cipher = Aes256Gcm::new(key); - - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - - let ciphertext = cipher.encrypt(nonce, serialized_bytes.as_slice())?; - - let mut encrypted_bytes = vec![]; - encrypted_bytes.extend_from_slice(&nonce_bytes); - encrypted_bytes.extend_from_slice(&ciphertext); - - Ok(encrypted_bytes) - } - - pub(crate) fn restore_key_manager( - secure_storage: SecureStorage, - encrypted_bytes: &[u8], - keys: &BackupPasswordKeys, - ) -> Result<()> { - if encrypted_bytes.len() < 12 { - return Err(TwonlyError::Generic( - "Invalid encrypted backup length".into(), - )); - } - - let (nonce_bytes, ciphertext) = encrypted_bytes.split_at(12); - let nonce = Nonce::from_slice(nonce_bytes); - - let key = aes_gcm::Key::::from_slice(&keys.encryption_key); - let cipher = Aes256Gcm::new(key); - - let decrypted_bytes = cipher.decrypt(nonce, ciphertext)?; - - let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?; - - key_manager.store_to_keychain(&secure_storage)?; - - Ok(()) - } -} - -#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] -pub(crate) struct BackupPlainTextContent { - pub(crate) user_id: i64, - pub(crate) key_manager: KeyManager, -} - -impl BackupPlainTextContent {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_backup_encryption_decryption() { - let mut key_manager = KeyManager::generate().unwrap(); - let password = "my_secure_password"; - let salt = "my_random_salt"; - - let keys = BackupPasswordKeys::from_password(password, salt).unwrap(); - key_manager.backup_password = Some(keys.clone()); - - let content = BackupPlainTextContent { - user_id: 12345, - key_manager, - }; - - let encrypted = content.get_encrypted_backup().unwrap(); - let decrypted = BackupPlainTextContent::from_encrypted_backup(&encrypted, &keys).unwrap(); - - assert_eq!(content.user_id, decrypted.user_id); - assert_eq!(content.key_manager.main_key, decrypted.key_manager.main_key); - } -} diff --git a/rust/src/backup/backup_passwordless/types.rs b/rust/src/backup/backup_passwordless/types.rs index 77952828..8cba833c 100644 --- a/rust/src/backup/backup_passwordless/types.rs +++ b/rust/src/backup/backup_passwordless/types.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use serde::{Deserialize, Serialize}; /// Send from the person who tries to recover their account. diff --git a/rust/src/backup/mod.rs b/rust/src/backup/mod.rs index 73396f42..512a160c 100644 --- a/rust/src/backup/mod.rs +++ b/rust/src/backup/mod.rs @@ -1,3 +1,3 @@ -mod backup_archive; -pub(crate) mod backup_password; -mod backup_passwordless; +pub(crate) mod backup_archive; +pub(crate) mod backup_identity; +pub(crate) mod backup_passwordless; diff --git a/rust/src/bridge/mod.rs b/rust/src/bridge/mod.rs index 3616b2e1..f675cbb7 100644 --- a/rust/src/bridge/mod.rs +++ b/rust/src/bridge/mod.rs @@ -11,6 +11,7 @@ use crate::context::Context; use crate::database::Database; use crate::error::Result; use crate::error::TwonlyError; +use crate::keys::KeyManager; use crate::secure_storage::SecureStorage; use crate::utils::Shared; use flutter_rust_bridge::frb; @@ -18,6 +19,7 @@ use protocols::user_discovery::UserDiscovery; pub use protocols::user_discovery::traits::AnnouncedUser; pub use protocols::user_discovery::traits::OtherPromotion; +use tokio::sync::Mutex; pub struct InitConfig { pub database_dir: String, @@ -46,8 +48,10 @@ pub(crate) struct TwonlyFlutter { pub(crate) config: InitConfig, pub(crate) user_discovery: Shared>, + #[allow(dead_code)] pub(crate) rust_db: Arc, pub(crate) secure_storage: SecureStorage, + pub(crate) key_manager: Arc>, } pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> { diff --git a/rust/src/bridge/wrapper/backup.rs b/rust/src/bridge/wrapper/backup.rs new file mode 100644 index 00000000..26f92441 --- /dev/null +++ b/rust/src/bridge/wrapper/backup.rs @@ -0,0 +1,90 @@ +use std::path::PathBuf; + +use crate::backup::backup_archive::BackupArchive; +use crate::backup::backup_identity::BackupIdentity; +use crate::bridge::get_twonly_flutter; +use crate::context::Context; +use crate::error::{Result, TwonlyError}; +pub use crate::keys::backup_password_keys::BackupPasswordKeys; + +pub struct RustBackupIdentity(); +pub struct RustBackupArchive(); + +impl RustBackupIdentity { + pub async fn get_backup_password_keys( + user_id: i64, + password: String, + ) -> Result { + BackupPasswordKeys::from_password(&password, user_id) + } + pub async fn get_backup_id() -> Option { + let key_manager = get_twonly_flutter().ok()?.key_manager.lock().await; + Some(hex::encode(key_manager.backup_password.clone()?.backup_id)) + } + + pub async fn set_backup_password_keys(user_id: i64, password: String) -> Result<()> { + let backup_keys = BackupPasswordKeys::from_password(&password, user_id)?; + let ctx = get_twonly_flutter()?; + let mut key_manager = ctx.key_manager.lock().await; + key_manager.backup_password = Some(backup_keys); + key_manager.store_to_keychain(&ctx.secure_storage)?; + Ok(()) + } + + pub async fn import_backup_password_keys( + backup_id: Vec, + encryption_key: Vec, + ) -> Result<()> { + let backup_id: [u8; 32] = backup_id + .try_into() + .map_err(|a: Vec| TwonlyError::WronKeySize(2, a.len()))?; + + let encryption_key: [u8; 32] = encryption_key + .try_into() + .map_err(|a: Vec| TwonlyError::WronKeySize(2, a.len()))?; + + let backup_keys = BackupPasswordKeys::new(backup_id, encryption_key); + let ctx = get_twonly_flutter()?; + let mut key_manager = ctx.key_manager.lock().await; + key_manager.backup_password = Some(backup_keys); + key_manager.store_to_keychain(&ctx.secure_storage)?; + Ok(()) + } + + pub async fn get_identity_backup_bytes() -> Result> { + let key_manager = get_twonly_flutter()?.key_manager.lock().await; + return BackupIdentity::encrypt_key_manager(&key_manager); + } + + pub async fn restore_identity_backup( + keys: BackupPasswordKeys, + encrypted_bytes: Vec, + ) -> Result<()> { + let ctx = get_twonly_flutter()?; + BackupIdentity::restore_key_manager(&ctx.secure_storage, &keys, &encrypted_bytes)?; + let restored = crate::keys::KeyManager::try_from_keychain(&ctx.secure_storage)?; + *ctx.key_manager.lock().await = restored; + Ok(()) + } +} +impl RustBackupArchive { + pub async fn create_backup_archive() -> Result<(String, String)> { + let ctx = Context::get_static()?; + let path = BackupArchive::create_backup(&ctx).await?; + let key_manager = get_twonly_flutter()?.key_manager.lock().await; + let token = hex::encode(key_manager.main_key.get_backup_download_token()); + Ok((token, path.canonicalize()?.to_string_lossy().to_string())) + } + + pub async fn restore_backup_archive(file_path: String) -> Result<()> { + let ctx = Context::get_static()?; + BackupArchive::restore_from_backup(ctx, &PathBuf::from(file_path)).await + } + + pub async fn get_backup_download_token() -> Option { + let key_manager = get_twonly_flutter().ok()?.key_manager.lock().await; + Some(hex::encode( + key_manager.main_key.get_backup_download_token(), + )) + } +} diff --git a/rust/src/bridge/wrapper/key_manager.rs b/rust/src/bridge/wrapper/key_manager.rs index efe1dc88..d18d61a2 100644 --- a/rust/src/bridge/wrapper/key_manager.rs +++ b/rust/src/bridge/wrapper/key_manager.rs @@ -1,12 +1,92 @@ -use crate::error::Result; -use crate::{bridge::get_twonly_flutter, keys::KeyManager}; +use std::collections::HashMap; -pub struct FlutterKeyManager {} +use crate::bridge::get_twonly_flutter; +use crate::error::{Result, TwonlyError}; +use crate::keys::SignalIdentityKey; -impl FlutterKeyManager { +pub struct RustKeyManager {} + +impl RustKeyManager { pub async fn get_login_token() -> Result> { - let ctx = get_twonly_flutter()?; - let key_manager = KeyManager::try_from_keychain(&ctx.secure_storage)?; + let key_manager = get_twonly_flutter()?.key_manager.lock().await; Ok(key_manager.main_key.get_login_token().to_vec()) } + + pub async fn import_signal_identity( + identity_key_pair_structure: Vec, + registration_id: i64, + signed_pre_key_store: HashMap>, + ) -> Result<()> { + let ctx = get_twonly_flutter()?; + let mut key_manager = ctx.key_manager.lock().await; + key_manager.signal_identity = Some(SignalIdentityKey { + identity_key_pair_structure, + registration_id, + pre_key_store: signed_pre_key_store, + }); + key_manager.store_to_keychain(&ctx.secure_storage)?; + Ok(()) + } + + pub async fn get_signal_identity() -> Result<(Vec, i64)> { + let ctx = get_twonly_flutter()?; + let key_manager = ctx.key_manager.lock().await; + if let Some(signal_identity) = &key_manager.signal_identity { + Ok(( + signal_identity.identity_key_pair_structure.to_owned(), + signal_identity.registration_id, + )) + } else { + Err(TwonlyError::SignalIdentityNotFound) + } + } + + pub async fn load_signed_prekey(signed_pre_key_id: i64) -> Result>> { + let ctx = get_twonly_flutter()?; + let key_manager = ctx.key_manager.lock().await; + if let Some(signal_identity) = &key_manager.signal_identity { + Ok(signal_identity + .pre_key_store + .get(&signed_pre_key_id) + .cloned()) + } else { + Err(TwonlyError::SignalIdentityNotFound) + } + } + + pub async fn store_signed_prekey(signed_pre_key_id: i64, record: Vec) -> Result<()> { + let ctx = get_twonly_flutter()?; + let mut key_manager = ctx.key_manager.lock().await; + if let Some(signal_identity) = &mut key_manager.signal_identity { + signal_identity + .pre_key_store + .insert(signed_pre_key_id, record); + key_manager.store_to_keychain(&ctx.secure_storage)?; + Ok(()) + } else { + Err(TwonlyError::SignalIdentityNotFound) + } + } + + pub async fn remove_signed_prekey(signed_pre_key_id: i64) -> Result<()> { + let ctx = get_twonly_flutter()?; + let mut key_manager = ctx.key_manager.lock().await; + if let Some(signal_identity) = &mut key_manager.signal_identity { + signal_identity.pre_key_store.remove(&signed_pre_key_id); + key_manager.store_to_keychain(&ctx.secure_storage)?; + Ok(()) + } else { + Err(TwonlyError::SignalIdentityNotFound) + } + } + + pub async fn load_signed_prekeys() -> Result>> { + let ctx = get_twonly_flutter()?; + let key_manager = ctx.key_manager.lock().await; + if let Some(signal_identity) = &key_manager.signal_identity { + Ok(signal_identity.pre_key_store.to_owned()) + } else { + Err(TwonlyError::SignalIdentityNotFound) + } + } } diff --git a/rust/src/bridge/wrapper/mod.rs b/rust/src/bridge/wrapper/mod.rs index e76f21d2..75173c53 100644 --- a/rust/src/bridge/wrapper/mod.rs +++ b/rust/src/bridge/wrapper/mod.rs @@ -1,2 +1,3 @@ +pub mod backup; pub mod key_manager; pub mod user_discovery; diff --git a/rust/src/context.rs b/rust/src/context.rs index 5b8a01c3..3ac01d50 100644 --- a/rust/src/context.rs +++ b/rust/src/context.rs @@ -11,7 +11,7 @@ use crate::{ }; use protocols::user_discovery::UserDiscovery; use std::{path::PathBuf, sync::Arc}; -use tokio::sync::OnceCell; +use tokio::sync::{Mutex, OnceCell}; use zeroize::Zeroize; use crate::{bridge::TwonlyFlutter, secure_storage::SecureStorage, standalone::TwonlyStandalone}; @@ -35,6 +35,7 @@ impl Context { Self::init_common(config, true).await } + #[allow(dead_code)] pub(crate) async fn init_standalone(config: InitConfig) -> Result<()> { Self::init_common(config, false).await } @@ -44,6 +45,8 @@ impl Context { database_dir: PathBuf, data_dir: PathBuf, ) -> Result { + use tokio::sync::Mutex; + std::fs::create_dir_all(&database_dir)?; std::fs::create_dir_all(&data_dir)?; @@ -73,6 +76,7 @@ impl Context { config, rust_db, secure_storage, + key_manager: Arc::new(Mutex::new(key_manager)), })) } @@ -92,7 +96,7 @@ impl Context { let rust_db_path = database_dir.join("rust_db.sqlite"); tracing::info!("Initialized twonly workspace."); - let _: Result<&'static Context> = GLOBAL_CONTEXT + let res: Result<&'static Context> = GLOBAL_CONTEXT .get_or_try_init(|| async { let key_manager = match KeyManager::try_from_keychain(&secure_storage) { Ok(key) => key, @@ -127,6 +131,7 @@ impl Context { config, secure_storage, rust_db, + key_manager: Arc::new(Mutex::new(key_manager)), user_discovery: Shared::new(UserDiscovery::new( UserDiscoveryStoreFlutter {}, UserDiscoveryUtilsFlutter {}, @@ -136,12 +141,13 @@ impl Context { Ok(Context::Standalone(TwonlyStandalone { config, rust_db, + key_manager: Arc::new(Mutex::new(key_manager)), secure_storage, })) } }) .await; - + res?; Ok(()) } @@ -149,12 +155,12 @@ impl Context { GLOBAL_CONTEXT.get().ok_or(TwonlyError::Initialization) } - pub(crate) fn get_secure_storage(&self) -> Result<&SecureStorage> { - match self { - Self::Flutter(twonly) => Ok(&twonly.secure_storage), - Self::Standalone(twonly) => Ok(&twonly.secure_storage), - } - } + // pub(crate) fn get_secure_storage(&self) -> Result<&SecureStorage> { + // match self { + // Self::Flutter(twonly) => Ok(&twonly.secure_storage), + // Self::Standalone(twonly) => Ok(&twonly.secure_storage), + // } + // } pub(crate) fn get_config(&self) -> Result<&InitConfig> { match self { @@ -163,7 +169,10 @@ impl Context { } } - pub(crate) fn get_key_manager(&self) -> Result { - KeyManager::try_from_keychain(self.get_secure_storage()?) + pub(crate) async fn get_key_manager(&self) -> Result> { + match self { + Self::Flutter(twonly) => Ok(twonly.key_manager.lock().await), + Self::Standalone(twonly) => Ok(twonly.key_manager.lock().await), + } } } diff --git a/rust/src/database/mod.rs b/rust/src/database/mod.rs index d78c47cb..46721db9 100644 --- a/rust/src/database/mod.rs +++ b/rust/src/database/mod.rs @@ -96,7 +96,6 @@ impl Database { #[cfg(test)] mod tests { use crate::database::tables::received_messages::ReceivedMessage; - use chrono::Utc; use super::*; use tempfile::tempdir; diff --git a/rust/src/database/tables/received_messages.rs b/rust/src/database/tables/received_messages.rs index 50502a02..6a4520de 100644 --- a/rust/src/database/tables/received_messages.rs +++ b/rust/src/database/tables/received_messages.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use chrono::{DateTime, Utc}; use sqlx::FromRow; diff --git a/rust/src/error.rs b/rust/src/error.rs index 599b15a1..7a688384 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -14,9 +14,15 @@ pub enum TwonlyError { #[error("Tried to access the wrong context")] WrongContext, + #[error("Tried to access signal identity while it does not exists")] + SignalIdentityNotFound, + #[error("init_flutter_callbacks was not called")] MissingCallbackInitialization, + #[error("wrong input key size. expected: {0} but got {1}")] + WronKeySize(usize, usize), + #[error("Could not find the given database")] DatabaseNotFound, diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index d5e35e75..f134f3a5 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1007286393; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1215442517; // Section: executor @@ -46,21 +46,6 @@ flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs -fn wire__crate__bridge__wrapper__key_manager__flutter_key_manager_get_login_token_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: "flutter_key_manager_get_login_token", 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| async move { - transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::key_manager::FlutterKeyManager::get_login_token().await?; Ok(output_ok) - })().await) - } }) -} fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -225,6 +210,298 @@ fn wire__crate__bridge__initialize_twonly_flutter_impl( }, ) } +fn wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_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: "rust_backup_archive_create_backup_archive", 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| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupArchive::create_backup_archive().await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_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: "rust_backup_archive_get_backup_download_token", 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| async move { + transform_result_sse::<_, ()>((move || async move { + let output_ok = Result::<_,()>::Ok(crate::bridge::wrapper::backup::RustBackupArchive::get_backup_download_token().await)?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_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: "rust_backup_archive_restore_backup_archive", 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_file_path = ::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupArchive::restore_backup_archive(api_file_path).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_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_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_backup_identity_get_backup_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); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let output_ok = Result::<_, ()>::Ok( + crate::bridge::wrapper::backup::RustBackupIdentity::get_backup_id() + .await, + )?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_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: "rust_backup_identity_get_backup_password_keys", 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_user_id = ::sse_decode(&mut deserializer); +let api_password = ::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupIdentity::get_backup_password_keys(api_user_id, api_password).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_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: "rust_backup_identity_get_identity_backup_bytes", 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| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupIdentity::get_identity_backup_bytes().await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_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: "rust_backup_identity_import_backup_password_keys", 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_backup_id = >::sse_decode(&mut deserializer); +let api_encryption_key = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupIdentity::import_backup_password_keys(api_backup_id, api_encryption_key).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_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: "rust_backup_identity_restore_identity_backup", 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_keys = ::sse_decode(&mut deserializer); +let api_encrypted_bytes = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupIdentity::restore_identity_backup(api_keys, api_encrypted_bytes).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_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: "rust_backup_identity_set_backup_password_keys", 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_user_id = ::sse_decode(&mut deserializer); +let api_password = ::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::backup::RustBackupIdentity::set_backup_password_keys(api_user_id, api_password).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_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: "rust_key_manager_get_login_token", + 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| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::bridge::wrapper::key_manager::RustKeyManager::get_login_token() + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_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: "rust_key_manager_get_signal_identity", 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| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::get_signal_identity().await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_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: "rust_key_manager_import_signal_identity", 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_identity_key_pair_structure = >::sse_decode(&mut deserializer); +let api_registration_id = ::sse_decode(&mut deserializer); +let api_signed_pre_key_store = >>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::import_signal_identity(api_identity_key_pair_structure, api_registration_id, api_signed_pre_key_store).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_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: "rust_key_manager_load_signed_prekey", 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_signed_pre_key_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 output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::load_signed_prekey(api_signed_pre_key_id).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_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: "rust_key_manager_load_signed_prekeys", 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| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::load_signed_prekeys().await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_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: "rust_key_manager_remove_signed_prekey", 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_signed_pre_key_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 output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::remove_signed_prekey(api_signed_pre_key_id).await?; Ok(output_ok) + })().await) + } }) +} +fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_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: "rust_key_manager_store_signed_prekey", 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_signed_pre_key_id = ::sse_decode(&mut deserializer); +let api_record = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { + let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::store_signed_prekey(api_signed_pre_key_id, api_record).await?; Ok(output_ok) + })().await) + } }) +} // Section: static_checks @@ -707,6 +984,14 @@ impl SseDecode for flutter_rust_bridge::DartOpaque { } } +impl SseDecode for std::collections::HashMap> { + // 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 { @@ -737,6 +1022,18 @@ impl SseDecode for crate::bridge::AnnouncedUser { } } +impl SseDecode for crate::keys::backup_password_keys::BackupPasswordKeys { + // 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_backupId = <[u8; 32]>::sse_decode(deserializer); + let mut var_encryptionKey = <[u8; 32]>::sse_decode(deserializer); + return crate::keys::backup_password_keys::BackupPasswordKeys { + backup_id: var_backupId, + encryption_key: var_encryptionKey, + }; + } +} + 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 { @@ -744,13 +1041,6 @@ impl SseDecode for bool { } } -impl SseDecode for crate::bridge::wrapper::key_manager::FlutterKeyManager { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - return crate::bridge::wrapper::key_manager::FlutterKeyManager {}; - } -} - impl SseDecode for crate::bridge::wrapper::user_discovery::FlutterUserDiscovery { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -820,6 +1110,29 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec<(i64, 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::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(<(i64, Vec)>::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 { @@ -897,6 +1210,54 @@ impl SseDecode for crate::bridge::OtherPromotion { } } +impl SseDecode for (i64, 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 var_field0 = ::sse_decode(deserializer); + let mut var_field1 = >::sse_decode(deserializer); + return (var_field0, var_field1); + } +} + +impl SseDecode for (Vec, i64) { + // 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_field0 = >::sse_decode(deserializer); + let mut var_field1 = ::sse_decode(deserializer); + return (var_field0, var_field1); + } +} + +impl SseDecode for (String, 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 var_field0 = ::sse_decode(deserializer); + let mut var_field1 = ::sse_decode(deserializer); + return (var_field0, var_field1); + } +} + +impl SseDecode for crate::bridge::wrapper::backup::RustBackupArchive { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + return crate::bridge::wrapper::backup::RustBackupArchive(); + } +} + +impl SseDecode for crate::bridge::wrapper::backup::RustBackupIdentity { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + return crate::bridge::wrapper::backup::RustBackupIdentity(); + } +} + +impl SseDecode for crate::bridge::wrapper::key_manager::RustKeyManager { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + return crate::bridge::wrapper::key_manager::RustKeyManager {}; + } +} + impl SseDecode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -911,6 +1272,14 @@ impl SseDecode for u8 { } } +impl SseDecode for [u8; 32] { + // 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::from_vec_to_array(inner); + } +} + 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 {} @@ -939,15 +1308,30 @@ fn pde_ffi_dispatcher_primary_impl( ) { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 1 => wire__crate__bridge__wrapper__key_manager__flutter_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len), -2 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len), -3 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len), -4 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(port, ptr, rust_vec_len, data_len), -5 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len), -6 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len), -7 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_verification_state_for_user_impl(port, ptr, rust_vec_len, data_len), -8 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len), -9 => wire__crate__bridge__initialize_twonly_flutter_impl(port, ptr, rust_vec_len, data_len), + 1 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len), +2 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len), +3 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(port, ptr, rust_vec_len, data_len), +4 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len), +5 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len), +6 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_verification_state_for_user_impl(port, ptr, rust_vec_len, data_len), +7 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len), +8 => wire__crate__bridge__initialize_twonly_flutter_impl(port, ptr, rust_vec_len, data_len), +9 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len), +10 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len), +11 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len), +12 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len), +13 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +14 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len), +15 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +16 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len), +17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +18 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len), +19 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len), +20 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len), +21 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len), +22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len), +23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len), +24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -989,19 +1373,23 @@ impl flutter_rust_bridge::IntoIntoDart> } } // Codec=Dco (DartCObject based), see doc to use other codecs -impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::key_manager::FlutterKeyManager { +impl flutter_rust_bridge::IntoDart for crate::keys::backup_password_keys::BackupPasswordKeys { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { - Vec::::new().into_dart() + [ + self.backup_id.into_into_dart().into_dart(), + self.encryption_key.into_into_dart().into_dart(), + ] + .into_dart() } } impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive - for crate::bridge::wrapper::key_manager::FlutterKeyManager + for crate::keys::backup_password_keys::BackupPasswordKeys { } -impl flutter_rust_bridge::IntoIntoDart - for crate::bridge::wrapper::key_manager::FlutterKeyManager +impl flutter_rust_bridge::IntoIntoDart + for crate::keys::backup_password_keys::BackupPasswordKeys { - fn into_into_dart(self) -> crate::bridge::wrapper::key_manager::FlutterKeyManager { + fn into_into_dart(self) -> crate::keys::backup_password_keys::BackupPasswordKeys { self } } @@ -1068,6 +1456,57 @@ impl flutter_rust_bridge::IntoIntoDart self.into() } } +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::backup::RustBackupArchive { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + Vec::::new().into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::wrapper::backup::RustBackupArchive +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::wrapper::backup::RustBackupArchive +{ + fn into_into_dart(self) -> crate::bridge::wrapper::backup::RustBackupArchive { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::backup::RustBackupIdentity { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + Vec::::new().into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::wrapper::backup::RustBackupIdentity +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::wrapper::backup::RustBackupIdentity +{ + fn into_into_dart(self) -> crate::bridge::wrapper::backup::RustBackupIdentity { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::key_manager::RustKeyManager { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + Vec::::new().into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::bridge::wrapper::key_manager::RustKeyManager +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::bridge::wrapper::key_manager::RustKeyManager +{ + fn into_into_dart(self) -> crate::bridge::wrapper::key_manager::RustKeyManager { + self + } +} impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1083,6 +1522,13 @@ impl SseEncode for flutter_rust_bridge::DartOpaque { } } +impl SseEncode for std::collections::HashMap> { + // 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) { @@ -1106,6 +1552,14 @@ impl SseEncode for crate::bridge::AnnouncedUser { } } +impl SseEncode for crate::keys::backup_password_keys::BackupPasswordKeys { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + <[u8; 32]>::sse_encode(self.backup_id, serializer); + <[u8; 32]>::sse_encode(self.encryption_key, 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) { @@ -1113,11 +1567,6 @@ impl SseEncode for bool { } } -impl SseEncode for crate::bridge::wrapper::key_manager::FlutterKeyManager { - // 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 crate::bridge::wrapper::user_discovery::FlutterUserDiscovery { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} @@ -1178,6 +1627,26 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec<(i64, 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 { + <(i64, Vec)>::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) { @@ -1240,6 +1709,45 @@ impl SseEncode for crate::bridge::OtherPromotion { } } +impl SseEncode for (i64, 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.0, serializer); + >::sse_encode(self.1, serializer); + } +} + +impl SseEncode for (Vec, i64) { + // 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.0, serializer); + ::sse_encode(self.1, serializer); + } +} + +impl SseEncode for (String, 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.0, serializer); + ::sse_encode(self.1, serializer); + } +} + +impl SseEncode for crate::bridge::wrapper::backup::RustBackupArchive { + // 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 crate::bridge::wrapper::backup::RustBackupIdentity { + // 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 crate::bridge::wrapper::key_manager::RustKeyManager { + // 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 u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1254,6 +1762,19 @@ impl SseEncode for u8 { } } +impl SseEncode for [u8; 32] { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode( + { + let boxed: Box<[_]> = Box::new(self); + boxed.into_vec() + }, + serializer, + ); + } +} + 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) {} diff --git a/rust/src/keys/backup_password_keys.rs b/rust/src/keys/backup_password_keys.rs new file mode 100644 index 00000000..81612f05 --- /dev/null +++ b/rust/src/keys/backup_password_keys.rs @@ -0,0 +1,38 @@ +use crate::error::Result; +use scrypt::{scrypt, Params}; +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Debug, Clone, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub struct BackupPasswordKeys { + pub backup_id: [u8; 32], + pub encryption_key: [u8; 32], +} + +impl BackupPasswordKeys { + pub(crate) fn new(backup_id: [u8; 32], encryption_key: [u8; 32]) -> Self { + Self { + backup_id, + encryption_key, + } + } + + pub(crate) fn from_password(password: &str, user_id: i64) -> Result { + let params = Params::new(16, 8, 1)?; + let mut output = [0u8; 64]; + + scrypt( + password.as_bytes(), + &user_id.to_be_bytes(), + ¶ms, + &mut output, + )?; + + let mut backup_id = [0u8; 32]; + let mut encryption_key = [0u8; 32]; + backup_id.copy_from_slice(&output[0..32]); + encryption_key.copy_from_slice(&output[32..64]); + + Ok(Self::new(backup_id, encryption_key)) + } +} diff --git a/rust/src/keys/identity_key.rs b/rust/src/keys/identity_key.rs deleted file mode 100644 index 5f32ad6e..00000000 --- a/rust/src/keys/identity_key.rs +++ /dev/null @@ -1,8 +0,0 @@ -use serde::{Deserialize, Serialize}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] -pub(crate) enum IdentityKey { - Nost(), - Signal(), -} diff --git a/rust/src/keys/identity_key/mod.rs b/rust/src/keys/identity_key/mod.rs new file mode 100644 index 00000000..06cfb6c0 --- /dev/null +++ b/rust/src/keys/identity_key/mod.rs @@ -0,0 +1 @@ +pub(crate) mod signal_identity_key; diff --git a/rust/src/keys/identity_key/signal_identity_key.rs b/rust/src/keys/identity_key/signal_identity_key.rs new file mode 100644 index 00000000..d3b0ae7b --- /dev/null +++ b/rust/src/keys/identity_key/signal_identity_key.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub(crate) struct SignalIdentityKey { + // https://github.com/MixinNetwork/libsignal_protocol_dart/blob/c95a1586057022acdbb9c76b1692d94cc549bcc7/protobuf/LocalStorageProtocol.proto#L85 + pub(crate) identity_key_pair_structure: Vec, + pub(crate) registration_id: i64, + pub(crate) pre_key_store: HashMap>, +} + +impl SignalIdentityKey {} + +impl Zeroize for SignalIdentityKey { + fn zeroize(&mut self) { + self.identity_key_pair_structure.zeroize(); + self.registration_id.zeroize(); + for value in self.pre_key_store.values_mut() { + value.zeroize(); + } + self.pre_key_store.clear(); + } +} + +impl Drop for SignalIdentityKey { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl ZeroizeOnDrop for SignalIdentityKey {} diff --git a/rust/src/keys/main_key.rs b/rust/src/keys/main_key.rs index 812f9464..0612a127 100644 --- a/rust/src/keys/main_key.rs +++ b/rust/src/keys/main_key.rs @@ -28,15 +28,6 @@ impl MainKey { Self { main_key } } - /// Initializes a MainKey from an existing main key. - pub fn from_main_key(main_key: [u8; 32]) -> Self { - Self { main_key } - } - - pub fn as_bytes(&self) -> &[u8; 32] { - &self.main_key - } - /// Download token required to download a backup. /// This ensures that the user who tries to download the backup must have knowledge over the /// main key @@ -71,22 +62,22 @@ impl MainKey { } /// Encrypts a newly generated media key using the derived Media Main Key. - pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec { - self.encrypt_with_info(b"media_main_key", media_key) - } + // pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec { + // self.encrypt_with_info(b"media_main_key", media_key) + // } /// Decrypts a wrapped media key using the derived Media Main Key. - pub fn decrypt_media_key(&self, wrapped_media_key: &[u8]) -> Result<[u8; 32]> { - let decrypted = self.decrypt_with_info(b"media_main_key", wrapped_media_key)?; + // pub fn decrypt_media_key(&self, wrapped_media_key: &[u8]) -> Result<[u8; 32]> { + // let decrypted = self.decrypt_with_info(b"media_main_key", wrapped_media_key)?; - if decrypted.len() != 32 { - return Err("Invalid decrypted key length".to_string())?; - } + // if decrypted.len() != 32 { + // return Err("Invalid decrypted key length".to_string())?; + // } - let mut result = [0u8; 32]; - result.copy_from_slice(&decrypted); - Ok(result) - } + // let mut result = [0u8; 32]; + // result.copy_from_slice(&decrypted); + // Ok(result) + // } fn derive_key(&self, info: &[u8]) -> [u8; 32] { let hk = Hkdf::::new(None, &self.main_key); @@ -130,13 +121,6 @@ impl MainKey { mod tests { use super::*; - #[test] - fn test_generate_and_from_main_key() { - let km = MainKey::generate(); - let km2 = MainKey::from_main_key(km.main_key); - assert_eq!(km.main_key, km2.main_key); - } - #[test] fn test_backup_encryption_decryption_success() { let km = MainKey::generate(); @@ -176,74 +160,74 @@ mod tests { ); } - #[test] - fn test_media_key_encryption_decryption_success() { - let km = MainKey::generate(); - let mut media_key = [0u8; 32]; - OsRng.fill_bytes(&mut media_key); + // #[test] + // fn test_media_key_encryption_decryption_success() { + // let km = MainKey::generate(); + // let mut media_key = [0u8; 32]; + // OsRng.fill_bytes(&mut media_key); - let encrypted = km.encrypt_media_key(&media_key); - let decrypted = km.decrypt_media_key(&encrypted).unwrap(); + // let encrypted = km.encrypt_media_key(&media_key); + // let decrypted = km.decrypt_media_key(&encrypted).unwrap(); - assert_eq!(media_key, decrypted); - } + // assert_eq!(media_key, decrypted); + // } - #[test] - fn test_media_key_decryption_tampered_payload_fails() { - let km = MainKey::generate(); - let mut media_key = [0u8; 32]; - OsRng.fill_bytes(&mut media_key); + // #[test] + // fn test_media_key_decryption_tampered_payload_fails() { + // let km = MainKey::generate(); + // let mut media_key = [0u8; 32]; + // OsRng.fill_bytes(&mut media_key); - let mut encrypted = km.encrypt_media_key(&media_key); + // let mut encrypted = km.encrypt_media_key(&media_key); - // Tamper with the ciphertext - let last_idx = encrypted.len() - 1; - encrypted[last_idx] ^= 1; + // // Tamper with the ciphertext + // let last_idx = encrypted.len() - 1; + // encrypted[last_idx] ^= 1; - let result = km.decrypt_media_key(&encrypted); - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Decryption failure"); - } + // let result = km.decrypt_media_key(&encrypted); + // assert!(result.is_err()); + // assert_eq!(result.unwrap_err().to_string(), "Decryption failure"); + // } - #[test] - fn test_media_key_decryption_too_short_fails() { - let km = MainKey::generate(); - let short_payload = vec![0u8; 10]; // Less than 12 bytes nonce + // #[test] + // fn test_media_key_decryption_too_short_fails() { + // let km = MainKey::generate(); + // let short_payload = vec![0u8; 10]; // Less than 12 bytes nonce - let result = km.decrypt_media_key(&short_payload); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid encrypted data length" - ); - } + // let result = km.decrypt_media_key(&short_payload); + // assert!(result.is_err()); + // assert_eq!( + // result.unwrap_err().to_string(), + // "Invalid encrypted data length" + // ); + // } - #[test] - fn test_media_key_decryption_wrong_decrypted_length_fails() { - let km = MainKey::generate(); + // #[test] + // fn test_media_key_decryption_wrong_decrypted_length_fails() { + // let km = MainKey::generate(); - // Manually encrypt a 31 byte payload - let hk = Hkdf::::new(None, &km.main_key); - let mut media_main_key = [0u8; 32]; - hk.expand(b"media_main_key", &mut media_main_key) - .expect("HKDF expand failed"); + // // Manually encrypt a 31 byte payload + // let hk = Hkdf::::new(None, &km.main_key); + // let mut media_main_key = [0u8; 32]; + // hk.expand(b"media_main_key", &mut media_main_key) + // .expect("HKDF expand failed"); - let key = Key::::from_slice(&media_main_key); - let cipher = Aes256Gcm::new(key); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - let payload = vec![0u8; 31]; - let ciphertext = cipher - .encrypt(&nonce, payload.as_ref()) - .expect("encryption failure"); + // let key = Key::::from_slice(&media_main_key); + // let cipher = Aes256Gcm::new(key); + // let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + // let payload = vec![0u8; 31]; + // let ciphertext = cipher + // .encrypt(&nonce, payload.as_ref()) + // .expect("encryption failure"); - let mut encrypted = nonce.to_vec(); - encrypted.extend_from_slice(&ciphertext); + // let mut encrypted = nonce.to_vec(); + // encrypted.extend_from_slice(&ciphertext); - let result = km.decrypt_media_key(&encrypted); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid decrypted key length" - ); - } + // let result = km.decrypt_media_key(&encrypted); + // assert!(result.is_err()); + // assert_eq!( + // result.unwrap_err().to_string(), + // "Invalid decrypted key length" + // ); + // } } diff --git a/rust/src/keys/mod.rs b/rust/src/keys/mod.rs index 252f735a..01230f14 100644 --- a/rust/src/keys/mod.rs +++ b/rust/src/keys/mod.rs @@ -1,13 +1,13 @@ +pub(crate) mod backup_password_keys; mod identity_key; mod main_key; -use crate::backup::backup_password::BackupPasswordKeys; use crate::error::Result; use crate::error::TwonlyError; -pub(crate) use crate::keys::identity_key::IdentityKey; +pub(crate) use crate::keys::backup_password_keys::BackupPasswordKeys; +pub(crate) use crate::keys::identity_key::signal_identity_key::SignalIdentityKey; pub(crate) use crate::keys::main_key::{DatabaseKey, MainKey}; use crate::secure_storage::SecureStorage; -use aes_gcm::Aes256Gcm; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -17,7 +17,7 @@ const KEY_MANAGER_ID: &str = "twonly_key_manager"; pub(crate) struct KeyManager { pub(crate) user_id: Option, pub(crate) main_key: MainKey, - pub(crate) identity_keys: Vec, + pub(crate) signal_identity: Option, pub(crate) backup_password: Option, } @@ -25,7 +25,7 @@ impl KeyManager { pub fn generate() -> Result { Ok(KeyManager { main_key: MainKey::generate(), - identity_keys: vec![], + signal_identity: None, backup_password: None, user_id: None, }) diff --git a/rust/src/secure_storage.rs b/rust/src/secure_storage.rs index 0d44de4a..7fd08870 100644 --- a/rust/src/secure_storage.rs +++ b/rust/src/secure_storage.rs @@ -92,15 +92,15 @@ impl SecureStorage { /// Deletes the secret associated with the given key from the secure keyring. /// /// If the key does not exist, this function returns `Ok(())` (idempotent). - pub fn delete(&self, key: &str) -> Result<(), String> { - let entry = self.get_entry(key)?; + // pub fn delete(&self, key: &str) -> Result<(), String> { + // let entry = self.get_entry(key)?; - match entry.delete_credential() { - Ok(()) => Ok(()), - Err(KeyringError::NoEntry) => Ok(()), - Err(e) => Err(format!("Failed to delete secret from keyring: {}", e)), - } - } + // match entry.delete_credential() { + // Ok(()) => Ok(()), + // Err(KeyringError::NoEntry) => Ok(()), + // Err(e) => Err(format!("Failed to delete secret from keyring: {}", e)), + // } + // } /// Helper to create a keyring entry with the appropriate platform modifiers. fn get_entry(&self, key: &str) -> Result { @@ -142,10 +142,10 @@ mod tests { assert_eq!(read_val, Some(secret.to_string())); // 3. Delete the secret - storage.delete(key).expect("Failed to delete secret"); + // storage.delete(key).expect("Failed to delete secret"); // 4. Verify the secret is gone - let after_delete = storage.read(key).expect("Failed to read after delete"); - assert_eq!(after_delete, None); + // let after_delete = storage.read(key).expect("Failed to read after delete"); + // assert_eq!(after_delete, None); } } diff --git a/rust/src/standalone.rs b/rust/src/standalone.rs index b351d324..ee5479ba 100644 --- a/rust/src/standalone.rs +++ b/rust/src/standalone.rs @@ -1,11 +1,17 @@ +use tokio::sync::Mutex; + use crate::bridge::InitConfig; use crate::database::Database; +use crate::keys::KeyManager; use crate::secure_storage::SecureStorage; use std::sync::Arc; pub(crate) struct TwonlyStandalone { #[allow(dead_code)] pub(crate) config: InitConfig, + #[allow(dead_code)] pub(crate) rust_db: Arc, + #[allow(dead_code)] pub(crate) secure_storage: SecureStorage, + pub(crate) key_manager: Arc>, } diff --git a/test/services/backup_service_test.dart b/test/services/backup_service_test.dart new file mode 100644 index 00000000..7f3c428c --- /dev/null +++ b/test/services/backup_service_test.dart @@ -0,0 +1,233 @@ +import 'dart:io'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:drift/native.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:twonly/core/bridge.dart' as bridge; +import 'package:twonly/core/bridge/wrapper/backup.dart'; +import 'package:twonly/core/frb_generated.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/callbacks/callbacks.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/json/backup.model.dart'; +import 'package:twonly/src/model/json/userdata.model.dart' + hide LastBackupUploadState; +import 'package:twonly/src/services/api.service.dart'; +import 'package:twonly/src/services/backup.service.dart'; +import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; + +void main() { + if (!Platform.isMacOS) { + return; + } + + TestWidgetsFlutterBinding.ensureInitialized(); + + late Directory tempDir; + late Map initialUserData; + + setUpAll(() async { + const channel = MethodChannel('com.bbflight.background_downloader'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (methodCall) async { + if (methodCall.method == 'enqueue') { + return true; + } + return null; + }); + + final dylibPath = + '${Directory.current.path}/rust/target/debug/librust_lib_twonly.dylib'; + if (File(dylibPath).existsSync()) { + await RustLib.init( + externalLibrary: ExternalLibrary.open(dylibPath), + ); + } else { + await RustLib.init(); + } + await initFlutterCallbacksForRust(); + + tempDir = Directory.systemTemp.createTempSync('twonly_backup_test_'); + AppEnvironment.initTesting( + customCacheDir: tempDir.path, + customSupportDir: tempDir.path, + ); + + await bridge.initializeTwonlyFlutter( + config: bridge.InitConfig( + databaseDir: tempDir.path, + dataDir: tempDir.path, + ), + ); + }); + + setUp(() async { + await locator.reset(); + final dbFile = File('${tempDir.path}/twonly.sqlite'); + locator + ..registerSingleton( + TwonlyDB(NativeDatabase(dbFile)), + ) + ..registerSingleton(UserService()) + ..registerSingleton(ApiService()); + + userService.currentUser = UserData( + userId: 1, + username: 'test_user', + displayName: 'Test User', + subscriptionPlan: 'Free', + currentSetupPage: null, + )..appVersion = 100; + userService.isUserCreated = true; + await UserService.save(userService.currentUser); + initialUserData = (await KeyValueStore.get('user'))!; + + await RustBackupIdentity.setBackupPasswordKeys( + password: 'strong_password', + userId: 1, + ); + }); + + tearDown(() async { + try { + await twonlyDB.close(); + } catch (_) {} + }); + + tearDownAll(() async { + if (tempDir.existsSync()) { + try { + tempDir.deleteSync(recursive: true); + } catch (_) {} + } + }); + + group('BackupService Tests', () { + test('getData returns default backup status initially', () async { + final data = await BackupService.getData(); + expect(data.identityState, LastBackupUploadState.none); + expect(data.archiveState, LastBackupUploadState.none); + expect(data.identityLastSuccessFull, isNull); + expect(data.archiveLastSuccessFull, isNull); + }); + + test( + 'onBackupUpdated stream emits events when backup status changes', + () async { + var eventEmitted = false; + final subscription = BackupService.onBackupUpdated.listen((_) { + eventEmitted = true; + }); + + final dummyTask = UploadTask(url: 'http://localhost', filename: 'test'); + await BackupService.handleBackupStatusUpdate( + 'backup_identity', + TaskStatusUpdate(dummyTask, TaskStatus.complete), + ); + + await Future.delayed(Duration.zero); + expect(eventEmitted, isTrue); + await subscription.cancel(); + }, + ); + + test( + 'handleBackupStatusUpdate updates identity and archive status correctly', + () async { + // Test success update for identity status + final dummyTask1 = UploadTask( + url: 'http://localhost', + filename: 'test', + ); + await BackupService.handleBackupStatusUpdate( + 'backup_identity', + TaskStatusUpdate(dummyTask1, TaskStatus.complete), + ); + + var data = await BackupService.getData(); + expect(data.identityState, LastBackupUploadState.success); + expect(data.identityLastSuccessFull, isNotNull); + + // Test failure update for archive status + final dummyTask2 = UploadTask( + url: 'http://localhost', + filename: 'test', + ); + await BackupService.handleBackupStatusUpdate( + 'backup_archive', + TaskStatusUpdate(dummyTask2, TaskStatus.failed), + ); + + data = await BackupService.getData(); + expect(data.archiveState, LastBackupUploadState.failed); + expect(data.archiveLastSuccessFull, isNotNull); + }, + ); + + test( + 'startFullBackupRecovery returns usernameNotValid for offline/unknown user', + () async { + final error = await BackupService.startFullBackupRecovery( + 'unknown_user', + 'password', + ); + expect(error, RecoveryError.usernameNotValid); + }, + ); + + test( + 'Full backup recovery flow restores identity and user.json archive successfully', + () async { + final initialBackupIdStr = await RustBackupIdentity.getBackupId(); + + // 1. Create backups of baseline state purely natively to avoid background backup races + final identityBytes = await RustBackupIdentity.getIdentityBackupBytes(); + final (_, archivePath) = await RustBackupArchive.createBackupArchive(); + + // 2. Tamper with user.json data and verify alteration + await KeyValueStore.put( + 'user', + {'changed': true, 'username': 'tampered'}, + ); + + final changedUserData = await KeyValueStore.get('user'); + expect(changedUserData?['changed'], isTrue); + + // 3. Trigger a change of the key_manager before restoring + await RustBackupIdentity.importBackupPasswordKeys( + backupId: List.filled(32, 1), + encryptionKey: List.filled(32, 1), + ); + + final changedBackupIdStr = await RustBackupIdentity.getBackupId(); + expect(changedBackupIdStr, isNot(equals(initialBackupIdStr))); + + // 4. Restore identity and archive + final backupKeys = await RustBackupIdentity.getBackupPasswordKeys( + userId: 1, + password: 'strong_password', + ); + await RustBackupIdentity.restoreIdentityBackup( + keys: backupKeys, + encryptedBytes: identityBytes, + ); + + await RustBackupArchive.restoreBackupArchive(filePath: archivePath); + + final restoredBackupIdStr = await RustBackupIdentity.getBackupId(); + expect(restoredBackupIdStr, equals(initialBackupIdStr)); + + // 5. Verify user.json data is fully restored + final restoredUserData = await KeyValueStore.get('user'); + expect( + restoredUserData?['username'], + equals(initialUserData['username']), + ); + }, + ); + }); +} diff --git a/test/services/group_services_test.dart b/test/services/group_service_test.dart similarity index 98% rename from test/services/group_services_test.dart rename to test/services/group_service_test.dart index ffe8f65f..2d603ae4 100644 --- a/test/services/group_services_test.dart +++ b/test/services/group_service_test.dart @@ -10,7 +10,7 @@ import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/json/userdata.model.dart'; import 'package:twonly/src/services/api.service.dart'; -import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/services/user.service.dart'; class RealHttpOverrides extends HttpOverrides { diff --git a/test/utils/key_value_test.dart b/test/utils/key_value_test.dart new file mode 100644 index 00000000..a6a47983 --- /dev/null +++ b/test/utils/key_value_test.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; + +void main() { + late Directory tempDir; + + setUp(() { + tempDir = Directory.systemTemp.createTempSync('twonly_keyvalue_test_'); + AppEnvironment.initTesting( + customCacheDir: tempDir.path, + customSupportDir: tempDir.path, + ); + }); + + tearDown(() { + if (tempDir.existsSync()) { + try { + tempDir.deleteSync(recursive: true); + } catch (_) {} + } + }); + + group('KeyValueStore Tests', () { + test('get returns null for non-existent key', () async { + final result = await KeyValueStore.get('non_existent'); + expect(result, isNull); + }); + + test('put stores value and get retrieves it correctly', () async { + const key = 'test_key'; + final value = {'name': 'twonly', 'version': 1}; + + await KeyValueStore.put(key, value); + + final retrieved = await KeyValueStore.get(key); + expect(retrieved, isNotNull); + expect(retrieved?['name'], equals('twonly')); + expect(retrieved?['version'], equals(1)); + }); + + test('delete removes stored value successfully', () async { + const key = 'delete_key'; + final value = {'data': 'to_be_deleted'}; + + await KeyValueStore.put(key, value); + expect(await KeyValueStore.get(key), isNotNull); + + await KeyValueStore.delete(key); + expect(await KeyValueStore.get(key), isNull); + }); + + test('delete on non-existent key completes without error', () async { + await expectLater(KeyValueStore.delete('non_existent'), completes); + }); + + test('put overwrites existing value', () async { + const key = 'overwrite_key'; + final initialValue = {'status': 'initial'}; + final updatedValue = {'status': 'updated'}; + + await KeyValueStore.put(key, initialValue); + var retrieved = await KeyValueStore.get(key); + expect(retrieved?['status'], equals('initial')); + + await KeyValueStore.put(key, updatedValue); + retrieved = await KeyValueStore.get(key); + expect(retrieved?['status'], equals('updated')); + }); + + test('get handles corrupted JSON file gracefully by deleting it', () async { + const key = 'corrupt_key'; + final file = File('${tempDir.path}/keyvalue/$key.json'); + await file.parent.create(recursive: true); + await file.writeAsString('invalid json content'); + + expect(file.existsSync(), isTrue); + + final retrieved = await KeyValueStore.get(key); + expect(retrieved, isNull); + expect(file.existsSync(), isFalse); + }); + }); +}