mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 09:14:06 +00:00
remove legacy signal encryption
This commit is contained in:
parent
da9752d317
commit
e43169c2d2
94 changed files with 1625 additions and 6501 deletions
|
|
@ -63,25 +63,6 @@ dependencies:
|
||||||
git: https://github.com/Pyozer/dots_indicator.git
|
git: https://github.com/Pyozer/dots_indicator.git
|
||||||
commit: 508f5883ac79bdbc10254092de3f28f571d261cd
|
commit: 508f5883ac79bdbc10254092de3f28f571d261cd
|
||||||
commit: 4a90e557630b28834479ed9c64a9d2d0185d8e48
|
commit: 4a90e557630b28834479ed9c64a9d2d0185d8e48
|
||||||
libsignal_protocol_dart:
|
|
||||||
git: https://github.com/MixinNetwork/libsignal_protocol_dart.git
|
|
||||||
dependencies:
|
|
||||||
adaptive_number:
|
|
||||||
git: https://github.com/lemoony/adaptive_number_dart
|
|
||||||
commit: ea9178fdd4d82ac45cf0ec966ac870dae661124f
|
|
||||||
ed25519_edwards:
|
|
||||||
git: https://github.com/Tougee/ed25519.git
|
|
||||||
commit: 7353ba759ea9f4646cbf481c2ef949625c8ce4cf
|
|
||||||
optional:
|
|
||||||
git: https://github.com/tonio-ramirez/optional.dart.git
|
|
||||||
commit: 71c638891ce4f2aff35c7387727989f31f9d877d
|
|
||||||
pointycastle:
|
|
||||||
git: https://github.com/bcgit/pc-dart.git
|
|
||||||
commit: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
|
|
||||||
x25519:
|
|
||||||
git: https://github.com/Tougee/curve25519.git
|
|
||||||
commit: ecb1d357714537bba6e276ef45f093846d4beaee
|
|
||||||
commit: c95a1586057022acdbb9c76b1692d94cc549bcc7
|
|
||||||
lottie:
|
lottie:
|
||||||
git: https://github.com/xvrh/lottie-flutter.git
|
git: https://github.com/xvrh/lottie-flutter.git
|
||||||
commit: 127bc29f2c6bd8b32ec4064a09e54e6b31cd0a88
|
commit: 127bc29f2c6bd8b32ec4064a09e54e6b31cd0a88
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,10 @@ void main() {
|
||||||
// Initialize global variables
|
// Initialize global variables
|
||||||
await initBackgroundExecution();
|
await initBackgroundExecution();
|
||||||
|
|
||||||
// Try to connect to the API server
|
// Check the API connection state
|
||||||
final connected = await apiService.connect();
|
final state = await RustApi.connectionState();
|
||||||
|
|
||||||
// Print out the result or test it
|
// Print out the result or test it
|
||||||
expect(connected, isA<bool>());
|
expect(state, isA<ApiConnectionState>());
|
||||||
|
|
||||||
// We can also check if it's connected
|
|
||||||
// Depending on your test environment, this might be true or false
|
|
||||||
// if the server is unreachable without further setup
|
|
||||||
// expect(apiService.isConnected, isA<bool>());
|
|
||||||
|
|
||||||
// Close the connection after the test
|
|
||||||
if (apiService.isConnected) {
|
|
||||||
await apiService.close(() {});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,14 @@ class RustApi {
|
||||||
static Future<void> downloadPendingMedia() =>
|
static Future<void> downloadPendingMedia() =>
|
||||||
RustLib.instance.api.crateBridgeApiRustApiDownloadPendingMedia();
|
RustLib.instance.api.crateBridgeApiRustApiDownloadPendingMedia();
|
||||||
|
|
||||||
|
static Future<void> establishSignalSession({
|
||||||
|
required PlatformInt64 contactId,
|
||||||
|
Uint8List? expectedPublicKey,
|
||||||
|
}) => RustLib.instance.api.crateBridgeApiRustApiEstablishSignalSession(
|
||||||
|
contactId: contactId,
|
||||||
|
expectedPublicKey: expectedPublicKey,
|
||||||
|
);
|
||||||
|
|
||||||
static Stream<ApiEvent> events() =>
|
static Stream<ApiEvent> events() =>
|
||||||
RustLib.instance.api.crateBridgeApiRustApiEvents();
|
RustLib.instance.api.crateBridgeApiRustApiEvents();
|
||||||
|
|
||||||
|
|
@ -330,9 +338,9 @@ class RustApi {
|
||||||
required PlatformInt64 contactId,
|
required PlatformInt64 contactId,
|
||||||
required List<int> content,
|
required List<int> content,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
required bool onlySendIfNoReceiptsAreOpen,
|
bool? onlySendIfNoReceiptsAreOpen,
|
||||||
required bool onlyReturnEncryptedData,
|
bool? onlyReturnEncryptedData,
|
||||||
required bool blocking,
|
bool? blocking,
|
||||||
}) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContent(
|
}) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContent(
|
||||||
contactId: contactId,
|
contactId: contactId,
|
||||||
content: content,
|
content: content,
|
||||||
|
|
@ -346,7 +354,7 @@ class RustApi {
|
||||||
required String groupId,
|
required String groupId,
|
||||||
required List<int> content,
|
required List<int> content,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
required bool onlySendIfNoReceiptsAreOpen,
|
bool? onlySendIfNoReceiptsAreOpen,
|
||||||
}) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContentToGroup(
|
}) => RustLib.instance.api.crateBridgeApiRustApiSendEncryptedContentToGroup(
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
content: content,
|
content: content,
|
||||||
|
|
@ -410,6 +418,8 @@ class RustApi {
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<void> uploadPqcPreKeys({
|
static Future<void> uploadPqcPreKeys({
|
||||||
|
required List<int> publicIdentityKey,
|
||||||
|
required PlatformInt64 registrationId,
|
||||||
required PlatformInt64 eccSignedPrekeyId,
|
required PlatformInt64 eccSignedPrekeyId,
|
||||||
required List<int> eccSignedPrekey,
|
required List<int> eccSignedPrekey,
|
||||||
required List<int> eccSignedPrekeySignature,
|
required List<int> eccSignedPrekeySignature,
|
||||||
|
|
@ -418,6 +428,8 @@ class RustApi {
|
||||||
required List<int> kyberSignedPrekeySignature,
|
required List<int> kyberSignedPrekeySignature,
|
||||||
required List<PqcPreKeyInput> prekeys,
|
required List<PqcPreKeyInput> prekeys,
|
||||||
}) => RustLib.instance.api.crateBridgeApiRustApiUploadPqcPreKeys(
|
}) => RustLib.instance.api.crateBridgeApiRustApiUploadPqcPreKeys(
|
||||||
|
publicIdentityKey: publicIdentityKey,
|
||||||
|
registrationId: registrationId,
|
||||||
eccSignedPrekeyId: eccSignedPrekeyId,
|
eccSignedPrekeyId: eccSignedPrekeyId,
|
||||||
eccSignedPrekey: eccSignedPrekey,
|
eccSignedPrekey: eccSignedPrekey,
|
||||||
eccSignedPrekeySignature: eccSignedPrekeySignature,
|
eccSignedPrekeySignature: eccSignedPrekeySignature,
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,12 @@ import '../user_config.dart';
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
|
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
|
||||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Api`, `FlutterCallbacks`, `LegacySignal`, `Logging`
|
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Api`, `FlutterCallbacks`, `Logging`
|
||||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`
|
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`
|
||||||
|
|
||||||
Future<void> initFlutterCallbacks({
|
Future<void> initFlutterCallbacks({
|
||||||
required int callbackId,
|
required int callbackId,
|
||||||
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
|
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
|
||||||
required FutureOr<LegacySignalDecryptResult> Function(
|
|
||||||
PlatformInt64,
|
|
||||||
Uint8List,
|
|
||||||
int,
|
|
||||||
)
|
|
||||||
legacySignalDecrypt,
|
|
||||||
required FutureOr<LegacySignalEncryptResult?> Function(
|
|
||||||
PlatformInt64,
|
|
||||||
Uint8List,
|
|
||||||
)
|
|
||||||
legacySignalEncrypt,
|
|
||||||
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
|
||||||
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
||||||
apiMediaAction,
|
apiMediaAction,
|
||||||
required FutureOr<void> Function(PlatformInt64, Uint8List)
|
required FutureOr<void> Function(PlatformInt64, Uint8List)
|
||||||
|
|
@ -36,59 +24,9 @@ Future<void> initFlutterCallbacks({
|
||||||
}) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks(
|
}) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks(
|
||||||
callbackId: callbackId,
|
callbackId: callbackId,
|
||||||
loggingGetStreamSink: loggingGetStreamSink,
|
loggingGetStreamSink: loggingGetStreamSink,
|
||||||
legacySignalDecrypt: legacySignalDecrypt,
|
|
||||||
legacySignalEncrypt: legacySignalEncrypt,
|
|
||||||
apiResyncSignalSession: apiResyncSignalSession,
|
|
||||||
apiMediaAction: apiMediaAction,
|
apiMediaAction: apiMediaAction,
|
||||||
apiVerificationProof: apiVerificationProof,
|
apiVerificationProof: apiVerificationProof,
|
||||||
apiCreatePushAvatars: apiCreatePushAvatars,
|
apiCreatePushAvatars: apiCreatePushAvatars,
|
||||||
apiMediaReceived: apiMediaReceived,
|
apiMediaReceived: apiMediaReceived,
|
||||||
apiUserConfigChanged: apiUserConfigChanged,
|
apiUserConfigChanged: apiUserConfigChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
class LegacySignalDecryptResult {
|
|
||||||
/// Serialized `EncryptedContent` when legacy Signal decryption succeeded.
|
|
||||||
final Uint8List? plaintext;
|
|
||||||
|
|
||||||
/// Serialized protobuf enum value for `DecryptionErrorMessage.Type`.
|
|
||||||
final int? decryptionErrorType;
|
|
||||||
|
|
||||||
const LegacySignalDecryptResult({
|
|
||||||
this.plaintext,
|
|
||||||
this.decryptionErrorType,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => plaintext.hashCode ^ decryptionErrorType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is LegacySignalDecryptResult &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
plaintext == other.plaintext &&
|
|
||||||
decryptionErrorType == other.decryptionErrorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LegacySignalEncryptResult {
|
|
||||||
final Uint8List ciphertext;
|
|
||||||
|
|
||||||
/// `Message.Type.CIPHERTEXT` or `Message.Type.PREKEY_BUNDLE`.
|
|
||||||
final int messageType;
|
|
||||||
|
|
||||||
const LegacySignalEncryptResult({
|
|
||||||
required this.ciphertext,
|
|
||||||
required this.messageType,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => ciphertext.hashCode ^ messageType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is LegacySignalEncryptResult &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
ciphertext == other.ciphertext &&
|
|
||||||
messageType == other.messageType;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -47,36 +47,15 @@ class RustKeyManager {
|
||||||
static Future<void> importSignalIdentity({
|
static Future<void> importSignalIdentity({
|
||||||
required List<int> identityKeyPairStructure,
|
required List<int> identityKeyPairStructure,
|
||||||
required PlatformInt64 registrationId,
|
required PlatformInt64 registrationId,
|
||||||
required Map<PlatformInt64, Uint8List> signedPreKeyStore,
|
|
||||||
}) => RustLib.instance.api
|
}) => RustLib.instance.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity(
|
.crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity(
|
||||||
identityKeyPairStructure: identityKeyPairStructure,
|
identityKeyPairStructure: identityKeyPairStructure,
|
||||||
registrationId: registrationId,
|
registrationId: registrationId,
|
||||||
signedPreKeyStore: signedPreKeyStore,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<Uint8List?> loadSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<Map<PlatformInt64, Uint8List>> loadSignedPrekeys() => RustLib
|
|
||||||
.instance
|
|
||||||
.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
|
||||||
|
|
||||||
static Future<void> removeKeyManager() => RustLib.instance.api
|
static Future<void> removeKeyManager() => RustLib.instance.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager();
|
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager();
|
||||||
|
|
||||||
static Future<void> removeSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Serialize the key_manager. Needed for the passwordless_recovery feature.
|
/// Serialize the key_manager. Needed for the passwordless_recovery feature.
|
||||||
static Future<Uint8List> serialize() => RustLib.instance.api
|
static Future<Uint8List> serialize() => RustLib.instance.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerSerialize();
|
.crateBridgeWrapperKeyManagerRustKeyManagerSerialize();
|
||||||
|
|
@ -86,15 +65,6 @@ class RustKeyManager {
|
||||||
.api
|
.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerSetUserId(userId: userId);
|
.crateBridgeWrapperKeyManagerRustKeyManagerSetUserId(userId: userId);
|
||||||
|
|
||||||
static Future<void> storeSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
required List<int> record,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
record: record,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => 0;
|
int get hashCode => 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,16 @@ class RustSignal {
|
||||||
static Future<List<FrbPqcPreKey>> generatePqcPrekeys() => RustLib.instance.api
|
static Future<List<FrbPqcPreKey>> generatePqcPrekeys() => RustLib.instance.api
|
||||||
.crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
.crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
||||||
|
|
||||||
|
static Future<Uint8List?> getContactPublicKey({
|
||||||
|
required PlatformInt64 contactId,
|
||||||
|
}) => RustLib.instance.api
|
||||||
|
.crateBridgeWrapperSignalRustSignalGetContactPublicKey(
|
||||||
|
contactId: contactId,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<Uint8List> getUserPublicKey() =>
|
||||||
|
RustLib.instance.api.crateBridgeWrapperSignalRustSignalGetUserPublicKey();
|
||||||
|
|
||||||
static Future<void> processPrekeyBundle({
|
static Future<void> processPrekeyBundle({
|
||||||
required String name,
|
required String name,
|
||||||
required int deviceId,
|
required int deviceId,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -60,24 +60,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
FutureOr<void> Function(PlatformInt64)
|
FutureOr<void> Function(PlatformInt64)
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<LegacySignalEncryptResult?> Function(PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(PlatformInt64, Uint8List)
|
FutureOr<void> Function(PlatformInt64, Uint8List)
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<LegacySignalDecryptResult> Function(PlatformInt64, Uint8List, int)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(UserConfig)
|
FutureOr<void> Function(UserConfig)
|
||||||
dco_decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
@ -88,11 +76,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Map<String, List<String>> dco_decode_Map_String_list_String_None(dynamic raw);
|
Map<String, List<String>> dco_decode_Map_String_list_String_None(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> dco_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -126,26 +109,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool dco_decode_box_autoadd_bool(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double dco_decode_box_autoadd_f_64(dynamic raw);
|
double dco_decode_box_autoadd_f_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_box_autoadd_i_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult dco_decode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig
|
PasswordlessRecoveryConfig
|
||||||
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||||
|
|
@ -189,16 +167,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw);
|
LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalDecryptResult dco_decode_legacy_signal_decrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult dco_decode_legacy_signal_encrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -230,10 +198,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
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
|
@protected
|
||||||
List<(String, List<String>)> dco_decode_list_record_string_list_string(
|
List<(String, List<String>)> dco_decode_list_record_string_list_string(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -261,19 +225,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool? dco_decode_opt_box_autoadd_bool(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double? dco_decode_opt_box_autoadd_f_64(dynamic raw);
|
double? dco_decode_opt_box_autoadd_f_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult?
|
|
||||||
dco_decode_opt_box_autoadd_legacy_signal_encrypt_result(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig?
|
PasswordlessRecoveryConfig?
|
||||||
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||||
|
|
@ -301,11 +261,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -397,11 +352,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> sse_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -447,6 +397,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool sse_decode_box_autoadd_bool(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
|
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -455,20 +408,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult sse_decode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig
|
PasswordlessRecoveryConfig
|
||||||
sse_decode_box_autoadd_passwordless_recovery_config(
|
sse_decode_box_autoadd_passwordless_recovery_config(
|
||||||
|
|
@ -522,16 +467,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalDecryptResult sse_decode_legacy_signal_decrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult sse_decode_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -569,12 +504,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
List<(String, List<String>)> sse_decode_list_record_string_list_string(
|
List<(String, List<String>)> sse_decode_list_record_string_list_string(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -602,21 +531,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer);
|
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult?
|
|
||||||
sse_decode_opt_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig?
|
PasswordlessRecoveryConfig?
|
||||||
sse_decode_opt_box_autoadd_passwordless_recovery_config(
|
sse_decode_opt_box_autoadd_passwordless_recovery_config(
|
||||||
|
|
@ -650,11 +573,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -776,14 +694,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(
|
|
||||||
FutureOr<LegacySignalEncryptResult?> Function(PlatformInt64, Uint8List)
|
|
||||||
self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void
|
void
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
|
|
@ -791,14 +701,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(
|
|
||||||
FutureOr<LegacySignalDecryptResult> Function(PlatformInt64, Uint8List, int)
|
|
||||||
self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
void sse_encode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(UserConfig) self,
|
FutureOr<void> Function(UserConfig) self,
|
||||||
|
|
@ -814,12 +716,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
Map<PlatformInt64, Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_StreamSink_String_Sse(
|
void sse_encode_StreamSink_String_Sse(
|
||||||
RustStreamSink<String> self,
|
RustStreamSink<String> self,
|
||||||
|
|
@ -874,6 +770,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
|
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
@ -883,9 +782,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_i_64(
|
void sse_encode_box_autoadd_i_64(
|
||||||
PlatformInt64 self,
|
PlatformInt64 self,
|
||||||
|
|
@ -898,12 +794,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_passwordless_recovery_config(
|
void sse_encode_box_autoadd_passwordless_recovery_config(
|
||||||
PasswordlessRecoveryConfig self,
|
PasswordlessRecoveryConfig self,
|
||||||
|
|
@ -967,18 +857,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_decrypt_result(
|
|
||||||
LegacySignalDecryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_legacy_table_migration_count(
|
void sse_encode_legacy_table_migration_count(
|
||||||
LegacyTableMigrationCount self,
|
LegacyTableMigrationCount self,
|
||||||
|
|
@ -1027,12 +905,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
List<(PlatformInt64, Uint8List)> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_record_string_list_string(
|
void sse_encode_list_record_string_list_string(
|
||||||
List<(String, List<String>)> self,
|
List<(String, List<String>)> self,
|
||||||
|
|
@ -1067,10 +939,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer);
|
void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
|
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
void sse_encode_opt_box_autoadd_i_64(
|
||||||
|
|
@ -1078,12 +950,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_passwordless_recovery_config(
|
void sse_encode_opt_box_autoadd_passwordless_recovery_config(
|
||||||
PasswordlessRecoveryConfig? self,
|
PasswordlessRecoveryConfig? self,
|
||||||
|
|
@ -1126,12 +992,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
|
||||||
(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_record_list_prim_u_8_strict_i_64(
|
void sse_encode_record_list_prim_u_8_strict_i_64(
|
||||||
(Uint8List, PlatformInt64) self,
|
(Uint8List, PlatformInt64) self,
|
||||||
|
|
|
||||||
|
|
@ -62,24 +62,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
FutureOr<void> Function(PlatformInt64)
|
FutureOr<void> Function(PlatformInt64)
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<LegacySignalEncryptResult?> Function(PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(PlatformInt64, Uint8List)
|
FutureOr<void> Function(PlatformInt64, Uint8List)
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<LegacySignalDecryptResult> Function(PlatformInt64, Uint8List, int)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(UserConfig)
|
FutureOr<void> Function(UserConfig)
|
||||||
dco_decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
@ -90,11 +78,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Map<String, List<String>> dco_decode_Map_String_list_String_None(dynamic raw);
|
Map<String, List<String>> dco_decode_Map_String_list_String_None(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> dco_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -128,26 +111,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool dco_decode_box_autoadd_bool(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double dco_decode_box_autoadd_f_64(dynamic raw);
|
double dco_decode_box_autoadd_f_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_box_autoadd_i_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult dco_decode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig
|
PasswordlessRecoveryConfig
|
||||||
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||||
|
|
@ -191,16 +169,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw);
|
LegacyMigrationReport dco_decode_legacy_migration_report(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalDecryptResult dco_decode_legacy_signal_decrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult dco_decode_legacy_signal_encrypt_result(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -232,10 +200,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
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
|
@protected
|
||||||
List<(String, List<String>)> dco_decode_list_record_string_list_string(
|
List<(String, List<String>)> dco_decode_list_record_string_list_string(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -263,19 +227,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool? dco_decode_opt_box_autoadd_bool(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double? dco_decode_opt_box_autoadd_f_64(dynamic raw);
|
double? dco_decode_opt_box_autoadd_f_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult?
|
|
||||||
dco_decode_opt_box_autoadd_legacy_signal_encrypt_result(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig?
|
PasswordlessRecoveryConfig?
|
||||||
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||||
|
|
@ -303,11 +263,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -399,11 +354,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> sse_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -449,6 +399,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool sse_decode_box_autoadd_bool(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
|
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -457,20 +410,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult sse_decode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig
|
PasswordlessRecoveryConfig
|
||||||
sse_decode_box_autoadd_passwordless_recovery_config(
|
sse_decode_box_autoadd_passwordless_recovery_config(
|
||||||
|
|
@ -524,16 +469,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalDecryptResult sse_decode_legacy_signal_decrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult sse_decode_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -571,12 +506,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
List<(String, List<String>)> sse_decode_list_record_string_list_string(
|
List<(String, List<String>)> sse_decode_list_record_string_list_string(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -604,21 +533,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer);
|
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalEncryptResult?
|
|
||||||
sse_decode_opt_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PasswordlessRecoveryConfig?
|
PasswordlessRecoveryConfig?
|
||||||
sse_decode_opt_box_autoadd_passwordless_recovery_config(
|
sse_decode_opt_box_autoadd_passwordless_recovery_config(
|
||||||
|
|
@ -652,11 +575,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -778,14 +696,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(
|
|
||||||
FutureOr<LegacySignalEncryptResult?> Function(PlatformInt64, Uint8List)
|
|
||||||
self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void
|
void
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
|
|
@ -793,14 +703,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(
|
|
||||||
FutureOr<LegacySignalDecryptResult> Function(PlatformInt64, Uint8List, int)
|
|
||||||
self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
void sse_encode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(UserConfig) self,
|
FutureOr<void> Function(UserConfig) self,
|
||||||
|
|
@ -816,12 +718,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
Map<PlatformInt64, Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_StreamSink_String_Sse(
|
void sse_encode_StreamSink_String_Sse(
|
||||||
RustStreamSink<String> self,
|
RustStreamSink<String> self,
|
||||||
|
|
@ -876,6 +772,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
|
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
@ -885,9 +784,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_i_64(
|
void sse_encode_box_autoadd_i_64(
|
||||||
PlatformInt64 self,
|
PlatformInt64 self,
|
||||||
|
|
@ -900,12 +796,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_passwordless_recovery_config(
|
void sse_encode_box_autoadd_passwordless_recovery_config(
|
||||||
PasswordlessRecoveryConfig self,
|
PasswordlessRecoveryConfig self,
|
||||||
|
|
@ -969,18 +859,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_decrypt_result(
|
|
||||||
LegacySignalDecryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_legacy_table_migration_count(
|
void sse_encode_legacy_table_migration_count(
|
||||||
LegacyTableMigrationCount self,
|
LegacyTableMigrationCount self,
|
||||||
|
|
@ -1029,12 +907,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
List<(PlatformInt64, Uint8List)> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_record_string_list_string(
|
void sse_encode_list_record_string_list_string(
|
||||||
List<(String, List<String>)> self,
|
List<(String, List<String>)> self,
|
||||||
|
|
@ -1069,10 +941,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer);
|
void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
|
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
void sse_encode_opt_box_autoadd_i_64(
|
||||||
|
|
@ -1080,12 +952,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_legacy_signal_encrypt_result(
|
|
||||||
LegacySignalEncryptResult? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_passwordless_recovery_config(
|
void sse_encode_opt_box_autoadd_passwordless_recovery_config(
|
||||||
PasswordlessRecoveryConfig? self,
|
PasswordlessRecoveryConfig? self,
|
||||||
|
|
@ -1128,12 +994,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
|
||||||
(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_record_list_prim_u_8_strict_i_64(
|
void sse_encode_record_list_prim_u_8_strict_i_64(
|
||||||
(Uint8List, PlatformInt64) self,
|
(Uint8List, PlatformInt64) self,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
import 'package:twonly/src/services/api/api.service.dart';
|
||||||
import 'package:twonly/src/services/news.service.dart';
|
import 'package:twonly/src/services/news.service.dart';
|
||||||
|
|
@ -16,12 +15,10 @@ void setupLocator() {
|
||||||
..registerLazySingleton<UserService>(UserService.new)
|
..registerLazySingleton<UserService>(UserService.new)
|
||||||
..registerLazySingleton<ApiService>(ApiService.new)
|
..registerLazySingleton<ApiService>(ApiService.new)
|
||||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new)
|
..registerLazySingleton<TwonlyDB>(TwonlyDB.new)
|
||||||
..registerLazySingleton<SignalDB>(SignalDB.new)
|
|
||||||
..registerLazySingleton<NewsService>(NewsService.new);
|
..registerLazySingleton<NewsService>(NewsService.new);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserService get userService => locator<UserService>();
|
UserService get userService => locator<UserService>();
|
||||||
ApiService get apiService => locator<ApiService>();
|
ApiService get apiService => locator<ApiService>();
|
||||||
TwonlyDB get twonlyDB => locator<TwonlyDB>();
|
TwonlyDB get twonlyDB => locator<TwonlyDB>();
|
||||||
SignalDB get signalDB => locator<SignalDB>();
|
|
||||||
NewsService get newsService => locator<NewsService>();
|
NewsService get newsService => locator<NewsService>();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import 'package:twonly/core/bridge/callbacks.dart';
|
import 'package:twonly/core/bridge/callbacks.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/callbacks/legacy_signal.callbacks.dart';
|
|
||||||
import 'package:twonly/src/callbacks/logging.callbacks.dart';
|
import 'package:twonly/src/callbacks/logging.callbacks.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||||
import 'package:twonly/src/services/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/key_verification.service.dart';
|
import 'package:twonly/src/services/key_verification.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/avatars.dart';
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
|
|
||||||
|
|
@ -31,9 +29,6 @@ Future<void> initFlutterCallbacksForRust() async {
|
||||||
await initFlutterCallbacks(
|
await initFlutterCallbacks(
|
||||||
callbackId: isolateCallbackId,
|
callbackId: isolateCallbackId,
|
||||||
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
||||||
legacySignalDecrypt: LegacySignalCallbacks.decrypt,
|
|
||||||
legacySignalEncrypt: LegacySignalCallbacks.encrypt,
|
|
||||||
apiResyncSignalSession: handleSessionResync,
|
|
||||||
apiMediaAction: _apiMediaAction,
|
apiMediaAction: _apiMediaAction,
|
||||||
apiVerificationProof: KeyVerificationService.handleVerificationProof,
|
apiVerificationProof: KeyVerificationService.handleVerificationProof,
|
||||||
apiCreatePushAvatars: (contactId) =>
|
apiCreatePushAvatars: (contactId) =>
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
import 'package:twonly/core/bridge/callbacks.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|
||||||
as pb;
|
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
/// Flutter boundary for the legacy libsignal_protocol_dart implementation.
|
|
||||||
///
|
|
||||||
/// Only V1 CIPHERTEXT and PREKEY_BUNDLE messages may cross this boundary.
|
|
||||||
/// CIPHERTEXT_V2 remains fully Rust-owned.
|
|
||||||
abstract final class LegacySignalCallbacks {
|
|
||||||
static Future<LegacySignalDecryptResult> decrypt(
|
|
||||||
PlatformInt64 fromUserId,
|
|
||||||
Uint8List ciphertext,
|
|
||||||
int messageType,
|
|
||||||
) async {
|
|
||||||
if (!_isLegacyMessageType(messageType)) {
|
|
||||||
return const LegacySignalDecryptResult(
|
|
||||||
decryptionErrorType: 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final (content, errorType) = await signalDecryptMessageV1(
|
|
||||||
fromUserId,
|
|
||||||
ciphertext,
|
|
||||||
messageType,
|
|
||||||
);
|
|
||||||
return LegacySignalDecryptResult(
|
|
||||||
plaintext: content == null
|
|
||||||
? null
|
|
||||||
: Uint8List.fromList(content.writeToBuffer()),
|
|
||||||
decryptionErrorType: errorType?.value,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
Log.error('Legacy Signal decryption callback failed: $error');
|
|
||||||
return const LegacySignalDecryptResult(decryptionErrorType: 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<LegacySignalEncryptResult?> encrypt(
|
|
||||||
PlatformInt64 targetUserId,
|
|
||||||
Uint8List plaintext,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final encrypted = await signalEncryptMessage(
|
|
||||||
targetUserId,
|
|
||||||
plaintext,
|
|
||||||
);
|
|
||||||
if (encrypted == null || !_isLegacyMessageType(encrypted.type.value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return LegacySignalEncryptResult(
|
|
||||||
ciphertext: encrypted.ciphertext,
|
|
||||||
messageType: encrypted.type.value,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
Log.error('Legacy Signal encryption callback failed: $error');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _isLegacyMessageType(int messageType) =>
|
|
||||||
messageType == pb.Message_Type.CIPHERTEXT.value ||
|
|
||||||
messageType == pb.Message_Type.PREKEY_BUNDLE.value;
|
|
||||||
}
|
|
||||||
|
|
@ -49,10 +49,15 @@ Future<void> deleteGroup(String groupId) async {
|
||||||
groupMembers,
|
groupMembers,
|
||||||
)..where((t) => t.groupId.equals(groupId))).get();
|
)..where((t) => t.groupId.equals(groupId))).get();
|
||||||
}
|
}
|
||||||
Future<Group?> createNewGroup(GroupsCompanion group) async {
|
Future<Group?> createNewGroup(GroupsCompanion group) async {
|
||||||
return _insertGroup(group);
|
return _insertGroup(group);
|
||||||
}
|
}
|
||||||
Future<void> insertGroupAction(GroupHistoriesCompanion action) async {
|
|
||||||
|
Future<void> insertOrUpdateGroupMember(GroupMembersCompanion members) async {
|
||||||
|
await into(groupMembers).insertOnConflictUpdate(members);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> insertGroupAction(GroupHistoriesCompanion action) async {
|
||||||
var insertAction = action;
|
var insertAction = action;
|
||||||
if (!action.groupHistoryId.present) {
|
if (!action.groupHistoryId.present) {
|
||||||
insertAction = action.copyWith(
|
insertAction = action.copyWith(
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,42 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
||||||
|
|
||||||
/// Returns a map of contactId → the verification type of the earliest
|
/// Returns a map of contactId → the verification type of the earliest
|
||||||
/// [KeyVerification] row for that contact.
|
/// [KeyVerification] row for that contact.
|
||||||
Stream<List<(KeyVerification, Contact?)>> watchContactVerification(
|
Future<Map<int, VerificationType>>
|
||||||
|
getFirstVerificationTypeByContacts() async {
|
||||||
|
final rows = await (select(
|
||||||
|
keyVerifications,
|
||||||
|
)..orderBy([(kv) => OrderingTerm.asc(kv.createdAt)])).get();
|
||||||
|
|
||||||
|
final result = <int, VerificationType>{};
|
||||||
|
for (final row in rows) {
|
||||||
|
result.putIfAbsent(row.contactId, () => row.type);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isContactVerified(int contactId) async {
|
||||||
|
final verifierKv = alias(keyVerifications, 'verifierKv');
|
||||||
|
final query = select(keyVerifications).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
verifierKv,
|
||||||
|
verifierKv.contactId.equalsExp(keyVerifications.verifiedBy),
|
||||||
|
),
|
||||||
|
])..where(keyVerifications.contactId.equals(contactId));
|
||||||
|
|
||||||
|
final rows = await query.get();
|
||||||
|
for (final row in rows) {
|
||||||
|
final kv = row.readTable(keyVerifications);
|
||||||
|
final hasVerifierKv = row.readTableOrNull(verifierKv) != null;
|
||||||
|
if (kv.type == VerificationType.contactSharedByVerified) {
|
||||||
|
if (hasVerifierKv) return true;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<(KeyVerification, Contact?)>> watchContactVerification(
|
||||||
int contactId,
|
int contactId,
|
||||||
) {
|
) {
|
||||||
final verifier = alias(contacts, 'verifier');
|
final verifier = alias(contacts, 'verifier');
|
||||||
|
|
@ -291,7 +326,24 @@ Stream<VerificationStatus> watchAllGroupMembersVerified(String groupId) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Future<void> deleteKeyVerificationById(
|
|
||||||
|
Future<void> deleteKeyVerification(int contactId) async {
|
||||||
|
try {
|
||||||
|
await (delete(
|
||||||
|
keyVerifications,
|
||||||
|
)..where((kv) => kv.contactId.equals(contactId))).go();
|
||||||
|
if (userService.currentUser.isUserDiscoveryEnabled) {
|
||||||
|
await FlutterUserDiscovery.updateVerificationStateForUser(
|
||||||
|
callbackId: isolateCallbackId,
|
||||||
|
contactId: contactId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteKeyVerificationById(
|
||||||
int verificationId,
|
int verificationId,
|
||||||
int contactId,
|
int contactId,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'signal.dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$SignalDaoMixin on DatabaseAccessor<TwonlyDB> {
|
|
||||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
|
||||||
$SignalContactPreKeysTable get signalContactPreKeys =>
|
|
||||||
attachedDatabase.signalContactPreKeys;
|
|
||||||
$SignalContactSignedPreKeysTable get signalContactSignedPreKeys =>
|
|
||||||
attachedDatabase.signalContactSignedPreKeys;
|
|
||||||
SignalDaoManager get managers => SignalDaoManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignalDaoManager {
|
|
||||||
final _$SignalDaoMixin _db;
|
|
||||||
SignalDaoManager(this._db);
|
|
||||||
$$ContactsTableTableManager get contacts =>
|
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
|
||||||
$$SignalContactPreKeysTableTableManager get signalContactPreKeys =>
|
|
||||||
$$SignalContactPreKeysTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.signalContactPreKeys,
|
|
||||||
);
|
|
||||||
$$SignalContactSignedPreKeysTableTableManager
|
|
||||||
get signalContactSignedPreKeys =>
|
|
||||||
$$SignalContactSignedPreKeysTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.signalContactSignedPreKeys,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:drift_flutter/drift_flutter.dart'
|
|
||||||
show DriftNativeOptions, driftDatabase;
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_identity_key_store.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_session_store.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_signed_pre_key_store.table.dart';
|
|
||||||
|
|
||||||
part 'signal.db.g.dart';
|
|
||||||
|
|
||||||
@DriftDatabase(
|
|
||||||
tables: [
|
|
||||||
SignalIdentityKeyStores,
|
|
||||||
SignalPreKeyStores,
|
|
||||||
SignalSenderKeyStores,
|
|
||||||
SignalSessionStores,
|
|
||||||
SignalSignedPreKeyStores,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
class SignalDB extends _$SignalDB {
|
|
||||||
SignalDB([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
|
||||||
|
|
||||||
@override
|
|
||||||
// This database shares the legacy twonly.sqlite file, whose user_version is
|
|
||||||
// already 25. Keeping that version avoids a false downgrade while only the
|
|
||||||
// Signal tables remain owned by Drift.
|
|
||||||
int get schemaVersion => 25;
|
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
|
||||||
return driftDatabase(
|
|
||||||
name: 'twonly',
|
|
||||||
native: DriftNativeOptions(
|
|
||||||
databaseDirectory: getApplicationSupportDirectory,
|
|
||||||
shareAcrossIsolates: true,
|
|
||||||
setup: (database) {
|
|
||||||
database
|
|
||||||
..execute('PRAGMA journal_mode=DELETE;')
|
|
||||||
..execute('PRAGMA synchronous=FULL;')
|
|
||||||
..execute('PRAGMA busy_timeout=5000;')
|
|
||||||
..execute('PRAGMA foreign_keys=ON;');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,81 +0,0 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
|
|
||||||
class SignalIdentityKeyStore extends IdentityKeyStore {
|
|
||||||
SignalIdentityKeyStore(this.identityKeyPair, this.localRegistrationId);
|
|
||||||
|
|
||||||
final IdentityKeyPair identityKeyPair;
|
|
||||||
final int localRegistrationId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
|
|
||||||
final identity =
|
|
||||||
await (signalDB.select(signalDB.signalIdentityKeyStores)..where(
|
|
||||||
(t) =>
|
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
|
||||||
t.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (identity == null) return null;
|
|
||||||
return IdentityKey.fromBytes(identity.identityKey, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<IdentityKeyPair> getIdentityKeyPair() async => identityKeyPair;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> getLocalRegistrationId() async => localRegistrationId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isTrustedIdentity(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
IdentityKey? identityKey,
|
|
||||||
Direction? direction,
|
|
||||||
) async {
|
|
||||||
final trusted = await getIdentity(address);
|
|
||||||
if (identityKey == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return trusted == null ||
|
|
||||||
const ListEquality<dynamic>().equals(
|
|
||||||
trusted.serialize(),
|
|
||||||
identityKey.serialize(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> saveIdentity(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
IdentityKey? identityKey,
|
|
||||||
) async {
|
|
||||||
if (identityKey == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (await getIdentity(address) == null) {
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalIdentityKeyStores)
|
|
||||||
.insert(
|
|
||||||
SignalIdentityKeyStoresCompanion(
|
|
||||||
deviceId: Value(address.getDeviceId()),
|
|
||||||
name: Value(address.getName()),
|
|
||||||
identityKey: Value(identityKey.serialize()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await (signalDB.update(signalDB.signalIdentityKeyStores)..where(
|
|
||||||
(t) =>
|
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
|
||||||
t.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.write(
|
|
||||||
SignalIdentityKeyStoresCompanion(
|
|
||||||
identityKey: Value(identityKey.serialize()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
class SignalPreKeyStore extends PreKeyStore {
|
|
||||||
@override
|
|
||||||
Future<bool> containsPreKey(int preKeyId) async {
|
|
||||||
final preKeyRecord = await (signalDB.select(
|
|
||||||
signalDB.signalPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).get();
|
|
||||||
return preKeyRecord.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<PreKeyRecord> loadPreKey(int preKeyId) async {
|
|
||||||
final preKeyRecord = await (signalDB.select(
|
|
||||||
signalDB.signalPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).get();
|
|
||||||
if (preKeyRecord.isEmpty) {
|
|
||||||
throw InvalidKeyIdException(
|
|
||||||
'[PREKEY] No such preKey record!',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final preKey = preKeyRecord.first.preKey;
|
|
||||||
return PreKeyRecord.fromBuffer(preKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removePreKey(int preKeyId) async {
|
|
||||||
await (signalDB.delete(
|
|
||||||
signalDB.signalPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).go();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
|
|
||||||
final preKeyCompanion = SignalPreKeyStoresCompanion(
|
|
||||||
preKeyId: Value(preKeyId),
|
|
||||||
preKey: Value(record.serialize()),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalPreKeyStores)
|
|
||||||
.insert(preKeyCompanion, mode: InsertMode.insertOrReplace);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('$e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/src/database/signal/signal_identity_key_store.dart';
|
|
||||||
import 'package:twonly/src/database/signal/signal_pre_key_store.dart';
|
|
||||||
import 'package:twonly/src/database/signal/signal_session_store.dart';
|
|
||||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
|
||||||
|
|
||||||
class SignalSignalProtocolStore implements SignalProtocolStore {
|
|
||||||
SignalSignalProtocolStore(
|
|
||||||
IdentityKeyPair identityKeyPair,
|
|
||||||
int registrationId,
|
|
||||||
) {
|
|
||||||
_identityKeyStore = SignalIdentityKeyStore(
|
|
||||||
identityKeyPair,
|
|
||||||
registrationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final preKeyStore = SignalPreKeyStore();
|
|
||||||
final sessionStore = SignalSessionStore();
|
|
||||||
final signedPreKeyStore = SignalSignedPreKeyStore();
|
|
||||||
|
|
||||||
late IdentityKeyStore _identityKeyStore;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<IdentityKeyPair> getIdentityKeyPair() async =>
|
|
||||||
_identityKeyStore.getIdentityKeyPair();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> getLocalRegistrationId() async =>
|
|
||||||
_identityKeyStore.getLocalRegistrationId();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> saveIdentity(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
IdentityKey? identityKey,
|
|
||||||
) async => _identityKeyStore.saveIdentity(address, identityKey);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isTrustedIdentity(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
IdentityKey? identityKey,
|
|
||||||
Direction direction,
|
|
||||||
) async =>
|
|
||||||
_identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async =>
|
|
||||||
_identityKeyStore.getIdentity(address);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<PreKeyRecord> loadPreKey(int preKeyId) async =>
|
|
||||||
preKeyStore.loadPreKey(preKeyId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
|
|
||||||
await preKeyStore.storePreKey(preKeyId, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> containsPreKey(int preKeyId) async =>
|
|
||||||
preKeyStore.containsPreKey(preKeyId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removePreKey(int preKeyId) async {
|
|
||||||
await preKeyStore.removePreKey(preKeyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<SessionRecord> loadSession(SignalProtocolAddress address) async =>
|
|
||||||
sessionStore.loadSession(address);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<int>> getSubDeviceSessions(String name) async =>
|
|
||||||
sessionStore.getSubDeviceSessions(name);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSession(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
SessionRecord record,
|
|
||||||
) async {
|
|
||||||
await sessionStore.storeSession(address, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> containsSession(SignalProtocolAddress address) async =>
|
|
||||||
sessionStore.containsSession(address);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteSession(SignalProtocolAddress address) async {
|
|
||||||
await sessionStore.deleteSession(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteAllSessions(String name) async {
|
|
||||||
await sessionStore.deleteAllSessions(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<SignedPreKeyRecord> loadSignedPreKey(int signedPreKeyId) async =>
|
|
||||||
signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<SignedPreKeyRecord>> loadSignedPreKeys() async =>
|
|
||||||
signedPreKeyStore.loadSignedPreKeys();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSignedPreKey(
|
|
||||||
int signedPreKeyId,
|
|
||||||
SignedPreKeyRecord record,
|
|
||||||
) async {
|
|
||||||
await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> containsSignedPreKey(int signedPreKeyId) async =>
|
|
||||||
signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removeSignedPreKey(int signedPreKeyId) async {
|
|
||||||
await signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
|
|
||||||
class SignalSenderKeyStore extends SenderKeyStore {
|
|
||||||
@override
|
|
||||||
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
|
||||||
final identity =
|
|
||||||
await (signalDB.select(signalDB.signalSenderKeyStores)
|
|
||||||
..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (identity == null) {
|
|
||||||
throw InvalidKeyIdException(
|
|
||||||
'No such sender key record! - $senderKeyName',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SenderKeyRecord.fromSerialized(identity.senderKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSenderKey(
|
|
||||||
SenderKeyName senderKeyName,
|
|
||||||
SenderKeyRecord record,
|
|
||||||
) async {
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalSenderKeyStores)
|
|
||||||
.insert(
|
|
||||||
SignalSenderKeyStoresCompanion(
|
|
||||||
senderKey: Value(record.serialize()),
|
|
||||||
senderKeyName: Value(senderKeyName.serialize()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
|
|
||||||
class SignalSessionStore extends SessionStore {
|
|
||||||
@override
|
|
||||||
Future<bool> containsSession(SignalProtocolAddress address) async {
|
|
||||||
final sessions =
|
|
||||||
await (signalDB.select(signalDB.signalSessionStores)..where(
|
|
||||||
(tbl) =>
|
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
|
||||||
tbl.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
return sessions.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteAllSessions(String name) async {
|
|
||||||
await (signalDB.delete(
|
|
||||||
signalDB.signalSessionStores,
|
|
||||||
)..where((tbl) => tbl.name.equals(name))).go();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteSession(SignalProtocolAddress address) async {
|
|
||||||
await (signalDB.delete(signalDB.signalSessionStores)..where(
|
|
||||||
(tbl) =>
|
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
|
||||||
tbl.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<int>> getSubDeviceSessions(String name) async {
|
|
||||||
final deviceIds =
|
|
||||||
await (signalDB.select(signalDB.signalSessionStores)..where(
|
|
||||||
(tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
return deviceIds.map((row) => row.deviceId).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
|
|
||||||
final dbSession =
|
|
||||||
await (signalDB.select(signalDB.signalSessionStores)..where(
|
|
||||||
(tbl) =>
|
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
|
||||||
tbl.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (dbSession.isEmpty) {
|
|
||||||
return SessionRecord();
|
|
||||||
}
|
|
||||||
final session = dbSession.first.sessionRecord;
|
|
||||||
return SessionRecord.fromSerialized(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSession(
|
|
||||||
SignalProtocolAddress address,
|
|
||||||
SessionRecord record,
|
|
||||||
) async {
|
|
||||||
final sessionCompanion = SignalSessionStoresCompanion(
|
|
||||||
deviceId: Value(address.getDeviceId()),
|
|
||||||
name: Value(address.getName()),
|
|
||||||
sessionRecord: Value(record.serialize()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!await containsSession(address)) {
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalSessionStores)
|
|
||||||
.insert(sessionCompanion);
|
|
||||||
} else {
|
|
||||||
await (signalDB.update(signalDB.signalSessionStores)..where(
|
|
||||||
(tbl) =>
|
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
|
||||||
tbl.name.equals(address.getName()),
|
|
||||||
))
|
|
||||||
.write(sessionCompanion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
import 'package:twonly/src/utils/secure_storage.dart';
|
|
||||||
|
|
||||||
Future<HashMap<int, Uint8List>> getSignalSignedPreKeyStoreOld() async {
|
|
||||||
final storeSerialized = await SecureStorage.instance.read(
|
|
||||||
key: 'signed_pre_key_store',
|
|
||||||
);
|
|
||||||
final store = HashMap<int, Uint8List>();
|
|
||||||
if (storeSerialized == null) {
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
final storeHashMap = json.decode(storeSerialized) as List<dynamic>;
|
|
||||||
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<SignedPreKeyRecord> loadSignedPreKey(int signedPreKeyId) async {
|
|
||||||
final record = await (signalDB.select(
|
|
||||||
signalDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).get();
|
|
||||||
if (record.isEmpty) {
|
|
||||||
throw InvalidKeyIdException(
|
|
||||||
'No such signed prekey record! $signedPreKeyId',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SignedPreKeyRecord.fromSerialized(record.first.signedPreKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<SignedPreKeyRecord>> loadSignedPreKeys() async {
|
|
||||||
final records = await signalDB
|
|
||||||
.select(signalDB.signalSignedPreKeyStores)
|
|
||||||
.get();
|
|
||||||
return records
|
|
||||||
.map((r) => SignedPreKeyRecord.fromSerialized(r.signedPreKey))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSignedPreKey(
|
|
||||||
int signedPreKeyId,
|
|
||||||
SignedPreKeyRecord record,
|
|
||||||
) async {
|
|
||||||
final companion = SignalSignedPreKeyStoresCompanion(
|
|
||||||
signedPreKeyId: Value(signedPreKeyId),
|
|
||||||
signedPreKey: Value(record.serialize()),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalSignedPreKeyStores)
|
|
||||||
.insert(companion, mode: InsertMode.insertOrReplace);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('$e');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> containsSignedPreKey(int signedPreKeyId) async {
|
|
||||||
final record = await (signalDB.select(
|
|
||||||
signalDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).get();
|
|
||||||
return record.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removeSignedPreKey(int signedPreKeyId) async {
|
|
||||||
await (signalDB.delete(
|
|
||||||
signalDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).go();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalIdentityKeyStore')
|
|
||||||
class SignalIdentityKeyStores extends Table {
|
|
||||||
IntColumn get deviceId => integer()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
BlobColumn get identityKey => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {deviceId, name};
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalPreKeyStore')
|
|
||||||
class SignalPreKeyStores extends Table {
|
|
||||||
IntColumn get preKeyId => integer()();
|
|
||||||
BlobColumn get preKey => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {preKeyId};
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalSenderKeyStore')
|
|
||||||
class SignalSenderKeyStores extends Table {
|
|
||||||
TextColumn get senderKeyName => text()();
|
|
||||||
BlobColumn get senderKey => blob()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {senderKeyName};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalSessionStore')
|
|
||||||
class SignalSessionStores extends Table {
|
|
||||||
IntColumn get deviceId => integer()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
BlobColumn get sessionRecord => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {deviceId, name};
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalSignedPreKeyStore')
|
|
||||||
class SignalSignedPreKeyStores extends Table {
|
|
||||||
IntColumn get signedPreKeyId => integer()();
|
|
||||||
BlobColumn get signedPreKey => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {signedPreKeyId};
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'signal_identity.model.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class SignalIdentity {
|
|
||||||
const SignalIdentity({
|
|
||||||
required this.identityKeyPairU8List,
|
|
||||||
required this.registrationId,
|
|
||||||
});
|
|
||||||
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SignalIdentityFromJson(json);
|
|
||||||
|
|
||||||
final int registrationId;
|
|
||||||
|
|
||||||
@Uint8ListConverter()
|
|
||||||
final Uint8List identityKeyPairU8List;
|
|
||||||
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
|
|
||||||
const Uint8ListConverter();
|
|
||||||
@override
|
|
||||||
Uint8List fromJson(String json) {
|
|
||||||
return base64Decode(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toJson(Uint8List object) {
|
|
||||||
return base64Encode(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'signal_identity.model.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
SignalIdentity _$SignalIdentityFromJson(Map<String, dynamic> json) =>
|
|
||||||
SignalIdentity(
|
|
||||||
identityKeyPairU8List: const Uint8ListConverter().fromJson(
|
|
||||||
json['identityKeyPairU8List'] as String,
|
|
||||||
),
|
|
||||||
registrationId: (json['registrationId'] as num).toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SignalIdentityToJson(SignalIdentity instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'registrationId': instance.registrationId,
|
|
||||||
'identityKeyPairU8List': const Uint8ListConverter().toJson(
|
|
||||||
instance.identityKeyPairU8List,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
@ -7,7 +7,6 @@ import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
/// The ApiProvider is responsible for communicating with the server.
|
/// The ApiProvider is responsible for communicating with the server.
|
||||||
|
|
@ -47,7 +46,6 @@ class ApiService {
|
||||||
unawaited(reuploadMediaFiles());
|
unawaited(reuploadMediaFiles());
|
||||||
|
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
resetResyncedUsers();
|
|
||||||
// resetUserDiscoveryRequestUpdates();
|
// resetUserDiscoveryRequestUpdates();
|
||||||
memoriesCloudService.init();
|
memoriesCloudService.init();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
|
||||||
as server;
|
as server;
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
|
||||||
hide Message;
|
hide Message;
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/secure_storage.dart';
|
import 'package:twonly/src/utils/secure_storage.dart';
|
||||||
|
|
@ -69,18 +68,17 @@ Future<void> handleMediaError(MediaFile media) async {
|
||||||
targetMessageId: message.messageId,
|
targetMessageId: message.messageId,
|
||||||
),
|
),
|
||||||
).writeToBuffer(),
|
).writeToBuffer(),
|
||||||
onlySendIfNoReceiptsAreOpen: false,
|
|
||||||
onlyReturnEncryptedData: false,
|
|
||||||
blocking: true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> importSignalContactAndCreateRequest(
|
Future<bool> importSignalContactAndCreateRequest(
|
||||||
server.Response_UserData userdata,
|
server.Response_UserData userdata,
|
||||||
) async {
|
) async {
|
||||||
if (!await processSignalUserData(userdata)) {
|
try {
|
||||||
return false;
|
await RustApi.establishSignalSession(
|
||||||
}
|
contactId: userdata.userId.toInt(),
|
||||||
|
expectedPublicKey: Uint8List.fromList(userdata.publicIdentityKey),
|
||||||
|
);
|
||||||
|
|
||||||
// 2. Then send user request
|
// 2. Then send user request
|
||||||
await RustApi.sendEncryptedContent(
|
await RustApi.sendEncryptedContent(
|
||||||
|
|
@ -90,12 +88,13 @@ Future<bool> importSignalContactAndCreateRequest(
|
||||||
type: EncryptedContent_ContactRequest_Type.REQUEST,
|
type: EncryptedContent_ContactRequest_Type.REQUEST,
|
||||||
),
|
),
|
||||||
).writeToBuffer(),
|
).writeToBuffer(),
|
||||||
onlySendIfNoReceiptsAreOpen: false,
|
|
||||||
onlyReturnEncryptedData: false,
|
|
||||||
blocking: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Failed to establish session and send contact request: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, String>?> getAuthenticationHeader() async {
|
Future<Map<String, String>?> getAuthenticationHeader() async {
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,6 @@ Future<bool> removeMemberFromGroup(Group group, Uint8List key, int contactId) =>
|
||||||
groupPublicKey: key,
|
groupPublicKey: key,
|
||||||
contactId: contactId,
|
contactId: contactId,
|
||||||
);
|
);
|
||||||
Future<bool> leaveAsNonAdminFromGroup(Group group) =>
|
Future<bool> leaveGroup(Group group) =>
|
||||||
rust_groups.leaveGroup(groupId: group.groupId);
|
rust_groups.leaveGroup(groupId: group.groupId);
|
||||||
|
Future<bool> leaveAsNonAdminFromGroup(Group group) => leaveGroup(group);
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/services/passwordless_recovery.service.dart'
|
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||||
show PasswordlessRecoveryService;
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.utils.dart';
|
import 'package:twonly/src/utils/qr.utils.dart';
|
||||||
|
|
@ -102,7 +101,9 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
||||||
if (publicKey != null) {
|
if (publicKey != null) {
|
||||||
try {
|
try {
|
||||||
final contact = contacts.first;
|
final contact = contacts.first;
|
||||||
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
final storedPublicKey = await RustSignal.getContactPublicKey(
|
||||||
|
contactId: contact.userId,
|
||||||
|
);
|
||||||
final receivedPublicKey = base64Url.decode(publicKey);
|
final receivedPublicKey = base64Url.decode(publicKey);
|
||||||
if (storedPublicKey == null ||
|
if (storedPublicKey == null ||
|
||||||
receivedPublicKey.isEmpty ||
|
receivedPublicKey.isEmpty ||
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ import 'dart:typed_data';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||||
as pb;
|
as pb;
|
||||||
import 'package:twonly/src/providers/routing.provider.dart';
|
import 'package:twonly/src/providers/routing.provider.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||||
|
|
@ -62,9 +61,6 @@ class KeyVerificationService {
|
||||||
calculatedMac: calculatedMac,
|
calculatedMac: calculatedMac,
|
||||||
),
|
),
|
||||||
).writeToBuffer(),
|
).writeToBuffer(),
|
||||||
onlySendIfNoReceiptsAreOpen: false,
|
|
||||||
onlyReturnEncryptedData: false,
|
|
||||||
blocking: true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +70,9 @@ class KeyVerificationService {
|
||||||
) async {
|
) async {
|
||||||
Log.info('Received a verification proof. Verifying the calculated mac...');
|
Log.info('Received a verification proof. Verifying the calculated mac...');
|
||||||
|
|
||||||
final contactPubKey = await getPublicKeyFromContact(fromUserId);
|
final contactPubKey = await RustSignal.getContactPublicKey(
|
||||||
|
contactId: fromUserId,
|
||||||
|
);
|
||||||
if (contactPubKey == null) {
|
if (contactPubKey == null) {
|
||||||
Log.error('No public key stored..');
|
Log.error('No public key stored..');
|
||||||
return;
|
return;
|
||||||
|
|
@ -121,7 +119,9 @@ class KeyVerificationService {
|
||||||
required List<int> sharedPublicIdentityKey,
|
required List<int> sharedPublicIdentityKey,
|
||||||
required int senderId,
|
required int senderId,
|
||||||
}) async {
|
}) async {
|
||||||
final publicIdentityKey = await getPublicKeyFromContact(contactId);
|
final publicIdentityKey = await RustSignal.getContactPublicKey(
|
||||||
|
contactId: contactId,
|
||||||
|
);
|
||||||
if (publicIdentityKey == null) {
|
if (publicIdentityKey == null) {
|
||||||
Log.info('No public key stored for contact $contactId');
|
Log.info('No public key stored for contact $contactId');
|
||||||
return;
|
return;
|
||||||
|
|
@ -148,7 +148,7 @@ Future<List<int>> _createVerificationBytes(
|
||||||
) async {
|
) async {
|
||||||
final bytes = <int>[];
|
final bytes = <int>[];
|
||||||
|
|
||||||
final userPublicKey = await getUserPublicKey();
|
final userPublicKey = await RustSignal.getUserPublicKey();
|
||||||
|
|
||||||
final ownBytes = [
|
final ownBytes = [
|
||||||
..._userIdToLeBytes(userService.currentUser.userId),
|
..._userIdToLeBytes(userService.currentUser.userId),
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
|
||||||
import 'package:twonly/src/database/signal.db.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/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
import 'package:twonly/src/utils/secure_storage.dart';
|
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||||
|
|
||||||
Future<void> runMigrations() async {
|
Future<void> runMigrations() async {
|
||||||
|
|
@ -56,42 +47,6 @@ Future<void> runMigrations() async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (userService.currentUser.appVersion < 113) {
|
if (userService.currentUser.appVersion < 113) {
|
||||||
var migrationSuccess = true;
|
|
||||||
final signalIdentity = await SecureStorage.instance.read(
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
key: SecureStorageKeys.signalIdentity,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signalIdentity != null) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(signalIdentity);
|
|
||||||
final identity = SignalIdentity.fromJson(
|
|
||||||
decoded as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await RustKeyManager.importSignalIdentity(
|
|
||||||
identityKeyPairStructure: identity.identityKeyPairU8List,
|
|
||||||
registrationId: identity.registrationId,
|
|
||||||
signedPreKeyStore: await getSignalSignedPreKeyStoreOld(),
|
|
||||||
);
|
|
||||||
Log.info('Importing signal identify to the rust key manager');
|
|
||||||
|
|
||||||
// Clean up old keys after successful migration
|
|
||||||
await SecureStorage.instance.delete(
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
key: SecureStorageKeys.signalIdentity,
|
|
||||||
);
|
|
||||||
await SecureStorage.instance.delete(
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
key: SecureStorageKeys.signalSignedPreKey,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Failed to migrate signal identity: $e');
|
|
||||||
migrationSuccess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (migrationSuccess) {
|
|
||||||
await UserService.update((u) {
|
await UserService.update((u) {
|
||||||
u
|
u
|
||||||
..appVersion = 113
|
..appVersion = 113
|
||||||
|
|
@ -102,7 +57,7 @@ Future<void> runMigrations() async {
|
||||||
..twonlySafeBackup?.backupId = Uint8List(0);
|
..twonlySafeBackup?.backupId = Uint8List(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (userService.currentUser.appVersion < 114) {
|
if (userService.currentUser.appVersion < 114) {
|
||||||
final allMedia = await twonlyDB.mediaFilesDao
|
final allMedia = await twonlyDB.mediaFilesDao
|
||||||
.select(twonlyDB.mediaFiles)
|
.select(twonlyDB.mediaFiles)
|
||||||
|
|
@ -120,30 +75,8 @@ Future<void> runMigrations() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userService.currentUser.appVersion < 115) {
|
if (userService.currentUser.appVersion < 115) {
|
||||||
var migrationSuccess = true;
|
|
||||||
try {
|
|
||||||
final rustStore = await RustKeyManager.loadSignedPrekeys();
|
|
||||||
for (final entry in rustStore.entries) {
|
|
||||||
final companion = SignalSignedPreKeyStoresCompanion(
|
|
||||||
signedPreKeyId: Value(entry.key),
|
|
||||||
signedPreKey: Value(entry.value),
|
|
||||||
);
|
|
||||||
await signalDB
|
|
||||||
.into(signalDB.signalSignedPreKeyStores)
|
|
||||||
.insert(
|
|
||||||
companion,
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
await RustKeyManager.removeSignedPrekey(signedPreKeyId: entry.key);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Failed to migrate signed prekeys to Drift: $e');
|
|
||||||
migrationSuccess = false;
|
|
||||||
}
|
|
||||||
if (migrationSuccess) {
|
|
||||||
await UserService.update((u) => u.appVersion = 115);
|
await UserService.update((u) => u.appVersion = 115);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (userService.currentUser.appVersion < 116) {
|
if (userService.currentUser.appVersion < 116) {
|
||||||
if (userService.currentUser.userDiscoveryThreshold == 2) {
|
if (userService.currentUser.userDiscoveryThreshold == 2) {
|
||||||
|
|
|
||||||
|
|
@ -311,7 +311,11 @@ class PasswordlessRecoveryService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
unawaited(RustApi.performPasswordlessRecoveryHeartbeat());
|
unawaited(
|
||||||
|
RustApi.performPasswordlessRecoveryHeartbeat().catchError((e) {
|
||||||
|
Log.warn('Failed to perform passwordless recovery heartbeat: $e');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// The passwordless is configured successfully.
|
// The passwordless is configured successfully.
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// multi device support is not planned, so just set this to one
|
|
||||||
const int defaultDeviceId = 1;
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
// ignore: implementation_imports
|
|
||||||
import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|
||||||
as pb;
|
|
||||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
class SignalEncryptResult {
|
|
||||||
SignalEncryptResult(this.ciphertext, this.type);
|
|
||||||
final Uint8List ciphertext;
|
|
||||||
final pb.Message_Type type;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SignalEncryptResult?> signalEncryptMessage(
|
|
||||||
int target,
|
|
||||||
Uint8List plaintextContent,
|
|
||||||
) async {
|
|
||||||
return lockingSignalProtocol.protect<SignalEncryptResult?>(() async {
|
|
||||||
try {
|
|
||||||
final signalStore = (await getSignalStore())!;
|
|
||||||
final address = getSignalAddress(target);
|
|
||||||
final session = SessionCipher.fromStore(signalStore, address);
|
|
||||||
final cipherText = await session.encrypt(plaintextContent);
|
|
||||||
|
|
||||||
pb.Message_Type type;
|
|
||||||
switch (cipherText.getType()) {
|
|
||||||
case CiphertextMessage.prekeyType:
|
|
||||||
type = pb.Message_Type.PREKEY_BUNDLE;
|
|
||||||
case CiphertextMessage.whisperType:
|
|
||||||
type = pb.Message_Type.CIPHERTEXT;
|
|
||||||
default:
|
|
||||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SignalEncryptResult(cipherText.serialize(), type);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Could not encrypt message for target $target: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
|
|
||||||
signalDecryptMessageV1(
|
|
||||||
int fromUserId,
|
|
||||||
Uint8List encryptedContentRaw,
|
|
||||||
int type, {
|
|
||||||
Set<int>? brokenSessionsInCurrentBatch,
|
|
||||||
}) async {
|
|
||||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V1)');
|
|
||||||
final (
|
|
||||||
decryptedContent,
|
|
||||||
errorType,
|
|
||||||
needsResync,
|
|
||||||
) = await lockingSignalProtocol.protect(() async {
|
|
||||||
Log.info('Lock acquired for $fromUserId (V1)');
|
|
||||||
try {
|
|
||||||
// Yield execution to the event loop to prevent UI freezing during bulk decryption
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
|
|
||||||
final session = SessionCipher.fromStore(
|
|
||||||
(await getSignalStore())!,
|
|
||||||
getSignalAddress(fromUserId),
|
|
||||||
);
|
|
||||||
|
|
||||||
Uint8List plaintext;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case CiphertextMessage.prekeyType:
|
|
||||||
plaintext = await session.decrypt(
|
|
||||||
PreKeySignalMessage(encryptedContentRaw),
|
|
||||||
);
|
|
||||||
case CiphertextMessage.whisperType:
|
|
||||||
plaintext = await session.decryptFromSignal(
|
|
||||||
SignalMessage.fromSerialized(encryptedContentRaw),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
Log.error('Unknown Message Decryption Type: $type');
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
recordResyncAttempt(fromUserId, success: true);
|
|
||||||
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
|
|
||||||
} on InvalidKeyIdException catch (e) {
|
|
||||||
Log.warn(e);
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
pb.PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} on DuplicateMessageException catch (e) {
|
|
||||||
// This is normal behavior: This can happen in case a message was decrypted, but before further processing
|
|
||||||
// the user killed the app. This results in a new transmission from the server, but as the message was already
|
|
||||||
// decrypted, this error happens. In this case, request the message again.
|
|
||||||
Log.info(e);
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} on InvalidMessageException catch (e) {
|
|
||||||
Log.warn(e);
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Log.info('Released lockingSignalProtocol for $fromUserId (V1)');
|
|
||||||
|
|
||||||
// Handle session resync OUTSIDE the lock to avoid holding it during
|
|
||||||
// network round-trips (which can block for up to 60 seconds)
|
|
||||||
if (needsResync) {
|
|
||||||
brokenSessionsInCurrentBatch?.add(fromUserId);
|
|
||||||
if (shouldAttemptResync(fromUserId)) {
|
|
||||||
if (await handleSessionResync(fromUserId)) {
|
|
||||||
// This flag prevents from resyncing the session the client received
|
|
||||||
// multiple new messages from the server he could not decrypt
|
|
||||||
recordResyncAttempt(fromUserId, success: false);
|
|
||||||
|
|
||||||
// This message contains a new PreKeyBundle establishing a new signal
|
|
||||||
// session
|
|
||||||
await RustApi.sendEncryptedContent(
|
|
||||||
contactId: fromUserId,
|
|
||||||
content: pb.EncryptedContent(
|
|
||||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
|
||||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
|
||||||
),
|
|
||||||
).writeToBuffer(),
|
|
||||||
onlySendIfNoReceiptsAreOpen: false,
|
|
||||||
onlyReturnEncryptedData: false,
|
|
||||||
blocking: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (decryptedContent, errorType);
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
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/database/signal/signal_signed_pre_key_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/utils.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
Future<SignalIdentity?> getSignalIdentity() async {
|
|
||||||
try {
|
|
||||||
final identity = await RustKeyManager.getSignalIdentity();
|
|
||||||
return SignalIdentity(
|
|
||||||
identityKeyPairU8List: identity.$1,
|
|
||||||
registrationId: identity.$2,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('could not load signal identity: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<IdentityKeyPair?> getSignalIdentityKeyPair() async {
|
|
||||||
final signalIdentity = await getSignalIdentity();
|
|
||||||
if (signalIdentity == null) return null;
|
|
||||||
return IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> getUserPublicKey() async {
|
|
||||||
final signalIdentity = (await getSignalIdentity())!;
|
|
||||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
|
||||||
final keyPair = await signalStore.getIdentityKeyPair();
|
|
||||||
return keyPair.getPublicKey().serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createIfNotExistsSignalIdentity() async {
|
|
||||||
// check if identity already exists
|
|
||||||
final existingIdentity = await getSignalIdentity();
|
|
||||||
if (existingIdentity != null) {
|
|
||||||
final store = await getSignalStoreFromIdentity(existingIdentity);
|
|
||||||
final keys = await store.loadSignedPreKeys();
|
|
||||||
if (keys.isEmpty) {
|
|
||||||
Log.warn(
|
|
||||||
'Signal identity exists but signed prekeys are missing. Generating a new one.',
|
|
||||||
);
|
|
||||||
final keyPair = await store.getIdentityKeyPair();
|
|
||||||
final signedPreKey = generateSignedPreKey(keyPair, defaultDeviceId);
|
|
||||||
await SignalSignedPreKeyStore().storeSignedPreKey(
|
|
||||||
signedPreKey.id,
|
|
||||||
signedPreKey,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final identityKeyPair = generateIdentityKeyPair();
|
|
||||||
final registrationId = generateRegistrationId(true);
|
|
||||||
|
|
||||||
final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId);
|
|
||||||
|
|
||||||
await SignalSignedPreKeyStore().storeSignedPreKey(
|
|
||||||
signedPreKey.id,
|
|
||||||
signedPreKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
await RustKeyManager.importSignalIdentity(
|
|
||||||
identityKeyPairStructure: identityKeyPair.serialize(),
|
|
||||||
registrationId: registrationId,
|
|
||||||
signedPreKeyStore: const {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import 'dart:math';
|
|
||||||
import 'package:mutex/mutex.dart';
|
|
||||||
|
|
||||||
/// Unified lock for all Signal protocol operations (encryption, decryption, session management).
|
|
||||||
final lockingSignalProtocol = Mutex();
|
|
||||||
|
|
||||||
/// Tracking users who have already been resynced in the current session.
|
|
||||||
final Map<int, ({int failureCount, DateTime lastAttempt})> _resyncAttempts = {};
|
|
||||||
|
|
||||||
const int maxResyncAttempts = 3;
|
|
||||||
|
|
||||||
bool shouldAttemptResync(int userId) {
|
|
||||||
final attempt = _resyncAttempts[userId];
|
|
||||||
if (attempt == null) return true;
|
|
||||||
if (attempt.failureCount >= maxResyncAttempts) return false;
|
|
||||||
|
|
||||||
final cooldown = Duration(
|
|
||||||
minutes: 5 * pow(5, attempt.failureCount - 1).toInt(),
|
|
||||||
);
|
|
||||||
return DateTime.now().difference(attempt.lastAttempt) > cooldown;
|
|
||||||
}
|
|
||||||
|
|
||||||
void recordResyncAttempt(int userId, {required bool success}) {
|
|
||||||
if (success) {
|
|
||||||
_resyncAttempts.remove(userId);
|
|
||||||
} else {
|
|
||||||
final current = _resyncAttempts[userId];
|
|
||||||
_resyncAttempts[userId] = (
|
|
||||||
failureCount: (current?.failureCount ?? 0) + 1,
|
|
||||||
lastAttempt: DateTime.now(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the resync tracking set (currently unused, backoff handles expiry naturally).
|
|
||||||
void resetResyncedUsers() {
|
|
||||||
// No-op. We want the backoff state to persist across reconnects.
|
|
||||||
}
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart' as drift;
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
|
||||||
import 'package:twonly/core/signal/engine.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.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/utils/log.dart';
|
|
||||||
|
|
||||||
Future<bool> processSignalUserData(Response_UserData userData) async {
|
|
||||||
return lockingSignalProtocol.protect(() async {
|
|
||||||
return _processSignalUserData(userData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _processSignalUserData(Response_UserData userData) async {
|
|
||||||
if (userData.hasPqcBundle()) {
|
|
||||||
return _processSignalUserDataV2(userData);
|
|
||||||
}
|
|
||||||
return _processSignalUserDataV1(userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
|
||||||
try {
|
|
||||||
final tempIdentityKey = IdentityKey(
|
|
||||||
Curve.decodePoint(
|
|
||||||
DjbECPublicKey(
|
|
||||||
Uint8List.fromList(userData.publicIdentityKey),
|
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final signalStore = await getSignalStore();
|
|
||||||
if (signalStore != null) {
|
|
||||||
final existingIdentity = await signalStore.getIdentity(
|
|
||||||
SignalProtocolAddress(userData.userId.toString(), defaultDeviceId),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingIdentity != null && existingIdentity != tempIdentityKey) {
|
|
||||||
Log.error(
|
|
||||||
'Identity key mismatch for contact ${userData.userId}! Existing V1 key does not match the incoming V2 identity key.',
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int? tempEccPreKeyId;
|
|
||||||
Uint8List? tempEccPreKeyPublic;
|
|
||||||
if (userData.pqcBundle.hasPrekey()) {
|
|
||||||
tempEccPreKeyId = userData.pqcBundle.prekey.eccPreKeyId.toInt();
|
|
||||||
tempEccPreKeyPublic = Uint8List.fromList(
|
|
||||||
userData.pqcBundle.prekey.eccPreKey,
|
|
||||||
);
|
|
||||||
} else if (userData.prekeys.isNotEmpty) {
|
|
||||||
tempEccPreKeyId = userData.prekeys.first.id.toInt();
|
|
||||||
tempEccPreKeyPublic = Curve.decodePoint(
|
|
||||||
DjbECPublicKey(
|
|
||||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
).serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
final tempKyberPreKeyId = userData.pqcBundle.hasPrekey()
|
|
||||||
? userData.pqcBundle.prekey.kyberPreKeyId.toInt()
|
|
||||||
: userData.pqcBundle.kyberSignedPrekeyId.toInt();
|
|
||||||
final tempKyberPreKeyPublic = userData.pqcBundle.hasPrekey()
|
|
||||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKey)
|
|
||||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekey);
|
|
||||||
final tempKyberPreKeySignature = userData.pqcBundle.hasPrekey()
|
|
||||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKeySignature)
|
|
||||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekeySignature);
|
|
||||||
|
|
||||||
final rustBundle = FrbPreKeyBundle(
|
|
||||||
registrationId: userData.registrationId.toInt(),
|
|
||||||
deviceId: 1,
|
|
||||||
preKeyId: tempEccPreKeyId,
|
|
||||||
preKeyPublic: tempEccPreKeyPublic,
|
|
||||||
signedPreKeyId: userData.pqcBundle.eccSignedPrekeyId.toInt(),
|
|
||||||
signedPreKeyPublic: Uint8List.fromList(
|
|
||||||
userData.pqcBundle.eccSignedPrekey,
|
|
||||||
),
|
|
||||||
signedPreKeySignature: Uint8List.fromList(
|
|
||||||
userData.pqcBundle.eccSignedPrekeySignature,
|
|
||||||
),
|
|
||||||
identityKey: tempIdentityKey.publicKey.serialize(),
|
|
||||||
kyberPreKeyId: tempKyberPreKeyId,
|
|
||||||
kyberPreKeyPublic: tempKyberPreKeyPublic,
|
|
||||||
kyberPreKeySignature: tempKyberPreKeySignature,
|
|
||||||
);
|
|
||||||
|
|
||||||
await RustSignal.processPrekeyBundle(
|
|
||||||
name: userData.userId.toString(),
|
|
||||||
deviceId: 1,
|
|
||||||
bundle: rustBundle,
|
|
||||||
);
|
|
||||||
|
|
||||||
final contact = await twonlyDB.contactsDao.getContactById(
|
|
||||||
userData.userId.toInt(),
|
|
||||||
);
|
|
||||||
if (contact != null && contact.signalVersion != SignalVersion.v2) {
|
|
||||||
await twonlyDB.contactsDao.updateContact(
|
|
||||||
contact.userId,
|
|
||||||
const ContactsCompanion(signalVersion: drift.Value(SignalVersion.v2)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('could not process pqc bundle: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _processSignalUserDataV1(Response_UserData userData) async {
|
|
||||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
|
||||||
|
|
||||||
if (signalStore == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final targetAddress = getSignalAddress(userData.userId.toInt());
|
|
||||||
|
|
||||||
final sessionBuilder = SessionBuilder.fromSignalStore(
|
|
||||||
signalStore,
|
|
||||||
targetAddress,
|
|
||||||
);
|
|
||||||
|
|
||||||
ECPublicKey? tempPrePublicKey;
|
|
||||||
int? tempPreKeyId;
|
|
||||||
|
|
||||||
if (userData.prekeys.isNotEmpty) {
|
|
||||||
tempPrePublicKey = Curve.decodePoint(
|
|
||||||
DjbECPublicKey(
|
|
||||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
|
||||||
|
|
||||||
final tempSignedPreKeyPublic = Curve.decodePoint(
|
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
final tempSignedPreKeySignature = Uint8List.fromList(
|
|
||||||
userData.signedPrekeySignature,
|
|
||||||
);
|
|
||||||
|
|
||||||
final tempIdentityKey = IdentityKey(
|
|
||||||
Curve.decodePoint(
|
|
||||||
DjbECPublicKey(
|
|
||||||
Uint8List.fromList(userData.publicIdentityKey),
|
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final preKeyBundle = PreKeyBundle(
|
|
||||||
userData.registrationId.toInt(),
|
|
||||||
defaultDeviceId,
|
|
||||||
tempPreKeyId,
|
|
||||||
tempPrePublicKey,
|
|
||||||
tempSignedPreKeyId,
|
|
||||||
tempSignedPreKeyPublic,
|
|
||||||
tempSignedPreKeySignature,
|
|
||||||
tempIdentityKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('could not process pre key bundle: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
|
|
||||||
final signalStore = await getSignalStore();
|
|
||||||
if (signalStore == null) return null;
|
|
||||||
try {
|
|
||||||
final targetIdentity = await signalStore.getIdentity(
|
|
||||||
SignalProtocolAddress(
|
|
||||||
contactId.toString(),
|
|
||||||
defaultDeviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (targetIdentity != null) {
|
|
||||||
return targetIdentity.publicKey.serialize();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> handleSessionResync(int fromUserId) async {
|
|
||||||
final userData = await rustApiProtobuf(
|
|
||||||
RustApi.getUserById(userId: fromUserId),
|
|
||||||
decodeUserData,
|
|
||||||
);
|
|
||||||
if (userData != null) {
|
|
||||||
Log.info('Got new session data from the server to re-sync the session');
|
|
||||||
return processSignalUserData(userData);
|
|
||||||
}
|
|
||||||
Log.info('Could not download userdata from the server.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.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/identity.signal.dart';
|
|
||||||
|
|
||||||
Future<SignalSignalProtocolStore?> getSignalStore() async {
|
|
||||||
return getSignalStoreFromIdentity((await getSignalIdentity())!);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SignalSignalProtocolStore> getSignalStoreFromIdentity(
|
|
||||||
SignalIdentity signalIdentity,
|
|
||||||
) async {
|
|
||||||
final identityKeyPair = IdentityKeyPair.fromSerialized(
|
|
||||||
signalIdentity.identityKeyPairU8List,
|
|
||||||
);
|
|
||||||
|
|
||||||
return SignalSignalProtocolStore(
|
|
||||||
identityKeyPair,
|
|
||||||
signalIdentity.registrationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalProtocolAddress getSignalAddress(int userId) {
|
|
||||||
return SignalProtocolAddress(userId.toString(), defaultDeviceId);
|
|
||||||
}
|
|
||||||
|
|
@ -94,7 +94,11 @@ class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> handleRustUserConfigChanged(UserConfig config) async {
|
static Future<void> handleRustUserConfigChanged(UserConfig config) async {
|
||||||
|
try {
|
||||||
userService._applyRustUserConfig(config);
|
userService._applyRustUserConfig(config);
|
||||||
|
} catch (e) {
|
||||||
|
Log.warn(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyRustUserConfig(UserConfig config, {bool notify = true}) {
|
void _applyRustUserConfig(UserConfig config, {bool notify = true}) {
|
||||||
|
|
|
||||||
|
|
@ -6,41 +6,27 @@ import 'package:collection/collection.dart' show ListExtensions;
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
|
||||||
import 'package:twonly/src/services/key_verification.service.dart';
|
import 'package:twonly/src/services/key_verification.service.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class QrCodeUtils {
|
class QrCodeUtils {
|
||||||
static String linkPrefix = 'https://me.twonly.eu/qr/#';
|
static String linkPrefix = 'https://me.twonly.eu/qr/#';
|
||||||
|
|
||||||
static Future<String> publicProfileLink() async {
|
static Future<String> publicProfileLink() async {
|
||||||
final signalIdentity = (await getSignalIdentity())!;
|
final publicIdentityKey = await RustSignal.getUserPublicKey();
|
||||||
|
|
||||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
|
||||||
|
|
||||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
|
||||||
|
|
||||||
final secretVerificationToken =
|
final secretVerificationToken =
|
||||||
await KeyVerificationService.getNewSecretVerificationToken();
|
await KeyVerificationService.getNewSecretVerificationToken();
|
||||||
|
|
||||||
final publicProfile = PublicProfile(
|
final publicProfile = PublicProfile(
|
||||||
userId: Int64(userService.currentUser.userId),
|
userId: Int64(userService.currentUser.userId),
|
||||||
username: userService.currentUser.username,
|
username: userService.currentUser.username,
|
||||||
publicIdentityKey: (await signalStore.getIdentityKeyPair())
|
publicIdentityKey: publicIdentityKey,
|
||||||
.getPublicKey()
|
|
||||||
.serialize(),
|
|
||||||
registrationId: Int64(signalIdentity.registrationId),
|
|
||||||
signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(),
|
|
||||||
signedPrekeySignature: signedPreKey.signature,
|
|
||||||
signedPrekeyId: Int64(signedPreKey.id),
|
|
||||||
secretVerificationToken: secretVerificationToken,
|
secretVerificationToken: secretVerificationToken,
|
||||||
timestamp: Int64(clock.now().millisecondsSinceEpoch),
|
timestamp: Int64(clock.now().millisecondsSinceEpoch),
|
||||||
);
|
);
|
||||||
|
|
@ -88,7 +74,9 @@ class QrCodeUtils {
|
||||||
return (profile, null, false);
|
return (profile, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
final storedPublicKey = await RustSignal.getContactPublicKey(
|
||||||
|
contactId: contact.userId,
|
||||||
|
);
|
||||||
if (storedPublicKey == null) return null;
|
if (storedPublicKey == null) return null;
|
||||||
|
|
||||||
final verificationOk = profile.publicIdentityKey.equals(
|
final verificationOk = profile.publicIdentityKey.equals(
|
||||||
|
|
@ -127,12 +115,18 @@ class QrCodeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
||||||
final userdata = Response_UserData(
|
try {
|
||||||
userId: profile.userId,
|
await RustApi.establishSignalSession(
|
||||||
publicIdentityKey: profile.publicIdentityKey,
|
contactId: profile.userId.toInt(),
|
||||||
signedPrekey: profile.signedPrekey,
|
expectedPublicKey: Uint8List.fromList(profile.publicIdentityKey),
|
||||||
signedPrekeyId: profile.signedPrekeyId,
|
);
|
||||||
signedPrekeySignature: profile.signedPrekeySignature,
|
await RustApi.sendEncryptedContent(
|
||||||
|
contactId: profile.userId.toInt(),
|
||||||
|
content: EncryptedContent(
|
||||||
|
contactRequest: EncryptedContent_ContactRequest(
|
||||||
|
type: EncryptedContent_ContactRequest_Type.REQUEST,
|
||||||
|
),
|
||||||
|
).writeToBuffer(),
|
||||||
);
|
);
|
||||||
|
|
||||||
final added = await twonlyDB.contactsDao.insertOnConflictUpdate(
|
final added = await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
|
|
@ -145,14 +139,13 @@ Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (added > 0) {
|
||||||
// The user was added via the profile scanned from the QR code so the scanned public key was used.
|
// The user was added via the profile scanned from the QR code so the scanned public key was used.
|
||||||
await twonlyDB.keyVerificationDao.addKeyVerification(
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
profile.userId.toInt(),
|
profile.userId.toInt(),
|
||||||
VerificationType.qrScanned,
|
VerificationType.qrScanned,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (added > 0) {
|
|
||||||
if (await importSignalContactAndCreateRequest(userdata)) {
|
|
||||||
if (profile.hasSecretVerificationToken()) {
|
if (profile.hasSecretVerificationToken()) {
|
||||||
await KeyVerificationService.handleScannedVerificationToken(
|
await KeyVerificationService.handleScannedVerificationToken(
|
||||||
profile.userId.toInt(),
|
profile.userId.toInt(),
|
||||||
|
|
@ -160,10 +153,10 @@ Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
||||||
profile.secretVerificationToken,
|
profile.secretVerificationToken,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
} catch (e) {
|
||||||
|
Log.error('Failed to establish session and send contact request: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.utils.dart';
|
import 'package:twonly/src/utils/qr.utils.dart';
|
||||||
import 'package:twonly/src/visual/components/add_contact_dialog.comp.dart';
|
import 'package:twonly/src/visual/components/add_contact_dialog.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||||
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||||
|
|
@ -555,12 +556,23 @@ class MainCameraController {
|
||||||
profile.username,
|
profile.username,
|
||||||
);
|
);
|
||||||
if (shouldRequest == true && context.mounted) {
|
if (shouldRequest == true && context.mounted) {
|
||||||
|
final success = await addNewContactFromPublicProfile(profile);
|
||||||
|
if (context.mounted) {
|
||||||
|
if (success) {
|
||||||
showSnackbar(
|
showSnackbar(
|
||||||
context,
|
context,
|
||||||
context.lang.requestedUserToastText(profile.username),
|
context.lang.requestedUserToastText(profile.username),
|
||||||
level: SnackbarLevel.success,
|
level: SnackbarLevel.success,
|
||||||
);
|
);
|
||||||
await addNewContactFromPublicProfile(profile);
|
} else {
|
||||||
|
await showAlertDialog(
|
||||||
|
context,
|
||||||
|
context.lang.groupNetworkIssue,
|
||||||
|
context.lang.recoverErrorNoInternet,
|
||||||
|
customCancel: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,8 @@ class TwitterParser with BaseMetaInfo {
|
||||||
_url.startsWith('https://x.com/') && _url.contains('/status/')
|
_url.startsWith('https://x.com/') && _url.contains('/status/')
|
||||||
? Vendor.twitterPosting
|
? Vendor.twitterPosting
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get siteName =>
|
||||||
|
vendor == Vendor.twitterPosting ? 'X (formerly Twitter)' : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'
|
||||||
show FaIcon, FontAwesomeIcons;
|
show FaIcon, FontAwesomeIcons;
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart'
|
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart'
|
||||||
show ProfileQrCodeComp;
|
show ProfileQrCodeComp;
|
||||||
|
|
@ -17,7 +17,7 @@ class EmptyChatListComp extends StatelessWidget {
|
||||||
|
|
||||||
Future<void> _shareProfile(BuildContext context) async {
|
Future<void> _shareProfile(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
final pubKey = await getUserPublicKey();
|
final pubKey = await RustSignal.getUserPublicKey();
|
||||||
final params = ShareParams(
|
final params = ShareParams(
|
||||||
text:
|
text:
|
||||||
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
import 'package:twonly/src/services/api/utils.api.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||||
|
|
@ -94,7 +94,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _shareProfile() async {
|
Future<void> _shareProfile() async {
|
||||||
final pubKey = await getUserPublicKey();
|
final pubKey = await RustSignal.getUserPublicKey();
|
||||||
final params = ShareParams(
|
final params = ShareParams(
|
||||||
text:
|
text:
|
||||||
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.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/avatar_icon.comp.dart';
|
||||||
|
|
@ -123,7 +122,9 @@ class _ContactViewState extends State<ContactView> {
|
||||||
Future<void> handleReportUser(Contact contact) async {
|
Future<void> handleReportUser(Contact contact) async {
|
||||||
final reason = await showReportDialog(context, contact);
|
final reason = await showReportDialog(context, contact);
|
||||||
if (reason == null) return;
|
if (reason == null) return;
|
||||||
final res = await rustApiResult(RustApi.reportUser(userId: contact.userId, reason: reason));
|
final res = await rustApiResult(
|
||||||
|
RustApi.reportUser(userId: contact.userId, reason: reason),
|
||||||
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
showSnackbar(
|
showSnackbar(
|
||||||
|
|
@ -281,9 +282,10 @@ class _ContactViewState extends State<ContactView> {
|
||||||
icon: FontAwesomeIcons.arrowsRotate,
|
icon: FontAwesomeIcons.arrowsRotate,
|
||||||
text: 'Update Connection to V2 (PQXDH)',
|
text: 'Update Connection to V2 (PQXDH)',
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final userData = await rustApiProtobuf(RustApi.getUserById(userId: contact.userId), decodeUserData);
|
try {
|
||||||
if (userData != null) {
|
await RustApi.establishSignalSession(
|
||||||
await processSignalUserData(userData);
|
contactId: contact.userId,
|
||||||
|
);
|
||||||
final updatedContact = await twonlyDB.contactsDao
|
final updatedContact = await twonlyDB.contactsDao
|
||||||
.getContactById(contact.userId);
|
.getContactById(contact.userId);
|
||||||
final isV2 =
|
final isV2 =
|
||||||
|
|
@ -300,6 +302,13 @@ class _ContactViewState extends State<ContactView> {
|
||||||
: SnackbarLevel.error,
|
: SnackbarLevel.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackbar(
|
||||||
|
context,
|
||||||
|
'Failed to update connection to V2: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
|
|
@ -135,21 +134,7 @@ class _GroupViewState extends State<GroupView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
late bool success;
|
final success = await leaveGroup(_group!);
|
||||||
|
|
||||||
if (_group!.isGroupAdmin) {
|
|
||||||
// Current user is a admin, to the state can be updated by the user him self.
|
|
||||||
final keyPair = IdentityKeyPair.fromSerialized(
|
|
||||||
_group!.myGroupPrivateKey!,
|
|
||||||
);
|
|
||||||
success = !(await removeMemberFromGroup(
|
|
||||||
_group!,
|
|
||||||
keyPair.getPublicKey().serialize(),
|
|
||||||
userService.currentUser.userId,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
success = await leaveAsNonAdminFromGroup(_group!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -99,8 +98,6 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
|
|
||||||
Log.info('The result of the POW is $proof');
|
Log.info('The result of the POW is $proof');
|
||||||
|
|
||||||
await createIfNotExistsSignalIdentity();
|
|
||||||
|
|
||||||
var userId = 0;
|
var userId = 0;
|
||||||
|
|
||||||
final res = await rustApiResult(
|
final res = await rustApiResult(
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/contact_request_badge.comp.dart';
|
import 'package:twonly/src/visual/components/contact_request_badge.comp.dart';
|
||||||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||||
|
|
@ -31,7 +31,7 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
_publicKey = await getUserPublicKey();
|
_publicKey = await RustSignal.getUserPublicKey();
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
@ -34,57 +32,6 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
|
||||||
title: const Text('Trigger Signal Out-Of-Sync'),
|
|
||||||
onTap: () async {
|
|
||||||
final username = await showUserNameDialog(context);
|
|
||||||
if (username == null) return;
|
|
||||||
final contacts = await twonlyDB.contactsDao.getContactsByUsername(
|
|
||||||
username.toLowerCase(),
|
|
||||||
);
|
|
||||||
if (contacts.length != 1) {
|
|
||||||
Log.error('No single user fund');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final userId = contacts.first.userId;
|
|
||||||
|
|
||||||
final group = await twonlyDB.groupsDao.getDirectChat(userId);
|
|
||||||
if (group == null) {
|
|
||||||
Log.error('Target user must have a group!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final sessionStore = await getSignalStore();
|
|
||||||
|
|
||||||
// 1. Store a valid session
|
|
||||||
final originalSession = await sessionStore!.loadSession(
|
|
||||||
getSignalAddress(userId),
|
|
||||||
);
|
|
||||||
final serializedSession = originalSession.serialize();
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++) {
|
|
||||||
await RustApi.insertAndSendText(
|
|
||||||
groupId: group.groupId,
|
|
||||||
text: 'DesyncTest_1',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final corruptedSession = SessionRecord.fromSerialized(
|
|
||||||
serializedSession,
|
|
||||||
);
|
|
||||||
await sessionStore.storeSession(
|
|
||||||
getSignalAddress(userId),
|
|
||||||
corruptedSession,
|
|
||||||
);
|
|
||||||
|
|
||||||
await RustApi.insertAndSendText(
|
|
||||||
groupId: group.groupId,
|
|
||||||
text: 'DesyncTest_2',
|
|
||||||
);
|
|
||||||
|
|
||||||
// The other client should res
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Sending a lot of messages.'),
|
title: const Text('Sending a lot of messages.'),
|
||||||
subtitle: Text(lotsOfMessagesStatus),
|
subtitle: Text(lotsOfMessagesStatus),
|
||||||
|
|
|
||||||
42
pubspec.lock
42
pubspec.lock
|
|
@ -17,13 +17,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.68"
|
version: "1.3.68"
|
||||||
adaptive_number:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/adaptive_number"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "1.0.0"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -411,13 +404,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
ed25519_edwards:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/ed25519_edwards"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.3.1"
|
|
||||||
emoji_picker_flutter:
|
emoji_picker_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1190,13 +1176,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
libsignal_protocol_dart:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/libsignal_protocol_dart"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.8.0"
|
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1347,13 +1326,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0"
|
version: "9.3.0"
|
||||||
optional:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/optional"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "6.1.0+1"
|
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1529,13 +1501,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
pointycastle:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/pointycastle"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "4.0.0"
|
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2139,13 +2104,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.1+1"
|
version: "0.9.1+1"
|
||||||
x25519:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "dependencies/x25519"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.1.1"
|
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
18
pubspec.yaml
18
pubspec.yaml
|
|
@ -110,12 +110,6 @@ dependencies:
|
||||||
image: ^4.9.2
|
image: ^4.9.2
|
||||||
introduction_screen: ^4.0.0
|
introduction_screen: ^4.0.0
|
||||||
dots_indicator: ^4.0.1
|
dots_indicator: ^4.0.1
|
||||||
libsignal_protocol_dart: ^0.8.0
|
|
||||||
adaptive_number: ^1.0.0
|
|
||||||
ed25519_edwards: ^0.3.1
|
|
||||||
optional: ^6.1.0+1
|
|
||||||
pointycastle: ^4.0.0
|
|
||||||
x25519: ^0.1.1
|
|
||||||
lottie: ^3.5.1
|
lottie: ^3.5.1
|
||||||
mutex: ^3.1.0
|
mutex: ^3.1.0
|
||||||
photo_view: ^0.15.0
|
photo_view: ^0.15.0
|
||||||
|
|
@ -176,18 +170,6 @@ dependency_overrides:
|
||||||
path: ./dependencies/introduction_screen
|
path: ./dependencies/introduction_screen
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
path: ./dependencies/dots_indicator
|
path: ./dependencies/dots_indicator
|
||||||
libsignal_protocol_dart:
|
|
||||||
path: ./dependencies/libsignal_protocol_dart
|
|
||||||
adaptive_number:
|
|
||||||
path: ./dependencies/adaptive_number
|
|
||||||
ed25519_edwards:
|
|
||||||
path: ./dependencies/ed25519_edwards
|
|
||||||
optional:
|
|
||||||
path: ./dependencies/optional
|
|
||||||
pointycastle:
|
|
||||||
path: ./dependencies/pointycastle
|
|
||||||
x25519:
|
|
||||||
path: ./dependencies/x25519
|
|
||||||
lottie:
|
lottie:
|
||||||
path: ./dependencies/lottie
|
path: ./dependencies/lottie
|
||||||
mutex:
|
mutex:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ edition = "2021"
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
flutter_rust_bridge = { version = "=2.12.0", features = ["chrono"] }
|
flutter_rust_bridge = { version = "=2.12.0", features = ["chrono"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
|
|
@ -49,14 +52,19 @@ rand08 = { version = "0.8.5", package = "rand" }
|
||||||
rand = "0.9.4"
|
rand = "0.9.4"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = [
|
||||||
|
"rustls-tls",
|
||||||
|
] }
|
||||||
async-trait = "0.1.91"
|
async-trait = "0.1.91"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
tokio-tungstenite = { version = "0.28.0", default-features = false, features = [
|
tokio-tungstenite = { version = "0.28.0", default-features = false, features = [
|
||||||
"connect",
|
"connect",
|
||||||
"rustls-tls-native-roots",
|
"rustls-tls-native-roots",
|
||||||
] }
|
] }
|
||||||
rustls = { version = "0.23.43", default-features = false, features = ["ring", "std"] }
|
rustls = { version = "0.23.43", default-features = false, features = [
|
||||||
|
"ring",
|
||||||
|
"std",
|
||||||
|
] }
|
||||||
tokio-util = "0.7.16"
|
tokio-util = "0.7.16"
|
||||||
bon = "3.9.3"
|
bon = "3.9.3"
|
||||||
stream-tungstenite = "0.6.1"
|
stream-tungstenite = "0.6.1"
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,18 @@ mod verification;
|
||||||
|
|
||||||
use crate::api::messages::content_type_kind;
|
use crate::api::messages::content_type_kind;
|
||||||
use crate::api::messages::incoming::messages::{
|
use crate::api::messages::incoming::messages::{
|
||||||
decrypt_legacy_signal_with_error, ensure_contact_exists, handle_plaintext_content,
|
ensure_contact_exists, handle_plaintext_content, handle_sender_delivery_receipt,
|
||||||
handle_sender_delivery_receipt, process_encrypted_or_queue_error, queue_decryption_error,
|
process_encrypted_or_queue_error, queue_decryption_error, queue_sender_delivery_receipt,
|
||||||
queue_sender_delivery_receipt, retransmit_queued_receipts, spawn_receipt_delivery,
|
retransmit_queued_receipts, spawn_receipt_delivery,
|
||||||
};
|
};
|
||||||
use crate::api::proto::client as proto;
|
use crate::api::proto::client as proto;
|
||||||
use crate::api::proto::server_to_client::NewMessage;
|
use crate::api::proto::server_to_client::NewMessage;
|
||||||
use crate::api::proto::{client_to_server, server_to_client};
|
use crate::api::proto::{client_to_server, server_to_client};
|
||||||
use crate::bridge::callbacks::get_callbacks;
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::database::app::tables::{Contact, Group, Receipt};
|
use crate::database::app::tables::{Contact, Group, Receipt};
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use crate::sealed_sender::SealedSender;
|
use crate::sealed_sender::SealedSender;
|
||||||
|
use crate::services::contacts::ContactService;
|
||||||
use client_to_server::response::{ok, Response};
|
use client_to_server::response::{ok, Response};
|
||||||
use prost::Message as _;
|
use prost::Message as _;
|
||||||
use proto::message::Type;
|
use proto::message::Type;
|
||||||
|
|
@ -210,40 +210,45 @@ pub(crate) async fn handle_decoded_server_message(
|
||||||
handle_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?;
|
handle_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?;
|
||||||
}
|
}
|
||||||
Type::Ciphertext | Type::PrekeyBundle => {
|
Type::Ciphertext | Type::PrekeyBundle => {
|
||||||
let ciphertext = message.encrypted_content.ok_or_else(|| {
|
tracing::info!("Received legacy signal message; rejecting and upgrading session to v2");
|
||||||
TwonlyError::Generic("legacy encrypted client message has no ciphertext".into())
|
|
||||||
})?;
|
let has_v2_session = {
|
||||||
tracing::info!("Decrypting legacy signal message...");
|
let rust_database = ctx.rust_db.read().await.clone();
|
||||||
match decrypt_legacy_signal_with_error(from_user_id, ciphertext, message.r#type).await?
|
sqlx::query_scalar!(
|
||||||
{
|
"SELECT EXISTS(SELECT 1 FROM signal_sessions WHERE name = ? AND device_id = 1)",
|
||||||
Ok(content) => {
|
from_user_id.to_string(),
|
||||||
tracing::info!("Decrypted successfully, processing...");
|
|
||||||
sends_error_response = process_encrypted_or_queue_error(
|
|
||||||
ctx,
|
|
||||||
&mut t,
|
|
||||||
from_user_id,
|
|
||||||
&message.receipt_id,
|
|
||||||
content,
|
|
||||||
)
|
)
|
||||||
|
.fetch_one(&rust_database.pool)
|
||||||
.await?
|
.await?
|
||||||
.is_some();
|
!= 0
|
||||||
}
|
};
|
||||||
Err(error_type) => {
|
|
||||||
tracing::info!(error_type, "Decryption error");
|
let is_v2_contact = {
|
||||||
if error_type
|
let contact = Contact::get_contact_by_id(&mut t, from_user_id).await?;
|
||||||
== proto::plaintext_content::decryption_error_message::Type::PrekeyUnknown
|
contact.as_ref().is_some_and(|c| c.signal_version == "v2")
|
||||||
as i32
|
};
|
||||||
|
|
||||||
|
if !has_v2_session || !is_v2_contact {
|
||||||
|
if let Err(error) = ContactService::new(ctx)
|
||||||
|
.establish_signal_session(from_user_id, None)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
if let Ok(callbacks) = get_callbacks() {
|
tracing::warn!(
|
||||||
(callbacks.api.resync_signal_session)(from_user_id).await;
|
from_user_id,
|
||||||
|
"failed to establish v2 signal session from server: {error}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
sqlx::query!(
|
||||||
queue_decryption_error(&mut t, from_user_id, &message.receipt_id, error_type)
|
"UPDATE contacts SET signal_version = 'v2' WHERE user_id = ?",
|
||||||
|
from_user_id,
|
||||||
|
)
|
||||||
|
.execute(&mut *t)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_decryption_error(&mut t, from_user_id, &message.receipt_id, 0).await?;
|
||||||
sends_error_response = true;
|
sends_error_response = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::CiphertextV2 => {
|
Type::CiphertextV2 => {
|
||||||
let ciphertext = message.encrypted_content.ok_or_else(|| {
|
let ciphertext = message.encrypted_content.ok_or_else(|| {
|
||||||
TwonlyError::Generic("V2 encrypted client message has no ciphertext".into())
|
TwonlyError::Generic("V2 encrypted client message has no ciphertext".into())
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ use super::handle_encrypted;
|
||||||
use crate::api::proto::client::{self as proto};
|
use crate::api::proto::client::{self as proto};
|
||||||
use crate::api::Server;
|
use crate::api::Server;
|
||||||
use crate::bridge::api::ServerResult;
|
use crate::bridge::api::ServerResult;
|
||||||
use crate::bridge::callbacks::get_callbacks;
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::database::app::tables::{Contact, MediaFile, NewReceipt, Receipt};
|
use crate::database::app::tables::{Contact, MediaFile, NewReceipt, Receipt};
|
||||||
use crate::error::{twonly_error, Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use crate::services::contacts::ContactService;
|
use crate::services::contacts::ContactService;
|
||||||
use crate::utils::new_uuid_v4;
|
use crate::utils::new_uuid_v4;
|
||||||
use prost::Message as ProstMessage;
|
use prost::Message as ProstMessage;
|
||||||
|
|
@ -31,16 +30,10 @@ pub(crate) async fn queue_encrypted_content(
|
||||||
content: proto::EncryptedContent,
|
content: proto::EncryptedContent,
|
||||||
contact_will_send_receipt: bool,
|
contact_will_send_receipt: bool,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let Some(contact) = Contact::get_contact_by_id(t, target_user_id).await? else {
|
Contact::ensure_exists(t, target_user_id).await?;
|
||||||
return Err(twonly_error!("missing contact"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let message = proto::Message {
|
let message = proto::Message {
|
||||||
r#type: if contact.signal_version == "v2" {
|
r#type: proto::message::Type::CiphertextV2 as i32,
|
||||||
proto::message::Type::CiphertextV2 as i32
|
|
||||||
} else {
|
|
||||||
proto::message::Type::Ciphertext as i32
|
|
||||||
},
|
|
||||||
receipt_id: String::new(),
|
receipt_id: String::new(),
|
||||||
encrypted_content: Some(content.encode_to_vec()),
|
encrypted_content: Some(content.encode_to_vec()),
|
||||||
plaintext_content: None,
|
plaintext_content: None,
|
||||||
|
|
@ -97,17 +90,8 @@ pub(crate) async fn process_encrypted_or_queue_error(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let signal_version = Contact::get_contact_by_id(t, from_user_id)
|
|
||||||
.await?
|
|
||||||
.map(|c| c.signal_version)
|
|
||||||
.unwrap_or_else(|| "v2".to_string());
|
|
||||||
|
|
||||||
let response = proto::Message {
|
let response = proto::Message {
|
||||||
r#type: if signal_version == "v2" {
|
r#type: proto::message::Type::CiphertextV2 as i32,
|
||||||
proto::message::Type::CiphertextV2 as i32
|
|
||||||
} else {
|
|
||||||
proto::message::Type::Ciphertext as i32
|
|
||||||
},
|
|
||||||
receipt_id: String::new(),
|
receipt_id: String::new(),
|
||||||
encrypted_content: Some(response_content.encode_to_vec()),
|
encrypted_content: Some(response_content.encode_to_vec()),
|
||||||
plaintext_content: None,
|
plaintext_content: None,
|
||||||
|
|
@ -298,7 +282,7 @@ async fn encrypt_v2_with_session_recovery(
|
||||||
);
|
);
|
||||||
|
|
||||||
ContactService::new(ctx)
|
ContactService::new(ctx)
|
||||||
.establish_signal_session(contact_id)
|
.establish_signal_session(contact_id, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
encrypt(plaintext).await
|
encrypt(plaintext).await
|
||||||
|
|
@ -307,6 +291,84 @@ async fn encrypt_v2_with_session_recovery(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct PreparedQueuedReceipt {
|
||||||
|
pub contact_id: i64,
|
||||||
|
pub message_id: Option<String>,
|
||||||
|
pub contact_will_sends_receipt: i64,
|
||||||
|
pub account_deleted: i64,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn prepare_queued_receipt_details(
|
||||||
|
ctx: &Arc<Context>,
|
||||||
|
receipt_id: &str,
|
||||||
|
) -> Result<Option<PreparedQueuedReceipt>> {
|
||||||
|
let app_db = ctx.app_db.read().await.clone();
|
||||||
|
let row = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT r.contact_id, r.message, r.message_id, r.contact_will_sends_receipt,
|
||||||
|
r.retry_count, c.account_deleted, c.signal_version
|
||||||
|
FROM receipts r
|
||||||
|
JOIN contacts c ON c.user_id = r.contact_id
|
||||||
|
WHERE r.receipt_id = ?
|
||||||
|
"#,
|
||||||
|
receipt_id,
|
||||||
|
)
|
||||||
|
.fetch_optional(&app_db.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(row) = row else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut message = proto::Message::decode(row.message.as_slice())
|
||||||
|
.map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?;
|
||||||
|
message.receipt_id = receipt_id.to_owned();
|
||||||
|
|
||||||
|
let message_type = proto::message::Type::try_from(message.r#type)
|
||||||
|
.map_err(|_| TwonlyError::Generic("queued message has invalid type".into()))?;
|
||||||
|
|
||||||
|
let is_encrypted = matches!(
|
||||||
|
message_type,
|
||||||
|
proto::message::Type::Ciphertext
|
||||||
|
| proto::message::Type::PrekeyBundle
|
||||||
|
| proto::message::Type::CiphertextV2
|
||||||
|
);
|
||||||
|
|
||||||
|
if is_encrypted {
|
||||||
|
if row.signal_version != "v2" {
|
||||||
|
ContactService::new(ctx)
|
||||||
|
.establish_signal_session(row.contact_id, None)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let plaintext = message.encrypted_content.take().ok_or_else(|| {
|
||||||
|
TwonlyError::Generic("queued encrypted message has no content".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
message.encrypted_content =
|
||||||
|
Some(encrypt_v2_with_session_recovery(ctx, row.contact_id, plaintext).await?);
|
||||||
|
message.r#type = proto::message::Type::CiphertextV2 as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(PreparedQueuedReceipt {
|
||||||
|
contact_id: row.contact_id,
|
||||||
|
message_id: row.message_id,
|
||||||
|
contact_will_sends_receipt: row.contact_will_sends_receipt,
|
||||||
|
account_deleted: row.account_deleted,
|
||||||
|
payload: message.encode_to_vec(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn prepare_queued_receipt(
|
||||||
|
ctx: &Arc<Context>,
|
||||||
|
receipt_id: &str,
|
||||||
|
) -> Result<Option<Vec<u8>>> {
|
||||||
|
Ok(prepare_queued_receipt_details(ctx, receipt_id)
|
||||||
|
.await?
|
||||||
|
.map(|receipt| receipt.payload))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) -> Result<()> {
|
pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) -> Result<()> {
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
{
|
{
|
||||||
|
|
@ -324,56 +386,17 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
let app_db = ctx.app_db.read().await.clone();
|
let app_db = ctx.app_db.read().await.clone();
|
||||||
let row = sqlx::query!(
|
let Some(receipt) = prepare_queued_receipt_details(ctx, receipt_id).await? else {
|
||||||
r#"
|
|
||||||
SELECT r.contact_id, r.message, r.message_id, r.contact_will_sends_receipt,
|
|
||||||
r.retry_count, c.account_deleted
|
|
||||||
FROM receipts r
|
|
||||||
JOIN contacts c ON c.user_id = r.contact_id
|
|
||||||
WHERE r.receipt_id = ?
|
|
||||||
"#,
|
|
||||||
receipt_id,
|
|
||||||
)
|
|
||||||
.fetch_optional(&app_db.pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(row) = row else {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if row.account_deleted != 0 {
|
if receipt.account_deleted != 0 {
|
||||||
Receipt::delete(&app_db.pool, receipt_id).await?;
|
Receipt::delete(&app_db.pool, receipt_id).await?;
|
||||||
app_db.notify_committed(["receipts"]);
|
app_db.notify_committed(["receipts"]);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut message = proto::Message::decode(row.message.as_slice())?;
|
match Server::send_text_message(ctx, receipt.contact_id, receipt.payload).await? {
|
||||||
message.receipt_id = receipt_id.to_owned();
|
|
||||||
|
|
||||||
let message_type = proto::message::Type::try_from(message.r#type)?;
|
|
||||||
|
|
||||||
match message_type {
|
|
||||||
proto::message::Type::Ciphertext | proto::message::Type::PrekeyBundle => {
|
|
||||||
let plaintext = message.encrypted_content.take().ok_or_else(|| {
|
|
||||||
TwonlyError::Generic("queued legacy message has no plaintext".into())
|
|
||||||
})?;
|
|
||||||
let (ciphertext, encrypted_type) =
|
|
||||||
encrypt_legacy_signal(row.contact_id, plaintext).await?;
|
|
||||||
message.encrypted_content = Some(ciphertext);
|
|
||||||
message.r#type = encrypted_type;
|
|
||||||
}
|
|
||||||
proto::message::Type::CiphertextV2 => {
|
|
||||||
let plaintext = message
|
|
||||||
.encrypted_content
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| TwonlyError::Generic("queued V2 message has no plaintext".into()))?;
|
|
||||||
message.encrypted_content =
|
|
||||||
Some(encrypt_v2_with_session_recovery(ctx, row.contact_id, plaintext).await?);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
match Server::send_text_message(ctx, row.contact_id, message.encode_to_vec()).await? {
|
|
||||||
ServerResult::Ok(()) => {}
|
ServerResult::Ok(()) => {}
|
||||||
ServerResult::ErrorCode(code) => {
|
ServerResult::ErrorCode(code) => {
|
||||||
return Err(TwonlyError::Generic(format!(
|
return Err(TwonlyError::Generic(format!(
|
||||||
|
|
@ -384,7 +407,7 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut t = app_db.pool.begin().await?;
|
let mut t = app_db.pool.begin().await?;
|
||||||
if let Some(message_id) = row.message_id {
|
if let Some(message_id) = receipt.message_id {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO message_actions(message_id, contact_id, type)
|
INSERT INTO message_actions(message_id, contact_id, type)
|
||||||
|
|
@ -393,12 +416,12 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
|
||||||
DO UPDATE SET action_at = CAST(strftime('%s', 'now') AS INTEGER)
|
DO UPDATE SET action_at = CAST(strftime('%s', 'now') AS INTEGER)
|
||||||
"#,
|
"#,
|
||||||
message_id,
|
message_id,
|
||||||
row.contact_id,
|
receipt.contact_id,
|
||||||
)
|
)
|
||||||
.execute(&mut *t)
|
.execute(&mut *t)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
if row.contact_will_sends_receipt == 0 {
|
if receipt.contact_will_sends_receipt == 0 {
|
||||||
Receipt::delete(&mut *t, receipt_id).await?;
|
Receipt::delete(&mut *t, receipt_id).await?;
|
||||||
} else {
|
} else {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
|
|
@ -420,52 +443,6 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn prepare_queued_receipt(
|
|
||||||
ctx: &Arc<Context>,
|
|
||||||
receipt_id: &str,
|
|
||||||
) -> Result<Option<Vec<u8>>> {
|
|
||||||
let database = ctx.app_db.read().await.clone();
|
|
||||||
let row = sqlx::query!(
|
|
||||||
r#"SELECT contact_id, message, message_id, retry_count
|
|
||||||
FROM receipts WHERE receipt_id = ?"#,
|
|
||||||
receipt_id,
|
|
||||||
)
|
|
||||||
.fetch_optional(&database.pool)
|
|
||||||
.await?;
|
|
||||||
let Some(row) = row else { return Ok(None) };
|
|
||||||
let mut message = proto::Message::decode(row.message.as_slice())
|
|
||||||
.map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?;
|
|
||||||
message.receipt_id = receipt_id.to_owned();
|
|
||||||
|
|
||||||
match proto::message::Type::try_from(message.r#type)
|
|
||||||
.map_err(|_| TwonlyError::Generic("queued message has invalid type".into()))?
|
|
||||||
{
|
|
||||||
proto::message::Type::Ciphertext => {
|
|
||||||
let plaintext = message
|
|
||||||
.encrypted_content
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| TwonlyError::Generic("queued message has no content".into()))?;
|
|
||||||
|
|
||||||
let (ciphertext, message_type) =
|
|
||||||
encrypt_legacy_signal(row.contact_id, plaintext).await?;
|
|
||||||
|
|
||||||
message.encrypted_content = Some(ciphertext);
|
|
||||||
message.r#type = message_type;
|
|
||||||
}
|
|
||||||
proto::message::Type::CiphertextV2 => {
|
|
||||||
let plaintext = message
|
|
||||||
.encrypted_content
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| TwonlyError::Generic("queued message has no content".into()))?;
|
|
||||||
|
|
||||||
message.encrypted_content =
|
|
||||||
Some(encrypt_v2_with_session_recovery(ctx, row.contact_id, plaintext).await?);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(Some(message.encode_to_vec()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn retransmit_queued_receipts(ctx: &Arc<Context>) -> Result<()> {
|
pub async fn retransmit_queued_receipts(ctx: &Arc<Context>) -> Result<()> {
|
||||||
let database = ctx.app_db.read().await.clone();
|
let database = ctx.app_db.read().await.clone();
|
||||||
let receipt_ids = sqlx::query_scalar!(
|
let receipt_ids = sqlx::query_scalar!(
|
||||||
|
|
@ -490,27 +467,6 @@ pub async fn retransmit_queued_receipts(ctx: &Arc<Context>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn decrypt_legacy_signal_with_error(
|
|
||||||
from_user_id: i64,
|
|
||||||
ciphertext: Vec<u8>,
|
|
||||||
message_type: i32,
|
|
||||||
) -> Result<core::result::Result<proto::EncryptedContent, i32>> {
|
|
||||||
let callbacks = get_callbacks()?;
|
|
||||||
let decrypted = (callbacks.legacy_signal.decrypt)(from_user_id, ciphertext, message_type).await;
|
|
||||||
|
|
||||||
let Some(plaintext) = decrypted.plaintext else {
|
|
||||||
return Ok(Err(decrypted.decryption_error_type.unwrap_or(0)));
|
|
||||||
};
|
|
||||||
|
|
||||||
proto::EncryptedContent::decode(plaintext.as_slice())
|
|
||||||
.map(Ok)
|
|
||||||
.map_err(|error| {
|
|
||||||
TwonlyError::Generic(format!(
|
|
||||||
"Flutter returned invalid legacy Signal plaintext: {error}"
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn queue_decryption_error(
|
pub(crate) async fn queue_decryption_error(
|
||||||
tr: &mut Transaction<'_, Sqlite>,
|
tr: &mut Transaction<'_, Sqlite>,
|
||||||
from_user_id: i64,
|
from_user_id: i64,
|
||||||
|
|
@ -536,28 +492,6 @@ pub(crate) async fn queue_decryption_error(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn encrypt_legacy_signal(
|
|
||||||
target_user_id: i64,
|
|
||||||
plaintext: Vec<u8>,
|
|
||||||
) -> Result<(Vec<u8>, i32)> {
|
|
||||||
let callbacks = get_callbacks()?;
|
|
||||||
let encrypted = (callbacks.legacy_signal.encrypt)(target_user_id, plaintext)
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| TwonlyError::Generic("Flutter legacy Signal encryption failed".into()))?;
|
|
||||||
if !is_legacy_signal_type(encrypted.message_type) {
|
|
||||||
return Err(TwonlyError::Generic(format!(
|
|
||||||
"Flutter returned non-legacy Signal message type {}",
|
|
||||||
encrypted.message_type
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok((encrypted.ciphertext, encrypted.message_type))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_legacy_signal_type(message_type: i32) -> bool {
|
|
||||||
use proto::message::Type;
|
|
||||||
message_type == Type::Ciphertext as i32 || message_type == Type::PrekeyBundle as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn handle_sender_delivery_receipt(
|
pub(crate) async fn handle_sender_delivery_receipt(
|
||||||
transaction: &mut Transaction<'_, Sqlite>,
|
transaction: &mut Transaction<'_, Sqlite>,
|
||||||
from_user_id: i64,
|
from_user_id: i64,
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,38 @@ message Handshake {
|
||||||
message Register {
|
message Register {
|
||||||
string username = 1;
|
string username = 1;
|
||||||
optional string invite_code = 2;
|
optional string invite_code = 2;
|
||||||
bytes public_identity_key = 3;
|
// Deprecated legacy Signal fields. The server ignores these fields; they
|
||||||
bytes signed_prekey = 4;
|
// remain allocated only to avoid reusing their wire tags.
|
||||||
bytes signed_prekey_signature = 5;
|
optional bytes public_identity_key = 3;
|
||||||
int64 signed_prekey_id = 6;
|
optional bytes signed_prekey = 4;
|
||||||
int64 registration_id = 7;
|
optional bytes signed_prekey_signature = 5;
|
||||||
|
optional int64 signed_prekey_id = 6;
|
||||||
|
optional int64 registration_id = 7;
|
||||||
bool is_ios = 8;
|
bool is_ios = 8;
|
||||||
string lang_code = 9;
|
string lang_code = 9;
|
||||||
int64 proof_of_work = 10;
|
int64 proof_of_work = 10;
|
||||||
optional bytes login_token = 11;
|
optional bytes login_token = 11;
|
||||||
|
optional InitialPqcKeys initial_pqc_keys = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitialPqcKeys {
|
||||||
|
message PqcPreKey {
|
||||||
|
int64 ecc_pre_key_id = 1;
|
||||||
|
bytes ecc_pre_key = 2;
|
||||||
|
int64 kyber_pre_key_id = 3;
|
||||||
|
bytes kyber_pre_key = 4;
|
||||||
|
bytes kyber_pre_key_signature = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes public_identity_key = 1;
|
||||||
|
int64 registration_id = 2;
|
||||||
|
int64 ecc_signed_prekey_id = 3;
|
||||||
|
bytes ecc_signed_prekey = 4;
|
||||||
|
bytes ecc_signed_prekey_signature = 5;
|
||||||
|
int64 kyber_signed_prekey_id = 6;
|
||||||
|
bytes kyber_signed_prekey = 7;
|
||||||
|
bytes kyber_signed_prekey_signature = 8;
|
||||||
|
repeated PqcPreKey prekeys = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetAuthChallenge {}
|
message GetAuthChallenge {}
|
||||||
|
|
@ -159,6 +182,10 @@ message ApplicationData {
|
||||||
bytes kyber_signed_prekey = 5;
|
bytes kyber_signed_prekey = 5;
|
||||||
bytes kyber_signed_prekey_signature = 6;
|
bytes kyber_signed_prekey_signature = 6;
|
||||||
repeated PqcPreKey prekeys = 7;
|
repeated PqcPreKey prekeys = 7;
|
||||||
|
// Required when uploading keys for the first time after a keyless
|
||||||
|
// registration. Optional for later key refreshes.
|
||||||
|
optional bytes public_identity_key = 8;
|
||||||
|
optional int64 registration_id = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownloadDone {
|
message DownloadDone {
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ pub(crate) struct ApiAuthHandshaker {
|
||||||
pub context: Arc<Context>,
|
pub context: Arc<Context>,
|
||||||
pub api_client: Weak<ApiClient>,
|
pub api_client: Weak<ApiClient>,
|
||||||
pub is_authenticated: Arc<AtomicBool>,
|
pub is_authenticated: Arc<AtomicBool>,
|
||||||
pub can_use_login_token_for_auth: bool,
|
|
||||||
pub legacy_user_app_version: i64,
|
|
||||||
pub in_background: bool,
|
pub in_background: bool,
|
||||||
pub events: broadcast::Sender<ApiEvent>,
|
pub events: broadcast::Sender<ApiEvent>,
|
||||||
}
|
}
|
||||||
|
|
@ -308,21 +306,37 @@ impl Handshaker for ApiAuthHandshaker {
|
||||||
receiver: &mut dyn HandshakeReceiver,
|
receiver: &mut dyn HandshakeReceiver,
|
||||||
_context: &ConnectionContext,
|
_context: &ConnectionContext,
|
||||||
) -> std::result::Result<(), HandshakeError> {
|
) -> std::result::Result<(), HandshakeError> {
|
||||||
let user_id = match self.context.user_id().await {
|
let user = match UserConfig::load_from(&self.context)
|
||||||
Ok(id) => id,
|
.map_err(|e| HandshakeError::Protocol(e.to_string()))?
|
||||||
Err(e) => {
|
{
|
||||||
tracing::info!("ApiAuthHandshaker skipped: user_id missing ({})", e);
|
Some(u) => u,
|
||||||
|
None => {
|
||||||
|
tracing::info!("ApiAuthHandshaker skipped: user config is not present");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let user_id = self
|
||||||
|
.context
|
||||||
|
.key_manager
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.user_id
|
||||||
|
.filter(|user_id| *user_id > 0);
|
||||||
|
let Some(user_id) = user_id else {
|
||||||
|
tracing::info!("ApiAuthHandshaker skipped: KeyManager has no registered user ID");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let can_use_login_token = user.can_use_login_token_for_auth;
|
||||||
|
let app_version = user.app_version;
|
||||||
|
|
||||||
let _ = self.events.send(ApiEvent {
|
let _ = self.events.send(ApiEvent {
|
||||||
kind: ApiEventKind::ConnectionStateChanged,
|
kind: ApiEventKind::ConnectionStateChanged,
|
||||||
state: Some(ApiConnectionState::Authenticating),
|
state: Some(ApiConnectionState::Authenticating),
|
||||||
message: None,
|
message: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.can_use_login_token_for_auth {
|
if can_use_login_token {
|
||||||
match self
|
match self
|
||||||
.authenticate_with_login_token(sender, receiver, user_id)
|
.authenticate_with_login_token(sender, receiver, user_id)
|
||||||
.await
|
.await
|
||||||
|
|
@ -337,7 +351,7 @@ impl Handshaker for ApiAuthHandshaker {
|
||||||
return Err(HandshakeError::Protocol(error.to_string()));
|
return Err(HandshakeError::Protocol(error.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.legacy_user_app_version >= 62
|
} else if app_version >= 62
|
||||||
&& self
|
&& self
|
||||||
.authenticate_with_legacy_token(sender, receiver, user_id)
|
.authenticate_with_legacy_token(sender, receiver, user_id)
|
||||||
.await
|
.await
|
||||||
|
|
@ -350,10 +364,10 @@ impl Handshaker for ApiAuthHandshaker {
|
||||||
|
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"ApiAuthHandshaker: no auth method succeeded. legacy_app_version={}",
|
"ApiAuthHandshaker: no auth method succeeded. legacy_app_version={}",
|
||||||
self.legacy_user_app_version
|
app_version
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.legacy_user_app_version < 62 {
|
if app_version < 62 {
|
||||||
return Err(HandshakeError::Protocol(
|
return Err(HandshakeError::Protocol(
|
||||||
"legacy user version is too old for API authentication".into(),
|
"legacy user version is too old for API authentication".into(),
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,6 @@ impl ApiClient {
|
||||||
context,
|
context,
|
||||||
api_client: Arc::downgrade(self),
|
api_client: Arc::downgrade(self),
|
||||||
is_authenticated: self.is_authenticated.clone(),
|
is_authenticated: self.is_authenticated.clone(),
|
||||||
can_use_login_token_for_auth: self.config.can_use_login_token_for_auth,
|
|
||||||
legacy_user_app_version: self.config.legacy_user_app_version,
|
|
||||||
in_background: self.in_background.load(Ordering::Acquire),
|
in_background: self.in_background.load(Ordering::Acquire),
|
||||||
events: self.events.clone(),
|
events: self.events.clone(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use std::pin::Pin;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use stream_tungstenite::error::SendError;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
fn call_handle_server_message(
|
fn call_handle_server_message(
|
||||||
|
|
@ -80,15 +81,18 @@ impl ApiClient {
|
||||||
pub(crate) async fn send(self: &Arc<Self>, bytes: Vec<u8>) -> Result<()> {
|
pub(crate) async fn send(self: &Arc<Self>, bytes: Vec<u8>) -> Result<()> {
|
||||||
let ws_client = self.ws_client.lock().await.clone();
|
let ws_client = self.ws_client.lock().await.clone();
|
||||||
if let Some(client) = ws_client {
|
if let Some(client) = ws_client {
|
||||||
client
|
let msg =
|
||||||
.send_async(
|
stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(bytes.into());
|
||||||
stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(
|
let start = tokio::time::Instant::now();
|
||||||
bytes.into(),
|
loop {
|
||||||
),
|
match client.send_async(msg.clone()).await {
|
||||||
)
|
Ok(_) => return Ok(()),
|
||||||
.await
|
Err(SendError::NotConnected) if start.elapsed() < Duration::from_secs(10) => {
|
||||||
.map_err(|e| TwonlyError::Generic(format!("send error: {:?}", e)))?;
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
Ok(())
|
}
|
||||||
|
Err(e) => return Err(TwonlyError::Generic(format!("send error: {:?}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(TwonlyError::Generic("Not connected".into()))
|
Err(TwonlyError::Generic("Not connected".into()))
|
||||||
}
|
}
|
||||||
|
|
@ -147,14 +151,32 @@ impl ApiClient {
|
||||||
|
|
||||||
let ws_client = self.ws_client.lock().await.clone();
|
let ws_client = self.ws_client.lock().await.clone();
|
||||||
if let Some(client) = ws_client {
|
if let Some(client) = ws_client {
|
||||||
client
|
let msg = stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(
|
||||||
.send_async(
|
|
||||||
stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(
|
|
||||||
request.encode_to_vec().into(),
|
request.encode_to_vec().into(),
|
||||||
),
|
);
|
||||||
)
|
let start = tokio::time::Instant::now();
|
||||||
.await
|
let mut sent = false;
|
||||||
.map_err(|e| TwonlyError::Generic(format!("send error: {:?}", e)))?;
|
while start.elapsed() < Duration::from_secs(10) {
|
||||||
|
match client.send_async(msg.clone()).await {
|
||||||
|
Ok(_) => {
|
||||||
|
sent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(SendError::NotConnected) => {
|
||||||
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.pending.lock().await.remove(&sequence);
|
||||||
|
return Err(TwonlyError::Generic(format!("send error: {:?}", e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
self.pending.lock().await.remove(&sequence);
|
||||||
|
return Err(TwonlyError::Generic(
|
||||||
|
"send error: NotConnected (timeout)".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.pending.lock().await.remove(&sequence);
|
self.pending.lock().await.remove(&sequence);
|
||||||
return Err(TwonlyError::Generic("Not connected".into()));
|
return Err(TwonlyError::Generic("Not connected".into()));
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ use crate::api::runtime::helpers::decode_ok_value;
|
||||||
use crate::bridge::api::ServerResult;
|
use crate::bridge::api::ServerResult;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use libsignal_protocol::{GenericSignedPreKey, IdentityKeyPair, SignedPreKeyRecord};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
|
@ -21,44 +20,69 @@ impl Server {
|
||||||
lang_code: String,
|
lang_code: String,
|
||||||
is_ios: bool,
|
is_ios: bool,
|
||||||
) -> Result<ServerResult<i64>> {
|
) -> Result<ServerResult<i64>> {
|
||||||
let key_manager = ctx.key_manager.lock().await;
|
let mut key_manager = ctx.key_manager.lock().await;
|
||||||
|
if key_manager.signal_identity.is_none() {
|
||||||
|
let identity = crate::keys::SignalIdentityKey::generate()?;
|
||||||
|
key_manager.signal_identity = Some(identity);
|
||||||
|
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
||||||
|
}
|
||||||
let identity = key_manager
|
let identity = key_manager
|
||||||
.signal_identity
|
.signal_identity
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(TwonlyError::SignalIdentityNotFound)?;
|
.ok_or(TwonlyError::SignalIdentityNotFound)?;
|
||||||
let identity_pair =
|
|
||||||
IdentityKeyPair::try_from(identity.identity_key_pair_structure.as_slice())
|
let identity_key_pair_structure = identity.identity_key_pair_structure.clone();
|
||||||
.map_err(|error| TwonlyError::Signal(error.to_string()))?;
|
let registration_id = identity.registration_id;
|
||||||
let (&signed_prekey_id, record) = identity
|
|
||||||
.pre_key_store
|
|
||||||
.iter()
|
|
||||||
.min_by_key(|(id, _)| *id)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
TwonlyError::Generic("Signal signed-prekey store is empty".into())
|
|
||||||
})?;
|
|
||||||
let signed_prekey = SignedPreKeyRecord::deserialize(record)
|
|
||||||
.map_err(|error| TwonlyError::Signal(error.to_string()))?;
|
|
||||||
let login_token = key_manager.main_key.get_login_token().to_vec();
|
let login_token = key_manager.main_key.get_login_token().to_vec();
|
||||||
|
drop(key_manager);
|
||||||
|
|
||||||
|
let database = ctx.rust_db.read().await.clone();
|
||||||
|
let registration_engine = crate::signal::engine::RustSignalEngine::new_with_pool(
|
||||||
|
database.pool.clone(),
|
||||||
|
identity_key_pair_structure.clone(),
|
||||||
|
registration_id as u32,
|
||||||
|
"pending-registration".to_string(),
|
||||||
|
)?;
|
||||||
|
let bundle = registration_engine.generate_bundle().await?;
|
||||||
|
let prekeys = registration_engine
|
||||||
|
.generate_pqc_prekeys()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|key| client_to_server::handshake::initial_pqc_keys::PqcPreKey {
|
||||||
|
ecc_pre_key_id: i64::from(key.ecc_pre_key_id),
|
||||||
|
ecc_pre_key: key.ecc_pre_key,
|
||||||
|
kyber_pre_key_id: i64::from(key.kyber_pre_key_id),
|
||||||
|
kyber_pre_key: key.kyber_pre_key,
|
||||||
|
kyber_pre_key_signature: key.kyber_pre_key_signature,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
let initial_pqc_keys = client_to_server::handshake::InitialPqcKeys {
|
||||||
|
public_identity_key: bundle.identity_key,
|
||||||
|
registration_id: i64::from(bundle.registration_id),
|
||||||
|
ecc_signed_prekey_id: i64::from(bundle.signed_pre_key_id),
|
||||||
|
ecc_signed_prekey: bundle.signed_pre_key_public,
|
||||||
|
ecc_signed_prekey_signature: bundle.signed_pre_key_signature,
|
||||||
|
kyber_signed_prekey_id: i64::from(bundle.kyber_pre_key_id),
|
||||||
|
kyber_signed_prekey: bundle.kyber_pre_key_public,
|
||||||
|
kyber_signed_prekey_signature: bundle.kyber_pre_key_signature,
|
||||||
|
prekeys,
|
||||||
|
};
|
||||||
let register = client_to_server::handshake::Register {
|
let register = client_to_server::handshake::Register {
|
||||||
username,
|
username,
|
||||||
invite_code: None,
|
invite_code: None,
|
||||||
public_identity_key: identity_pair.identity_key().serialize().to_vec(),
|
public_identity_key: None,
|
||||||
signed_prekey: signed_prekey
|
signed_prekey: None,
|
||||||
.public_key()
|
signed_prekey_signature: None,
|
||||||
.map_err(|error| TwonlyError::Signal(error.to_string()))?
|
signed_prekey_id: None,
|
||||||
.serialize()
|
registration_id: None,
|
||||||
.to_vec(),
|
|
||||||
signed_prekey_signature: signed_prekey
|
|
||||||
.signature()
|
|
||||||
.map_err(|error| TwonlyError::Signal(error.to_string()))?,
|
|
||||||
signed_prekey_id,
|
|
||||||
registration_id: identity.registration_id,
|
|
||||||
is_ios,
|
is_ios,
|
||||||
lang_code,
|
lang_code,
|
||||||
proof_of_work,
|
proof_of_work,
|
||||||
login_token: Some(login_token),
|
login_token: Some(login_token),
|
||||||
|
initial_pqc_keys: Some(initial_pqc_keys),
|
||||||
};
|
};
|
||||||
drop(key_manager);
|
|
||||||
|
|
||||||
let bytes = Self::handshake(
|
let bytes = Self::handshake(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -66,10 +90,43 @@ impl Server {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
decode_ok_value(bytes, |value| match value {
|
let res = decode_ok_value(bytes, |value| match value {
|
||||||
ResponseOk::Userid(id) => Some(id),
|
ResponseOk::Userid(id) => Some(id),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})?;
|
||||||
|
|
||||||
|
if let ServerResult::Ok(user_id) = res {
|
||||||
|
let mut key_manager = ctx.key_manager.lock().await;
|
||||||
|
key_manager.user_id = Some(user_id);
|
||||||
|
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
||||||
|
let signal_identity = key_manager.signal_identity.as_ref().map(|identity| {
|
||||||
|
(
|
||||||
|
identity.identity_key_pair_structure.clone(),
|
||||||
|
identity.registration_id,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
drop(key_manager);
|
||||||
|
|
||||||
|
if let Some((identity_key_pair_structure, registration_id)) = signal_identity {
|
||||||
|
let database = ctx.rust_db.read().await.clone();
|
||||||
|
*ctx.signal_engine.lock().await =
|
||||||
|
Some(crate::signal::engine::RustSignalEngine::new_with_pool(
|
||||||
|
database.pool.clone(),
|
||||||
|
identity_key_pair_structure,
|
||||||
|
registration_id as u32,
|
||||||
|
user_id.to_string(),
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = crate::utils::current_time().with_timezone(&chrono::Utc);
|
||||||
|
let _ = crate::user_config::UserConfig::update(ctx, |config| {
|
||||||
|
config.signal_last_signed_pre_key_updated = Some(now);
|
||||||
|
config.signal_last_pqc_pre_keys_uploaded = Some(now);
|
||||||
|
});
|
||||||
|
let _ = crate::api::ApiRuntime::reload_configuration(ctx).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_proof_of_work(ctx: &Arc<Context>) -> Result<ServerResult<ProofOfWork>> {
|
pub async fn get_proof_of_work(ctx: &Arc<Context>) -> Result<ServerResult<ProofOfWork>> {
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,33 @@ impl Server {
|
||||||
.ok_or(crate::error::TwonlyError::SignalIdentityNotFound)?
|
.ok_or(crate::error::TwonlyError::SignalIdentityNotFound)?
|
||||||
.generate_bundle()
|
.generate_bundle()
|
||||||
.await?;
|
.await?;
|
||||||
|
let prekeys = engine
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(crate::error::TwonlyError::SignalIdentityNotFound)?
|
||||||
|
.generate_pqc_prekeys()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| PqcPreKeyInput {
|
||||||
|
ecc_pre_key_id: i64::from(key.ecc_pre_key_id),
|
||||||
|
ecc_pre_key: key.ecc_pre_key,
|
||||||
|
kyber_pre_key_id: i64::from(key.kyber_pre_key_id),
|
||||||
|
kyber_pre_key: key.kyber_pre_key,
|
||||||
|
kyber_pre_key_signature: key.kyber_pre_key_signature,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
drop(engine);
|
drop(engine);
|
||||||
|
|
||||||
Self::upload_pqc_pre_keys(
|
Self::upload_pqc_pre_keys(
|
||||||
ctx,
|
ctx,
|
||||||
|
bundle.identity_key,
|
||||||
|
i64::from(bundle.registration_id),
|
||||||
i64::from(bundle.signed_pre_key_id),
|
i64::from(bundle.signed_pre_key_id),
|
||||||
bundle.signed_pre_key_public,
|
bundle.signed_pre_key_public,
|
||||||
bundle.signed_pre_key_signature,
|
bundle.signed_pre_key_signature,
|
||||||
i64::from(bundle.kyber_pre_key_id),
|
i64::from(bundle.kyber_pre_key_id),
|
||||||
bundle.kyber_pre_key_public,
|
bundle.kyber_pre_key_public,
|
||||||
bundle.kyber_pre_key_signature,
|
bundle.kyber_pre_key_signature,
|
||||||
Vec::new(),
|
prekeys,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +79,8 @@ impl Server {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn upload_pqc_pre_keys(
|
pub async fn upload_pqc_pre_keys(
|
||||||
ctx: &Arc<Context>,
|
ctx: &Arc<Context>,
|
||||||
|
public_identity_key: Vec<u8>,
|
||||||
|
registration_id: i64,
|
||||||
ecc_signed_prekey_id: i64,
|
ecc_signed_prekey_id: i64,
|
||||||
ecc_signed_prekey: Vec<u8>,
|
ecc_signed_prekey: Vec<u8>,
|
||||||
ecc_signed_prekey_signature: Vec<u8>,
|
ecc_signed_prekey_signature: Vec<u8>,
|
||||||
|
|
@ -92,6 +110,8 @@ impl Server {
|
||||||
kyber_signed_prekey,
|
kyber_signed_prekey,
|
||||||
kyber_signed_prekey_signature,
|
kyber_signed_prekey_signature,
|
||||||
prekeys,
|
prekeys,
|
||||||
|
public_identity_key: Some(public_identity_key),
|
||||||
|
registration_id: Some(registration_id),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,13 @@ impl Server {
|
||||||
ctx: &Arc<Context>,
|
ctx: &Arc<Context>,
|
||||||
value: client_to_server::handshake::Handshake,
|
value: client_to_server::handshake::Handshake,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
|
let client = ApiRuntime::client(ctx).await?;
|
||||||
|
if client
|
||||||
|
.is_authenticated
|
||||||
|
.load(std::sync::atomic::Ordering::Acquire)
|
||||||
|
{
|
||||||
|
client.close().await;
|
||||||
|
}
|
||||||
ApiRuntime::request_binary(ctx, Self::handshake_request(value)).await
|
ApiRuntime::request_binary(ctx, Self::handshake_request(value)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ impl BackupIdentity {
|
||||||
return Err(TwonlyError::Generic("No backup password".into()));
|
return Err(TwonlyError::Generic("No backup password".into()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized_bytes = postcard::to_allocvec(key_manager)?;
|
let serialized_bytes = key_manager.to_bytes()?;
|
||||||
|
|
||||||
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&keys.encryption_key);
|
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&keys.encryption_key);
|
||||||
let cipher = Aes256Gcm::new(key);
|
let cipher = Aes256Gcm::new(key);
|
||||||
|
|
@ -55,7 +55,7 @@ impl BackupIdentity {
|
||||||
|
|
||||||
let decrypted_bytes = cipher.decrypt(nonce, ciphertext)?;
|
let decrypted_bytes = cipher.decrypt(nonce, ciphertext)?;
|
||||||
|
|
||||||
let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?;
|
let key_manager = KeyManager::from_bytes(&decrypted_bytes)?;
|
||||||
|
|
||||||
key_manager.store_to_keychain(secure_storage)?;
|
key_manager.store_to_keychain(secure_storage)?;
|
||||||
|
|
||||||
|
|
@ -69,6 +69,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_backup_encryption_decryption() {
|
fn test_backup_encryption_decryption() {
|
||||||
|
SecureStorage::init().unwrap();
|
||||||
let secure_storage = SecureStorage::new("testing");
|
let secure_storage = SecureStorage::new("testing");
|
||||||
let mut key_manager = KeyManager::generate().unwrap();
|
let mut key_manager = KeyManager::generate().unwrap();
|
||||||
let password = "my_secure_password";
|
let password = "my_secure_password";
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::api::messages::incoming::messages;
|
|
||||||
use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk;
|
|
||||||
use crate::api::runtime::helpers::decode_ok_value;
|
|
||||||
use crate::api::ApiRuntime;
|
use crate::api::ApiRuntime;
|
||||||
pub use crate::api::PqcPreKeyInput;
|
pub use crate::api::PqcPreKeyInput;
|
||||||
use crate::api::Server;
|
use crate::api::Server;
|
||||||
|
use crate::api::messages::incoming::messages;
|
||||||
|
use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk;
|
||||||
|
use crate::api::runtime::helpers::decode_ok_value;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use crate::frb_generated::StreamSink;
|
use crate::frb_generated::StreamSink;
|
||||||
use crate::services::contacts::ContactService;
|
use crate::services::contacts::ContactService;
|
||||||
|
use crate::services::messages::MessageService;
|
||||||
use crate::user_config::UserConfig;
|
use crate::user_config::UserConfig;
|
||||||
use flutter_rust_bridge::frb;
|
use flutter_rust_bridge::frb;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
|
@ -432,6 +433,8 @@ impl RustApi {
|
||||||
}
|
}
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn upload_pqc_pre_keys(
|
pub async fn upload_pqc_pre_keys(
|
||||||
|
public_identity_key: Vec<u8>,
|
||||||
|
registration_id: i64,
|
||||||
ecc_signed_prekey_id: i64,
|
ecc_signed_prekey_id: i64,
|
||||||
ecc_signed_prekey: Vec<u8>,
|
ecc_signed_prekey: Vec<u8>,
|
||||||
ecc_signed_prekey_signature: Vec<u8>,
|
ecc_signed_prekey_signature: Vec<u8>,
|
||||||
|
|
@ -443,6 +446,8 @@ impl RustApi {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
Server::upload_pqc_pre_keys(
|
Server::upload_pqc_pre_keys(
|
||||||
ctx,
|
ctx,
|
||||||
|
public_identity_key,
|
||||||
|
registration_id,
|
||||||
ecc_signed_prekey_id,
|
ecc_signed_prekey_id,
|
||||||
ecc_signed_prekey,
|
ecc_signed_prekey,
|
||||||
ecc_signed_prekey_signature,
|
ecc_signed_prekey_signature,
|
||||||
|
|
@ -465,9 +470,9 @@ impl RustApi {
|
||||||
contact_id: i64,
|
contact_id: i64,
|
||||||
content: Vec<u8>,
|
content: Vec<u8>,
|
||||||
message_id: Option<String>,
|
message_id: Option<String>,
|
||||||
only_send_if_no_receipts_are_open: bool,
|
only_send_if_no_receipts_are_open: Option<bool>,
|
||||||
only_return_encrypted_data: bool,
|
only_return_encrypted_data: Option<bool>,
|
||||||
blocking: bool,
|
blocking: Option<bool>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
crate::api::messages::outgoing::send_c2c_message_to_contact()
|
crate::api::messages::outgoing::send_c2c_message_to_contact()
|
||||||
|
|
@ -475,9 +480,9 @@ impl RustApi {
|
||||||
.contact_id(contact_id)
|
.contact_id(contact_id)
|
||||||
.encrypted_content(content)
|
.encrypted_content(content)
|
||||||
.maybe_message_id(message_id)
|
.maybe_message_id(message_id)
|
||||||
.only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open)
|
.only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open.unwrap_or(false))
|
||||||
.only_return_encrypted_data(only_return_encrypted_data)
|
.only_return_encrypted_data(only_return_encrypted_data.unwrap_or(false))
|
||||||
.blocking(blocking)
|
.blocking(blocking.unwrap_or(false))
|
||||||
.call()
|
.call()
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
|
|
@ -487,7 +492,7 @@ impl RustApi {
|
||||||
group_id: String,
|
group_id: String,
|
||||||
content: Vec<u8>,
|
content: Vec<u8>,
|
||||||
message_id: Option<String>,
|
message_id: Option<String>,
|
||||||
only_send_if_no_receipts_are_open: bool,
|
only_send_if_no_receipts_are_open: Option<bool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
crate::services::messages::MessageService::new(ctx)
|
crate::services::messages::MessageService::new(ctx)
|
||||||
|
|
@ -495,7 +500,7 @@ impl RustApi {
|
||||||
group_id,
|
group_id,
|
||||||
content,
|
content,
|
||||||
message_id,
|
message_id,
|
||||||
only_send_if_no_receipts_are_open,
|
only_send_if_no_receipts_are_open.unwrap_or(false),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -571,15 +576,23 @@ impl RustApi {
|
||||||
|
|
||||||
pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec<String>) -> Result<()> {
|
pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec<String>) -> Result<()> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
crate::services::messages::MessageService::new(ctx)
|
MessageService::new(ctx)
|
||||||
.notify_opened(contact_id, message_ids)
|
.notify_opened(contact_id, message_ids)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_contact_profile(contact_id: i64) -> Result<()> {
|
pub async fn send_contact_profile(contact_id: i64) -> Result<()> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
crate::services::contacts::ContactService::new(ctx)
|
ContactService::new(ctx).send_profile(contact_id).await
|
||||||
.send_profile(contact_id)
|
}
|
||||||
|
|
||||||
|
pub async fn establish_signal_session(
|
||||||
|
contact_id: i64,
|
||||||
|
expected_public_key: Option<Vec<u8>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = Context::get_static()?;
|
||||||
|
ContactService::new(ctx)
|
||||||
|
.establish_signal_session(contact_id, expected_public_key)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,33 +20,13 @@ tokio::task_local! {
|
||||||
pub(crate) static FLUTTER_CALLBACKS: std::sync::RwLock<Option<HashMap<u32, FlutterCallbacks>>> =
|
pub(crate) static FLUTTER_CALLBACKS: std::sync::RwLock<Option<HashMap<u32, FlutterCallbacks>>> =
|
||||||
std::sync::RwLock::new(None);
|
std::sync::RwLock::new(None);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct LegacySignalDecryptResult {
|
|
||||||
/// Serialized `EncryptedContent` when legacy Signal decryption succeeded.
|
|
||||||
pub plaintext: Option<Vec<u8>>,
|
|
||||||
/// Serialized protobuf enum value for `DecryptionErrorMessage.Type`.
|
|
||||||
pub decryption_error_type: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct LegacySignalEncryptResult {
|
|
||||||
pub ciphertext: Vec<u8>,
|
|
||||||
/// `Message.Type.CIPHERTEXT` or `Message.Type.PREKEY_BUNDLE`.
|
|
||||||
pub message_type: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
|
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
|
||||||
callback_generator! {
|
callback_generator! {
|
||||||
FlutterCallbacks {
|
FlutterCallbacks {
|
||||||
Logging logging {
|
Logging logging {
|
||||||
get_stream_sink: () => StreamSink<String>
|
get_stream_sink: () => StreamSink<String>
|
||||||
},
|
},
|
||||||
LegacySignal legacy_signal {
|
|
||||||
decrypt: (i64, Vec<u8>, i32) => LegacySignalDecryptResult,
|
|
||||||
encrypt: (i64, Vec<u8>) => Option<LegacySignalEncryptResult>
|
|
||||||
},
|
|
||||||
Api api {
|
Api api {
|
||||||
resync_signal_session: (i64) => (),
|
|
||||||
media_action: (String, String, i64, String) => (),
|
media_action: (String, String, i64, String) => (),
|
||||||
verification_proof: (i64, Vec<u8>) => (),
|
verification_proof: (i64, Vec<u8>) => (),
|
||||||
create_push_avatars: (i64) => (),
|
create_push_avatars: (i64) => (),
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::bridge::get_twonly_flutter;
|
use crate::bridge::get_twonly_flutter;
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use crate::keys::SignalIdentityKey;
|
use crate::keys::SignalIdentityKey;
|
||||||
|
|
@ -35,7 +33,10 @@ impl RustKeyManager {
|
||||||
match RustSignalEngine::new(user_id.to_string()).await {
|
match RustSignalEngine::new(user_id.to_string()).await {
|
||||||
Ok(engine) => *guard = Some(engine),
|
Ok(engine) => *guard = Some(engine),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Failed to initialize Signal engine on set_user_id: {}. It will be initialized later.", e);
|
tracing::warn!(
|
||||||
|
"Failed to initialize Signal engine on set_user_id: {}. It will be initialized later.",
|
||||||
|
e
|
||||||
|
);
|
||||||
*guard = None;
|
*guard = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +47,6 @@ impl RustKeyManager {
|
||||||
pub async fn import_signal_identity(
|
pub async fn import_signal_identity(
|
||||||
identity_key_pair_structure: Vec<u8>,
|
identity_key_pair_structure: Vec<u8>,
|
||||||
registration_id: i64,
|
registration_id: i64,
|
||||||
signed_pre_key_store: HashMap<i64, Vec<u8>>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = get_twonly_flutter()?;
|
let ctx = get_twonly_flutter()?;
|
||||||
let user_id = {
|
let user_id = {
|
||||||
|
|
@ -54,7 +54,6 @@ impl RustKeyManager {
|
||||||
key_manager.signal_identity = Some(SignalIdentityKey {
|
key_manager.signal_identity = Some(SignalIdentityKey {
|
||||||
identity_key_pair_structure,
|
identity_key_pair_structure,
|
||||||
registration_id,
|
registration_id,
|
||||||
pre_key_store: signed_pre_key_store,
|
|
||||||
});
|
});
|
||||||
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
||||||
key_manager.user_id
|
key_manager.user_id
|
||||||
|
|
@ -90,55 +89,6 @@ impl RustKeyManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_signed_prekey(signed_pre_key_id: i64) -> Result<Option<Vec<u8>>> {
|
|
||||||
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<u8>) -> 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<HashMap<i64, Vec<u8>>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove_key_manager() -> Result<()> {
|
pub async fn remove_key_manager() -> Result<()> {
|
||||||
let ctx = get_twonly_flutter()?;
|
let ctx = get_twonly_flutter()?;
|
||||||
crate::keys::KeyManager::remove_from_keychain(&ctx.secure_storage)?;
|
crate::keys::KeyManager::remove_from_keychain(&ctx.secure_storage)?;
|
||||||
|
|
@ -149,13 +99,12 @@ impl RustKeyManager {
|
||||||
pub async fn serialize() -> Result<Vec<u8>> {
|
pub async fn serialize() -> Result<Vec<u8>> {
|
||||||
let ctx = get_twonly_flutter()?;
|
let ctx = get_twonly_flutter()?;
|
||||||
let key_manager = ctx.key_manager.lock().await;
|
let key_manager = ctx.key_manager.lock().await;
|
||||||
let serialized_bytes = postcard::to_allocvec(&*key_manager)?;
|
key_manager.to_bytes()
|
||||||
Ok(serialized_bytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn import_serialized(serialized_bytes: Vec<u8>) -> Result<()> {
|
pub async fn import_serialized(serialized_bytes: Vec<u8>) -> Result<()> {
|
||||||
let ctx = get_twonly_flutter()?;
|
let ctx = get_twonly_flutter()?;
|
||||||
let key_manager: crate::keys::KeyManager = postcard::from_bytes(&serialized_bytes)?;
|
let key_manager = crate::keys::KeyManager::from_bytes(&serialized_bytes)?;
|
||||||
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
||||||
*ctx.key_manager.lock().await = key_manager;
|
*ctx.key_manager.lock().await = key_manager;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -45,4 +45,16 @@ impl RustSignal {
|
||||||
.decrypt_message(name.clone(), device_id, ciphertext.clone())
|
.decrypt_message(name.clone(), device_id, ciphertext.clone())
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_public_key() -> Result<Vec<u8>> {
|
||||||
|
let guard = get_twonly_flutter()?.signal_engine.lock().await;
|
||||||
|
let engine = guard.as_ref().ok_or(TwonlyError::Initialization)?;
|
||||||
|
engine.get_identity_key().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_contact_public_key(contact_id: i64) -> Result<Option<Vec<u8>>> {
|
||||||
|
let guard = get_twonly_flutter()?.signal_engine.lock().await;
|
||||||
|
let engine = guard.as_ref().ok_or(TwonlyError::Initialization)?;
|
||||||
|
engine.get_contact_identity_key(&contact_id.to_string()).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use crate::api::runtime::{ApiClient, ApiRuntime};
|
use crate::api::runtime::{ApiClient, ApiRuntime};
|
||||||
use crate::bridge::InitConfig;
|
use crate::bridge::InitConfig;
|
||||||
use crate::database::app::{AppDatabase, APP_DATABASE_FILE};
|
use crate::database::app::{APP_DATABASE_FILE, AppDatabase};
|
||||||
use crate::database::signal::Database;
|
use crate::database::signal::Database;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::error::TwonlyError;
|
use crate::error::TwonlyError;
|
||||||
|
|
@ -111,13 +111,11 @@ impl Context {
|
||||||
&self,
|
&self,
|
||||||
identity_key_pair_structure: Vec<u8>,
|
identity_key_pair_structure: Vec<u8>,
|
||||||
registration_id: i64,
|
registration_id: i64,
|
||||||
pre_key_store: std::collections::HashMap<i64, Vec<u8>>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut key_manager = self.key_manager.lock().await;
|
let mut key_manager = self.key_manager.lock().await;
|
||||||
key_manager.signal_identity = Some(crate::keys::SignalIdentityKey {
|
key_manager.signal_identity = Some(crate::keys::SignalIdentityKey {
|
||||||
identity_key_pair_structure,
|
identity_key_pair_structure,
|
||||||
registration_id,
|
registration_id,
|
||||||
pre_key_store,
|
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||||
);
|
);
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -937778436;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1286774374;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
|
|
@ -327,23 +327,65 @@ fn wire__crate__bridge__callbacks__init_flutter_callbacks_impl(
|
||||||
rust_vec_len_: i32,
|
rust_vec_len_: i32,
|
||||||
data_len_: i32,
|
data_len_: i32,
|
||||||
) {
|
) {
|
||||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "init_flutter_callbacks", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
|
||||||
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
debug_name: "init_flutter_callbacks",
|
||||||
|
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_callback_id = <u32>::sse_decode(&mut deserializer);
|
let api_callback_id = <u32>::sse_decode(&mut deserializer);
|
||||||
let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_logging_get_stream_sink =
|
||||||
let api_legacy_signal_decrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||||
let api_legacy_signal_encrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
let api_api_resync_signal_session = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
);
|
||||||
let api_api_media_action = decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_api_media_action =
|
||||||
let api_api_verification_proof = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException(
|
||||||
let api_api_create_push_avatars = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
let api_api_media_received = decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
);
|
||||||
let api_api_user_config_changed = decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| {
|
let api_api_verification_proof =
|
||||||
|
decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
|
);
|
||||||
|
let api_api_create_push_avatars = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
|
);
|
||||||
|
let api_api_media_received =
|
||||||
|
decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(
|
||||||
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
|
);
|
||||||
|
let api_api_user_config_changed =
|
||||||
|
decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
||||||
|
<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer),
|
||||||
|
);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
transform_result_sse::<_, ()>((move || {
|
transform_result_sse::<_, ()>((move || {
|
||||||
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_legacy_signal_decrypt, api_legacy_signal_encrypt, api_api_resync_signal_session, api_api_media_action, api_api_verification_proof, api_api_create_push_avatars, api_api_media_received, api_api_user_config_changed); })?; Ok(output_ok)
|
let output_ok = Result::<_, ()>::Ok({
|
||||||
|
crate::bridge::callbacks::init_flutter_callbacks(
|
||||||
|
api_callback_id,
|
||||||
|
api_logging_get_stream_sink,
|
||||||
|
api_api_media_action,
|
||||||
|
api_api_verification_proof,
|
||||||
|
api_api_create_push_avatars,
|
||||||
|
api_api_media_received,
|
||||||
|
api_api_user_config_changed,
|
||||||
|
);
|
||||||
|
})?;
|
||||||
|
Ok(output_ok)
|
||||||
})())
|
})())
|
||||||
} })
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fn wire__crate__bridge__initialize_twonly_flutter_impl(
|
fn wire__crate__bridge__initialize_twonly_flutter_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
|
@ -1124,6 +1166,47 @@ fn wire__crate__bridge__api__rust_api_download_pending_media_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__api__rust_api_establish_signal_session_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::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "rust_api_establish_signal_session",
|
||||||
|
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_contact_id = <i64>::sse_decode(&mut deserializer);
|
||||||
|
let api_expected_public_key = <Option<Vec<u8>>>::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::api::RustApi::establish_signal_session(
|
||||||
|
api_contact_id,
|
||||||
|
api_expected_public_key,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__bridge__api__rust_api_events_impl(
|
fn wire__crate__bridge__api__rust_api_events_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -2404,9 +2487,10 @@ fn wire__crate__bridge__api__rust_api_send_encrypted_content_impl(
|
||||||
let api_contact_id = <i64>::sse_decode(&mut deserializer);
|
let api_contact_id = <i64>::sse_decode(&mut deserializer);
|
||||||
let api_content = <Vec<u8>>::sse_decode(&mut deserializer);
|
let api_content = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
let api_message_id = <Option<String>>::sse_decode(&mut deserializer);
|
let api_message_id = <Option<String>>::sse_decode(&mut deserializer);
|
||||||
let api_only_send_if_no_receipts_are_open = <bool>::sse_decode(&mut deserializer);
|
let api_only_send_if_no_receipts_are_open =
|
||||||
let api_only_return_encrypted_data = <bool>::sse_decode(&mut deserializer);
|
<Option<bool>>::sse_decode(&mut deserializer);
|
||||||
let api_blocking = <bool>::sse_decode(&mut deserializer);
|
let api_only_return_encrypted_data = <Option<bool>>::sse_decode(&mut deserializer);
|
||||||
|
let api_blocking = <Option<bool>>::sse_decode(&mut deserializer);
|
||||||
deserializer.end();
|
deserializer.end();
|
||||||
move |context| async move {
|
move |context| async move {
|
||||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
|
@ -2453,7 +2537,8 @@ fn wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(
|
||||||
let api_group_id = <String>::sse_decode(&mut deserializer);
|
let api_group_id = <String>::sse_decode(&mut deserializer);
|
||||||
let api_content = <Vec<u8>>::sse_decode(&mut deserializer);
|
let api_content = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
let api_message_id = <Option<String>>::sse_decode(&mut deserializer);
|
let api_message_id = <Option<String>>::sse_decode(&mut deserializer);
|
||||||
let api_only_send_if_no_receipts_are_open = <bool>::sse_decode(&mut deserializer);
|
let api_only_send_if_no_receipts_are_open =
|
||||||
|
<Option<bool>>::sse_decode(&mut deserializer);
|
||||||
deserializer.end();
|
deserializer.end();
|
||||||
move |context| async move {
|
move |context| async move {
|
||||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
|
@ -2845,6 +2930,8 @@ fn wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(
|
||||||
};
|
};
|
||||||
let mut deserializer =
|
let mut deserializer =
|
||||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_public_identity_key = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
|
let api_registration_id = <i64>::sse_decode(&mut deserializer);
|
||||||
let api_ecc_signed_prekey_id = <i64>::sse_decode(&mut deserializer);
|
let api_ecc_signed_prekey_id = <i64>::sse_decode(&mut deserializer);
|
||||||
let api_ecc_signed_prekey = <Vec<u8>>::sse_decode(&mut deserializer);
|
let api_ecc_signed_prekey = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
let api_ecc_signed_prekey_signature = <Vec<u8>>::sse_decode(&mut deserializer);
|
let api_ecc_signed_prekey_signature = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
|
|
@ -2858,6 +2945,8 @@ fn wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(
|
||||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
(move || async move {
|
(move || async move {
|
||||||
let output_ok = crate::bridge::api::RustApi::upload_pqc_pre_keys(
|
let output_ok = crate::bridge::api::RustApi::upload_pqc_pre_keys(
|
||||||
|
api_public_identity_key,
|
||||||
|
api_registration_id,
|
||||||
api_ecc_signed_prekey_id,
|
api_ecc_signed_prekey_id,
|
||||||
api_ecc_signed_prekey,
|
api_ecc_signed_prekey,
|
||||||
api_ecc_signed_prekey_signature,
|
api_ecc_signed_prekey_signature,
|
||||||
|
|
@ -3324,40 +3413,9 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_ide
|
||||||
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
|
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 mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
let api_identity_key_pair_structure = <Vec<u8>>::sse_decode(&mut deserializer);
|
let api_identity_key_pair_structure = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
let api_registration_id = <i64>::sse_decode(&mut deserializer);
|
let api_registration_id = <i64>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
|
||||||
let api_signed_pre_key_store = <std::collections::HashMap<i64, Vec<u8>>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
|
|
||||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || 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)
|
let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::import_signal_identity(api_identity_key_pair_structure, api_registration_id).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::SseCodec,_,_,_>(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 = <i64>::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::SseCodec,_,_,_>(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)
|
})().await)
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
|
@ -3376,21 +3434,6 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manage
|
||||||
})().await)
|
})().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::SseCodec,_,_,_>(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 = <i64>::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_serialize_impl(
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -3468,22 +3511,6 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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::SseCodec,_,_,_>(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 = <i64>::sse_decode(&mut deserializer);
|
|
||||||
let api_record = <Vec<u8>>::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)
|
|
||||||
} })
|
|
||||||
}
|
|
||||||
fn wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(
|
fn wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -3643,6 +3670,83 @@ fn wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__signal__rust_signal_get_contact_public_key_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::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "rust_signal_get_contact_public_key",
|
||||||
|
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_contact_id = <i64>::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::signal::RustSignal::get_contact_public_key(
|
||||||
|
api_contact_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__signal__rust_signal_get_user_public_key_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::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "rust_signal_get_user_public_key",
|
||||||
|
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::signal::RustSignal::get_user_public_key()
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(
|
fn wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -4229,53 +4333,6 @@ fn decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
) -> impl Fn(
|
|
||||||
i64,
|
|
||||||
Vec<u8>,
|
|
||||||
) -> flutter_rust_bridge::DartFnFuture<
|
|
||||||
Option<crate::bridge::callbacks::LegacySignalEncryptResult>,
|
|
||||||
> {
|
|
||||||
use flutter_rust_bridge::IntoDart;
|
|
||||||
|
|
||||||
async fn body(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
arg0: i64,
|
|
||||||
arg1: Vec<u8>,
|
|
||||||
) -> Option<crate::bridge::callbacks::LegacySignalEncryptResult> {
|
|
||||||
let args = vec![
|
|
||||||
arg0.into_into_dart().into_dart(),
|
|
||||||
arg1.into_into_dart().into_dart(),
|
|
||||||
];
|
|
||||||
let message = FLUTTER_RUST_BRIDGE_HANDLER
|
|
||||||
.dart_fn_invoke(dart_opaque, args)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
|
||||||
let action = deserializer.cursor.read_u8().unwrap();
|
|
||||||
let ans = match action {
|
|
||||||
0 => std::result::Result::Ok(<Option<
|
|
||||||
crate::bridge::callbacks::LegacySignalEncryptResult,
|
|
||||||
>>::sse_decode(&mut deserializer)),
|
|
||||||
1 => std::result::Result::Err(
|
|
||||||
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
deserializer.end();
|
|
||||||
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
|
|
||||||
ans
|
|
||||||
}
|
|
||||||
|
|
||||||
move |arg0: i64, arg1: Vec<u8>| {
|
|
||||||
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(
|
|
||||||
dart_opaque.clone(),
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
dart_opaque: flutter_rust_bridge::DartOpaque,
|
||||||
) -> impl Fn(i64, Vec<u8>) -> flutter_rust_bridge::DartFnFuture<()> {
|
) -> impl Fn(i64, Vec<u8>) -> flutter_rust_bridge::DartFnFuture<()> {
|
||||||
|
|
@ -4312,57 +4369,6 @@ fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
) -> impl Fn(
|
|
||||||
i64,
|
|
||||||
Vec<u8>,
|
|
||||||
i32,
|
|
||||||
) -> flutter_rust_bridge::DartFnFuture<crate::bridge::callbacks::LegacySignalDecryptResult> {
|
|
||||||
use flutter_rust_bridge::IntoDart;
|
|
||||||
|
|
||||||
async fn body(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
arg0: i64,
|
|
||||||
arg1: Vec<u8>,
|
|
||||||
arg2: i32,
|
|
||||||
) -> crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
let args = vec![
|
|
||||||
arg0.into_into_dart().into_dart(),
|
|
||||||
arg1.into_into_dart().into_dart(),
|
|
||||||
arg2.into_into_dart().into_dart(),
|
|
||||||
];
|
|
||||||
let message = FLUTTER_RUST_BRIDGE_HANDLER
|
|
||||||
.dart_fn_invoke(dart_opaque, args)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
|
||||||
let action = deserializer.cursor.read_u8().unwrap();
|
|
||||||
let ans = match action {
|
|
||||||
0 => std::result::Result::Ok(
|
|
||||||
<crate::bridge::callbacks::LegacySignalDecryptResult>::sse_decode(
|
|
||||||
&mut deserializer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
1 => std::result::Result::Err(
|
|
||||||
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
deserializer.end();
|
|
||||||
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
|
|
||||||
ans
|
|
||||||
}
|
|
||||||
|
|
||||||
move |arg0: i64, arg1: Vec<u8>, arg2: i32| {
|
|
||||||
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(
|
|
||||||
dart_opaque.clone(),
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
fn decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
dart_opaque: flutter_rust_bridge::DartOpaque,
|
||||||
) -> impl Fn(crate::user_config::UserConfig) -> flutter_rust_bridge::DartFnFuture<()> {
|
) -> impl Fn(crate::user_config::UserConfig) -> flutter_rust_bridge::DartFnFuture<()> {
|
||||||
|
|
@ -4438,14 +4444,6 @@ impl SseDecode for std::collections::HashMap<String, Vec<String>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for std::collections::HashMap<i64, Vec<u8>> {
|
|
||||||
// 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 = <Vec<(i64, Vec<u8>)>>::sse_decode(deserializer);
|
|
||||||
return inner.into_iter().collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for StreamSink<String, flutter_rust_bridge::for_generated::SseCodec> {
|
impl SseDecode for StreamSink<String, flutter_rust_bridge::for_generated::SseCodec> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4665,30 +4663,6 @@ impl SseDecode for crate::bridge::wrapper::app_database::LegacyMigrationReport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
// 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_plaintext = <Option<Vec<u8>>>::sse_decode(deserializer);
|
|
||||||
let mut var_decryptionErrorType = <Option<i32>>::sse_decode(deserializer);
|
|
||||||
return crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
plaintext: var_plaintext,
|
|
||||||
decryption_error_type: var_decryptionErrorType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for crate::bridge::callbacks::LegacySignalEncryptResult {
|
|
||||||
// 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_ciphertext = <Vec<u8>>::sse_decode(deserializer);
|
|
||||||
let mut var_messageType = <i32>::sse_decode(deserializer);
|
|
||||||
return crate::bridge::callbacks::LegacySignalEncryptResult {
|
|
||||||
ciphertext: var_ciphertext,
|
|
||||||
message_type: var_messageType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
impl SseDecode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4793,18 +4767,6 @@ impl SseDecode for Vec<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for Vec<(i64, Vec<u8>)> {
|
|
||||||
// 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_ = <i32>::sse_decode(deserializer);
|
|
||||||
let mut ans_ = Vec::with_capacity(len_ as usize);
|
|
||||||
for idx_ in 0..len_ {
|
|
||||||
ans_.push(<(i64, Vec<u8>)>::sse_decode(deserializer));
|
|
||||||
}
|
|
||||||
return ans_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for Vec<(String, Vec<String>)> {
|
impl SseDecode for Vec<(String, Vec<String>)> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4889,6 +4851,17 @@ impl SseDecode for Option<crate::bridge::api::ApiConnectionState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for Option<bool> {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
if (<bool>::sse_decode(deserializer)) {
|
||||||
|
return Some(<bool>::sse_decode(deserializer));
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for Option<f64> {
|
impl SseDecode for Option<f64> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4900,17 +4873,6 @@ impl SseDecode for Option<f64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for Option<i32> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
|
||||||
if (<bool>::sse_decode(deserializer)) {
|
|
||||||
return Some(<i32>::sse_decode(deserializer));
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for Option<i64> {
|
impl SseDecode for Option<i64> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4922,19 +4884,6 @@ impl SseDecode for Option<i64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for Option<crate::bridge::callbacks::LegacySignalEncryptResult> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
|
||||||
if (<bool>::sse_decode(deserializer)) {
|
|
||||||
return Some(
|
|
||||||
<crate::bridge::callbacks::LegacySignalEncryptResult>::sse_decode(deserializer),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for Option<crate::user_config::PasswordlessRecoveryConfig> {
|
impl SseDecode for Option<crate::user_config::PasswordlessRecoveryConfig> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -5047,15 +4996,6 @@ impl SseDecode for crate::api::server::prekeys::PqcPreKeyInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for (i64, Vec<u8>) {
|
|
||||||
// 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 = <i64>::sse_decode(deserializer);
|
|
||||||
let mut var_field1 = <Vec<u8>>::sse_decode(deserializer);
|
|
||||||
return (var_field0, var_field1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for (Vec<u8>, i64) {
|
impl SseDecode for (Vec<u8>, i64) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -5445,91 +5385,90 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||||
29 => wire__crate__bridge__api__rust_api_download_done_impl(port, ptr, rust_vec_len, data_len),
|
29 => wire__crate__bridge__api__rust_api_download_done_impl(port, ptr, rust_vec_len, data_len),
|
||||||
30 => wire__crate__bridge__api__rust_api_download_media_impl(port, ptr, rust_vec_len, data_len),
|
30 => wire__crate__bridge__api__rust_api_download_media_impl(port, ptr, rust_vec_len, data_len),
|
||||||
31 => wire__crate__bridge__api__rust_api_download_pending_media_impl(port, ptr, rust_vec_len, data_len),
|
31 => wire__crate__bridge__api__rust_api_download_pending_media_impl(port, ptr, rust_vec_len, data_len),
|
||||||
32 => wire__crate__bridge__api__rust_api_events_impl(port, ptr, rust_vec_len, data_len),
|
32 => wire__crate__bridge__api__rust_api_establish_signal_session_impl(port, ptr, rust_vec_len, data_len),
|
||||||
33 => wire__crate__bridge__api__rust_api_force_ipa_check_impl(port, ptr, rust_vec_len, data_len),
|
33 => wire__crate__bridge__api__rust_api_events_impl(port, ptr, rust_vec_len, data_len),
|
||||||
34 => wire__crate__bridge__api__rust_api_get_memories_url_impl(port, ptr, rust_vec_len, data_len),
|
34 => wire__crate__bridge__api__rust_api_force_ipa_check_impl(port, ptr, rust_vec_len, data_len),
|
||||||
35 => wire__crate__bridge__api__rust_api_get_memories_usage_impl(port, ptr, rust_vec_len, data_len),
|
35 => wire__crate__bridge__api__rust_api_get_memories_url_impl(port, ptr, rust_vec_len, data_len),
|
||||||
36 => wire__crate__bridge__api__rust_api_get_plan_balance_impl(port, ptr, rust_vec_len, data_len),
|
36 => wire__crate__bridge__api__rust_api_get_memories_usage_impl(port, ptr, rust_vec_len, data_len),
|
||||||
37 => wire__crate__bridge__api__rust_api_get_proof_of_work_impl(port, ptr, rust_vec_len, data_len),
|
37 => wire__crate__bridge__api__rust_api_get_plan_balance_impl(port, ptr, rust_vec_len, data_len),
|
||||||
38 => wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len),
|
38 => wire__crate__bridge__api__rust_api_get_proof_of_work_impl(port, ptr, rust_vec_len, data_len),
|
||||||
39 => wire__crate__bridge__api__rust_api_get_user_by_id_impl(port, ptr, rust_vec_len, data_len),
|
39 => wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len),
|
||||||
40 => wire__crate__bridge__api__rust_api_get_user_data_impl(port, ptr, rust_vec_len, data_len),
|
40 => wire__crate__bridge__api__rust_api_get_user_by_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
41 => wire__crate__bridge__api__rust_api_get_user_id_from_username_impl(port, ptr, rust_vec_len, data_len),
|
41 => wire__crate__bridge__api__rust_api_get_user_data_impl(port, ptr, rust_vec_len, data_len),
|
||||||
42 => wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl(port, ptr, rust_vec_len, data_len),
|
42 => wire__crate__bridge__api__rust_api_get_user_id_from_username_impl(port, ptr, rust_vec_len, data_len),
|
||||||
43 => wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl(port, ptr, rust_vec_len, data_len),
|
43 => wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl(port, ptr, rust_vec_len, data_len),
|
||||||
44 => wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl(port, ptr, rust_vec_len, data_len),
|
44 => wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl(port, ptr, rust_vec_len, data_len),
|
||||||
45 => wire__crate__bridge__api__rust_api_insert_and_send_text_impl(port, ptr, rust_vec_len, data_len),
|
45 => wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl(port, ptr, rust_vec_len, data_len),
|
||||||
46 => wire__crate__bridge__api__rust_api_ipa_purchase_impl(port, ptr, rust_vec_len, data_len),
|
46 => wire__crate__bridge__api__rust_api_insert_and_send_text_impl(port, ptr, rust_vec_len, data_len),
|
||||||
47 => wire__crate__bridge__api__rust_api_load_plan_balance_impl(port, ptr, rust_vec_len, data_len),
|
47 => wire__crate__bridge__api__rust_api_ipa_purchase_impl(port, ptr, rust_vec_len, data_len),
|
||||||
48 => wire__crate__bridge__api__rust_api_notify_messages_opened_impl(port, ptr, rust_vec_len, data_len),
|
48 => wire__crate__bridge__api__rust_api_load_plan_balance_impl(port, ptr, rust_vec_len, data_len),
|
||||||
49 => wire__crate__bridge__api__rust_api_perform_passwordless_recovery_heartbeat_impl(port, ptr, rust_vec_len, data_len),
|
49 => wire__crate__bridge__api__rust_api_notify_messages_opened_impl(port, ptr, rust_vec_len, data_len),
|
||||||
50 => wire__crate__bridge__api__rust_api_prepare_queued_message_impl(port, ptr, rust_vec_len, data_len),
|
50 => wire__crate__bridge__api__rust_api_perform_passwordless_recovery_heartbeat_impl(port, ptr, rust_vec_len, data_len),
|
||||||
51 => wire__crate__bridge__api__rust_api_register_impl(port, ptr, rust_vec_len, data_len),
|
51 => wire__crate__bridge__api__rust_api_prepare_queued_message_impl(port, ptr, rust_vec_len, data_len),
|
||||||
52 => wire__crate__bridge__api__rust_api_register_passwordless_notification_impl(port, ptr, rust_vec_len, data_len),
|
52 => wire__crate__bridge__api__rust_api_register_impl(port, ptr, rust_vec_len, data_len),
|
||||||
53 => wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len),
|
53 => wire__crate__bridge__api__rust_api_register_passwordless_notification_impl(port, ptr, rust_vec_len, data_len),
|
||||||
54 => wire__crate__bridge__api__rust_api_reload_configuration_impl(port, ptr, rust_vec_len, data_len),
|
54 => wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len),
|
||||||
55 => wire__crate__bridge__api__rust_api_remove_additional_user_impl(port, ptr, rust_vec_len, data_len),
|
55 => wire__crate__bridge__api__rust_api_reload_configuration_impl(port, ptr, rust_vec_len, data_len),
|
||||||
56 => wire__crate__bridge__api__rust_api_report_user_impl(port, ptr, rust_vec_len, data_len),
|
56 => wire__crate__bridge__api__rust_api_remove_additional_user_impl(port, ptr, rust_vec_len, data_len),
|
||||||
57 => wire__crate__bridge__api__rust_api_request_binary_impl(port, ptr, rust_vec_len, data_len),
|
57 => wire__crate__bridge__api__rust_api_report_user_impl(port, ptr, rust_vec_len, data_len),
|
||||||
58 => wire__crate__bridge__api__rust_api_request_contact_by_username_impl(port, ptr, rust_vec_len, data_len),
|
58 => wire__crate__bridge__api__rust_api_request_binary_impl(port, ptr, rust_vec_len, data_len),
|
||||||
59 => wire__crate__bridge__api__rust_api_request_media_reupload_impl(port, ptr, rust_vec_len, data_len),
|
59 => wire__crate__bridge__api__rust_api_request_contact_by_username_impl(port, ptr, rust_vec_len, data_len),
|
||||||
60 => wire__crate__bridge__api__rust_api_request_memories_upload_impl(port, ptr, rust_vec_len, data_len),
|
60 => wire__crate__bridge__api__rust_api_request_media_reupload_impl(port, ptr, rust_vec_len, data_len),
|
||||||
61 => wire__crate__bridge__api__rust_api_retransmit_all_messages_impl(port, ptr, rust_vec_len, data_len),
|
61 => wire__crate__bridge__api__rust_api_request_memories_upload_impl(port, ptr, rust_vec_len, data_len),
|
||||||
62 => wire__crate__bridge__api__rust_api_send_binary_impl(port, ptr, rust_vec_len, data_len),
|
62 => wire__crate__bridge__api__rust_api_retransmit_all_messages_impl(port, ptr, rust_vec_len, data_len),
|
||||||
63 => wire__crate__bridge__api__rust_api_send_contact_profile_impl(port, ptr, rust_vec_len, data_len),
|
63 => wire__crate__bridge__api__rust_api_send_binary_impl(port, ptr, rust_vec_len, data_len),
|
||||||
64 => wire__crate__bridge__api__rust_api_send_encrypted_content_impl(port, ptr, rust_vec_len, data_len),
|
64 => wire__crate__bridge__api__rust_api_send_contact_profile_impl(port, ptr, rust_vec_len, data_len),
|
||||||
65 => wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(port, ptr, rust_vec_len, data_len),
|
65 => wire__crate__bridge__api__rust_api_send_encrypted_content_impl(port, ptr, rust_vec_len, data_len),
|
||||||
66 => wire__crate__bridge__api__rust_api_send_queued_message_impl(port, ptr, rust_vec_len, data_len),
|
66 => wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(port, ptr, rust_vec_len, data_len),
|
||||||
67 => wire__crate__bridge__api__rust_api_send_text_message_impl(port, ptr, rust_vec_len, data_len),
|
67 => wire__crate__bridge__api__rust_api_send_queued_message_impl(port, ptr, rust_vec_len, data_len),
|
||||||
68 => wire__crate__bridge__api__rust_api_send_typing_impl(port, ptr, rust_vec_len, data_len),
|
68 => wire__crate__bridge__api__rust_api_send_text_message_impl(port, ptr, rust_vec_len, data_len),
|
||||||
69 => wire__crate__bridge__api__rust_api_set_background_impl(port, ptr, rust_vec_len, data_len),
|
69 => wire__crate__bridge__api__rust_api_send_typing_impl(port, ptr, rust_vec_len, data_len),
|
||||||
70 => wire__crate__bridge__api__rust_api_set_login_token_impl(port, ptr, rust_vec_len, data_len),
|
70 => wire__crate__bridge__api__rust_api_set_background_impl(port, ptr, rust_vec_len, data_len),
|
||||||
71 => wire__crate__bridge__api__rust_api_set_network_available_impl(port, ptr, rust_vec_len, data_len),
|
71 => wire__crate__bridge__api__rust_api_set_login_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
72 => wire__crate__bridge__api__rust_api_submit_recovery_share_impl(port, ptr, rust_vec_len, data_len),
|
72 => wire__crate__bridge__api__rust_api_set_network_available_impl(port, ptr, rust_vec_len, data_len),
|
||||||
73 => wire__crate__bridge__api__rust_api_update_fcm_token_impl(port, ptr, rust_vec_len, data_len),
|
73 => wire__crate__bridge__api__rust_api_submit_recovery_share_impl(port, ptr, rust_vec_len, data_len),
|
||||||
74 => wire__crate__bridge__api__rust_api_update_signed_pre_key_impl(port, ptr, rust_vec_len, data_len),
|
74 => wire__crate__bridge__api__rust_api_update_fcm_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
75 => wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(port, ptr, rust_vec_len, data_len),
|
75 => wire__crate__bridge__api__rust_api_update_signed_pre_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
76 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len),
|
76 => wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
77 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len),
|
77 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len),
|
||||||
78 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len),
|
78 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len),
|
||||||
79 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len),
|
79 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len),
|
||||||
80 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len),
|
80 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len),
|
||||||
81 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len),
|
81 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len),
|
||||||
82 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len),
|
82 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
83 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len),
|
83 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len),
|
||||||
84 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
84 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
85 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len),
|
85 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
86 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
86 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len),
|
||||||
87 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len),
|
87 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
88 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
88 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len),
|
||||||
89 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
89 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
90 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
90 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
91 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
91 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
92 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
92 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
93 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len),
|
93 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
94 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len),
|
94 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
95 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
95 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len),
|
||||||
96 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
96 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
97 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
97 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len),
|
||||||
98 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len),
|
98 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len),
|
||||||
99 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
99 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
100 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len),
|
100 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len),
|
||||||
101 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
|
101 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len),
|
||||||
102 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
102 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len),
|
||||||
103 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len),
|
103 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
104 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len),
|
104 => wire__crate__bridge__wrapper__signal__rust_signal_get_contact_public_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
105 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len),
|
105 => wire__crate__bridge__wrapper__signal__rust_signal_get_user_public_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
106 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
106 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len),
|
||||||
107 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len),
|
107 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||||
108 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
108 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
||||||
109 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
109 => wire__crate__bridge__groups__update_chat_deletion_time_impl(port, ptr, rust_vec_len, data_len),
|
||||||
110 => wire__crate__bridge__groups__update_chat_deletion_time_impl(port, ptr, rust_vec_len, data_len),
|
110 => wire__crate__bridge__groups__update_group_name_impl(port, ptr, rust_vec_len, data_len),
|
||||||
111 => wire__crate__bridge__groups__update_group_name_impl(port, ptr, rust_vec_len, data_len),
|
112 => wire__crate__bridge__user_config__user_config_api_create_impl(port, ptr, rust_vec_len, data_len),
|
||||||
113 => wire__crate__bridge__user_config__user_config_api_create_impl(port, ptr, rust_vec_len, data_len),
|
113 => wire__crate__bridge__user_config__user_config_api_import_json_impl(port, ptr, rust_vec_len, data_len),
|
||||||
114 => wire__crate__bridge__user_config__user_config_api_import_json_impl(port, ptr, rust_vec_len, data_len),
|
114 => wire__crate__bridge__user_config__user_config_api_load_impl(port, ptr, rust_vec_len, data_len),
|
||||||
115 => wire__crate__bridge__user_config__user_config_api_load_impl(port, ptr, rust_vec_len, data_len),
|
115 => wire__crate__bridge__user_config__user_config_api_save_impl(port, ptr, rust_vec_len, data_len),
|
||||||
116 => wire__crate__bridge__user_config__user_config_api_save_impl(port, ptr, rust_vec_len, data_len),
|
116 => wire__crate__bridge__user_config__user_config_api_update_impl(port, ptr, rust_vec_len, data_len),
|
||||||
117 => wire__crate__bridge__user_config__user_config_api_update_impl(port, ptr, rust_vec_len, data_len),
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5543,7 +5482,7 @@ fn pde_ffi_dispatcher_sync_impl(
|
||||||
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
||||||
match func_id {
|
match func_id {
|
||||||
18 => wire__crate__bridge__api__rust_api_api_base_url_impl(ptr, rust_vec_len, data_len),
|
18 => wire__crate__bridge__api__rust_api_api_base_url_impl(ptr, rust_vec_len, data_len),
|
||||||
112 => wire__crate__bridge__user_config__user_config_api_clone_impl(
|
111 => wire__crate__bridge__user_config__user_config_api_clone_impl(
|
||||||
ptr,
|
ptr,
|
||||||
rust_vec_len,
|
rust_vec_len,
|
||||||
data_len,
|
data_len,
|
||||||
|
|
@ -5781,48 +5720,6 @@ impl flutter_rust_bridge::IntoIntoDart<crate::bridge::wrapper::app_database::Leg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
impl flutter_rust_bridge::IntoDart for crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
|
||||||
[
|
|
||||||
self.plaintext.into_into_dart().into_dart(),
|
|
||||||
self.decryption_error_type.into_into_dart().into_dart(),
|
|
||||||
]
|
|
||||||
.into_dart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
|
||||||
for crate::bridge::callbacks::LegacySignalDecryptResult
|
|
||||||
{
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::IntoIntoDart<crate::bridge::callbacks::LegacySignalDecryptResult>
|
|
||||||
for crate::bridge::callbacks::LegacySignalDecryptResult
|
|
||||||
{
|
|
||||||
fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
impl flutter_rust_bridge::IntoDart for crate::bridge::callbacks::LegacySignalEncryptResult {
|
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
|
||||||
[
|
|
||||||
self.ciphertext.into_into_dart().into_dart(),
|
|
||||||
self.message_type.into_into_dart().into_dart(),
|
|
||||||
]
|
|
||||||
.into_dart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
|
||||||
for crate::bridge::callbacks::LegacySignalEncryptResult
|
|
||||||
{
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::IntoIntoDart<crate::bridge::callbacks::LegacySignalEncryptResult>
|
|
||||||
for crate::bridge::callbacks::LegacySignalEncryptResult
|
|
||||||
{
|
|
||||||
fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalEncryptResult {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
impl flutter_rust_bridge::IntoDart
|
impl flutter_rust_bridge::IntoDart
|
||||||
for crate::bridge::wrapper::app_database::LegacyTableMigrationCount
|
for crate::bridge::wrapper::app_database::LegacyTableMigrationCount
|
||||||
{
|
{
|
||||||
|
|
@ -6305,13 +6202,6 @@ impl SseEncode for std::collections::HashMap<String, Vec<String>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for std::collections::HashMap<i64, Vec<u8>> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<Vec<(i64, Vec<u8>)>>::sse_encode(self.into_iter().collect(), serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for StreamSink<String, flutter_rust_bridge::for_generated::SseCodec> {
|
impl SseEncode for StreamSink<String, flutter_rust_bridge::for_generated::SseCodec> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6503,22 +6393,6 @@ impl SseEncode for crate::bridge::wrapper::app_database::LegacyMigrationReport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for crate::bridge::callbacks::LegacySignalDecryptResult {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<Option<Vec<u8>>>::sse_encode(self.plaintext, serializer);
|
|
||||||
<Option<i32>>::sse_encode(self.decryption_error_type, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for crate::bridge::callbacks::LegacySignalEncryptResult {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<Vec<u8>>::sse_encode(self.ciphertext, serializer);
|
|
||||||
<i32>::sse_encode(self.message_type, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
impl SseEncode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6599,16 +6473,6 @@ impl SseEncode for Vec<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for Vec<(i64, Vec<u8>)> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<i32>::sse_encode(self.len() as _, serializer);
|
|
||||||
for item in self {
|
|
||||||
<(i64, Vec<u8>)>::sse_encode(item, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for Vec<(String, Vec<String>)> {
|
impl SseEncode for Vec<(String, Vec<String>)> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6679,6 +6543,16 @@ impl SseEncode for Option<crate::bridge::api::ApiConnectionState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for Option<bool> {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
<bool>::sse_encode(self.is_some(), serializer);
|
||||||
|
if let Some(value) = self {
|
||||||
|
<bool>::sse_encode(value, serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for Option<f64> {
|
impl SseEncode for Option<f64> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6689,16 +6563,6 @@ impl SseEncode for Option<f64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for Option<i32> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<bool>::sse_encode(self.is_some(), serializer);
|
|
||||||
if let Some(value) = self {
|
|
||||||
<i32>::sse_encode(value, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for Option<i64> {
|
impl SseEncode for Option<i64> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6709,16 +6573,6 @@ impl SseEncode for Option<i64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for Option<crate::bridge::callbacks::LegacySignalEncryptResult> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<bool>::sse_encode(self.is_some(), serializer);
|
|
||||||
if let Some(value) = self {
|
|
||||||
<crate::bridge::callbacks::LegacySignalEncryptResult>::sse_encode(value, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for Option<crate::user_config::PasswordlessRecoveryConfig> {
|
impl SseEncode for Option<crate::user_config::PasswordlessRecoveryConfig> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6806,14 +6660,6 @@ impl SseEncode for crate::api::server::prekeys::PqcPreKeyInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for (i64, Vec<u8>) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<i64>::sse_encode(self.0, serializer);
|
|
||||||
<Vec<u8>>::sse_encode(self.1, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for (Vec<u8>, i64) {
|
impl SseEncode for (Vec<u8>, i64) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use scrypt::{scrypt, Params};
|
use scrypt::{Params, scrypt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use crate::error::Result;
|
||||||
|
use libsignal_protocol::IdentityKeyPair;
|
||||||
|
use rand::SeedableRng;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
|
@ -12,19 +13,24 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
pub(crate) struct SignalIdentityKey {
|
pub(crate) struct SignalIdentityKey {
|
||||||
pub(crate) identity_key_pair_structure: Vec<u8>,
|
pub(crate) identity_key_pair_structure: Vec<u8>,
|
||||||
pub(crate) registration_id: i64,
|
pub(crate) registration_id: i64,
|
||||||
pub(crate) pre_key_store: HashMap<i64, Vec<u8>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignalIdentityKey {}
|
impl SignalIdentityKey {
|
||||||
|
pub(crate) fn generate() -> Result<Self> {
|
||||||
|
let mut csprng = rand::rngs::StdRng::from_os_rng();
|
||||||
|
let identity_key_pair = IdentityKeyPair::generate(&mut csprng);
|
||||||
|
let registration_id = rand::Rng::random::<u32>(&mut csprng) & 0x7fff_ffff;
|
||||||
|
Ok(Self {
|
||||||
|
identity_key_pair_structure: identity_key_pair.serialize().to_vec(),
|
||||||
|
registration_id: i64::from(registration_id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Zeroize for SignalIdentityKey {
|
impl Zeroize for SignalIdentityKey {
|
||||||
fn zeroize(&mut self) {
|
fn zeroize(&mut self) {
|
||||||
self.identity_key_pair_structure.zeroize();
|
self.identity_key_pair_structure.zeroize();
|
||||||
self.registration_id.zeroize();
|
self.registration_id.zeroize();
|
||||||
for value in self.pre_key_store.values_mut() {
|
|
||||||
value.zeroize();
|
|
||||||
}
|
|
||||||
self.pre_key_store.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ pub(crate) use crate::keys::identity_key::signal_identity_key::SignalIdentityKey
|
||||||
pub(crate) use crate::keys::main_key::{DatabaseKey, MainKey};
|
pub(crate) use crate::keys::main_key::{DatabaseKey, MainKey};
|
||||||
use crate::secure_storage::SecureStorage;
|
use crate::secure_storage::SecureStorage;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
const KEY_MANAGER_ID: &str = "twonly_key_manager";
|
const KEY_MANAGER_ID: &str = "twonly_key_manager";
|
||||||
|
const KEY_MANAGER_V2_PREFIX: &[u8] = b"TWONLY_KEY_MANAGER_V2\0";
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
pub(crate) struct KeyManager {
|
pub(crate) struct KeyManager {
|
||||||
|
|
@ -44,14 +46,12 @@ impl KeyManager {
|
||||||
|
|
||||||
let bytes = hex::decode(hex_key)?;
|
let bytes = hex::decode(hex_key)?;
|
||||||
|
|
||||||
let main_key: KeyManager = postcard::from_bytes(&bytes)?;
|
Self::from_bytes(&bytes)
|
||||||
|
|
||||||
Ok(main_key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores the main key into the secure keychain/local storage.
|
/// Stores the main key into the secure keychain/local storage.
|
||||||
pub fn store_to_keychain(&self, storage: &SecureStorage) -> Result<()> {
|
pub fn store_to_keychain(&self, storage: &SecureStorage) -> Result<()> {
|
||||||
let serialized = postcard::to_allocvec(self)?;
|
let serialized = self.to_bytes()?;
|
||||||
|
|
||||||
let hex_key = hex::encode(serialized);
|
let hex_key = hex::encode(serialized);
|
||||||
storage.write(KEY_MANAGER_ID, &hex_key)?;
|
storage.write(KEY_MANAGER_ID, &hex_key)?;
|
||||||
|
|
@ -64,4 +64,130 @@ impl KeyManager {
|
||||||
storage.delete(KEY_MANAGER_ID)?;
|
storage.delete(KEY_MANAGER_ID)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||||
|
let payload = postcard::to_allocvec(self)?;
|
||||||
|
let mut serialized = Vec::with_capacity(KEY_MANAGER_V2_PREFIX.len() + payload.len());
|
||||||
|
serialized.extend_from_slice(KEY_MANAGER_V2_PREFIX);
|
||||||
|
serialized.extend_from_slice(&payload);
|
||||||
|
Ok(serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||||
|
if let Some(payload) = bytes.strip_prefix(KEY_MANAGER_V2_PREFIX) {
|
||||||
|
return Ok(postcard::from_bytes(payload)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key managers written before V2 used postcard's positional encoding and
|
||||||
|
// embedded a signed-prekey HashMap in the Signal identity. Read that
|
||||||
|
// shape once and discard the obsolete private-key copy during migration.
|
||||||
|
let legacy: LegacyKeyManager = postcard::from_bytes(bytes)?;
|
||||||
|
Ok(legacy.into_current())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LegacyKeyManager {
|
||||||
|
user_id: Option<i64>,
|
||||||
|
main_key: MainKey,
|
||||||
|
signal_identity: Option<LegacySignalIdentityKey>,
|
||||||
|
backup_password: Option<BackupPasswordKeys>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LegacyKeyManager {
|
||||||
|
fn into_current(self) -> KeyManager {
|
||||||
|
KeyManager {
|
||||||
|
user_id: self.user_id,
|
||||||
|
main_key: self.main_key,
|
||||||
|
signal_identity: self
|
||||||
|
.signal_identity
|
||||||
|
.map(LegacySignalIdentityKey::into_current),
|
||||||
|
backup_password: self.backup_password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LegacySignalIdentityKey {
|
||||||
|
identity_key_pair_structure: Vec<u8>,
|
||||||
|
registration_id: i64,
|
||||||
|
pre_key_store: HashMap<i64, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LegacySignalIdentityKey {
|
||||||
|
fn into_current(mut self) -> SignalIdentityKey {
|
||||||
|
for value in self.pre_key_store.values_mut() {
|
||||||
|
value.zeroize();
|
||||||
|
}
|
||||||
|
self.pre_key_store.clear();
|
||||||
|
SignalIdentityKey {
|
||||||
|
identity_key_pair_structure: std::mem::take(&mut self.identity_key_pair_structure),
|
||||||
|
registration_id: self.registration_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LegacySignalIdentityKey {
|
||||||
|
fn drop(&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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct OldKeyManager<'a> {
|
||||||
|
user_id: Option<i64>,
|
||||||
|
main_key: &'a MainKey,
|
||||||
|
signal_identity: Option<OldSignalIdentity>,
|
||||||
|
backup_password: Option<&'a BackupPasswordKeys>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct OldSignalIdentity {
|
||||||
|
identity_key_pair_structure: Vec<u8>,
|
||||||
|
registration_id: i64,
|
||||||
|
pre_key_store: HashMap<i64, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reads_legacy_key_manager_and_drops_pre_key_store() {
|
||||||
|
let key_manager = KeyManager::generate().unwrap();
|
||||||
|
let legacy = OldKeyManager {
|
||||||
|
user_id: Some(42),
|
||||||
|
main_key: &key_manager.main_key,
|
||||||
|
signal_identity: Some(OldSignalIdentity {
|
||||||
|
identity_key_pair_structure: vec![1, 2, 3],
|
||||||
|
registration_id: 7,
|
||||||
|
pre_key_store: HashMap::from([(1, vec![4, 5, 6])]),
|
||||||
|
}),
|
||||||
|
backup_password: None,
|
||||||
|
};
|
||||||
|
let bytes = postcard::to_allocvec(&legacy).unwrap();
|
||||||
|
|
||||||
|
let migrated = KeyManager::from_bytes(&bytes).unwrap();
|
||||||
|
assert_eq!(migrated.user_id, Some(42));
|
||||||
|
assert_eq!(
|
||||||
|
migrated.signal_identity,
|
||||||
|
Some(SignalIdentityKey {
|
||||||
|
identity_key_pair_structure: vec![1, 2, 3],
|
||||||
|
registration_id: 7,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v2_roundtrip_is_prefixed() {
|
||||||
|
let key_manager = KeyManager::generate().unwrap();
|
||||||
|
let bytes = key_manager.to_bytes().unwrap();
|
||||||
|
assert!(bytes.starts_with(KEY_MANAGER_V2_PREFIX));
|
||||||
|
assert_eq!(KeyManager::from_bytes(&bytes).unwrap(), key_manager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
aead::{Aead, Payload},
|
|
||||||
KeyInit, XChaCha20Poly1305, XNonce,
|
KeyInit, XChaCha20Poly1305, XNonce,
|
||||||
|
aead::{Aead, Payload},
|
||||||
};
|
};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use libsignal_protocol::{IdentityKeyPair, KeyPair, PublicKey};
|
use libsignal_protocol::{IdentityKeyPair, KeyPair, PublicKey};
|
||||||
|
|
@ -319,7 +319,7 @@ fn derive_encryption_key(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rand::{rngs::StdRng, SeedableRng};
|
use rand::{SeedableRng, rngs::StdRng};
|
||||||
|
|
||||||
fn test_message() -> proto::Message {
|
fn test_message() -> proto::Message {
|
||||||
proto::Message {
|
proto::Message {
|
||||||
|
|
@ -360,7 +360,6 @@ mod tests {
|
||||||
key_manager.signal_identity = Some(crate::keys::SignalIdentityKey {
|
key_manager.signal_identity = Some(crate::keys::SignalIdentityKey {
|
||||||
identity_key_pair_structure: recipient.serialize().to_vec(),
|
identity_key_pair_structure: recipient.serialize().to_vec(),
|
||||||
registration_id: 1,
|
registration_id: 1,
|
||||||
pre_key_store: Default::default(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let database = context.rust_db.read().await.clone();
|
let database = context.rust_db.read().await.clone();
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,11 @@ impl ContactService {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn establish_signal_session(&self, user_id: i64) -> Result<()> {
|
pub(crate) async fn establish_signal_session(
|
||||||
|
&self,
|
||||||
|
user_id: i64,
|
||||||
|
expected_public_key: Option<Vec<u8>>,
|
||||||
|
) -> Result<()> {
|
||||||
let user = match Server::get_user_by_id(&self.ctx, user_id).await? {
|
let user = match Server::get_user_by_id(&self.ctx, user_id).await? {
|
||||||
ServerResult::Ok(user) => user,
|
ServerResult::Ok(user) => user,
|
||||||
ServerResult::ErrorCode(code) => {
|
ServerResult::ErrorCode(code) => {
|
||||||
|
|
@ -70,7 +74,31 @@ impl ContactService {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.process_user_prekey_bundle(&user).await
|
|
||||||
|
if let Some(expected_key) = expected_public_key {
|
||||||
|
let server_key = user
|
||||||
|
.public_identity_key
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(TwonlyError::ApiResponseMissingField("public_identity_key"))?;
|
||||||
|
if server_key != &expected_key {
|
||||||
|
return Err(TwonlyError::Generic(format!(
|
||||||
|
"Public identity key mismatch for user {user_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process_user_prekey_bundle(&user).await?;
|
||||||
|
|
||||||
|
let database = self.ctx.app_db.read().await.clone();
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE contacts SET signal_version = 'v2' WHERE user_id = ?",
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.execute(&database.pool)
|
||||||
|
.await?;
|
||||||
|
database.notify_committed(["contacts"]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_user_prekey_bundle(
|
async fn process_user_prekey_bundle(
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,14 @@ impl GroupService {
|
||||||
|
|
||||||
pub async fn leave_group(&self, group_id: String) -> Result<bool> {
|
pub async fn leave_group(&self, group_id: String) -> Result<bool> {
|
||||||
let (db, group) = self.load_group(&group_id).await?;
|
let (db, group) = self.load_group(&group_id).await?;
|
||||||
|
let user_id = self.ctx.user_id().await?;
|
||||||
|
let (_, group_state) = GroupApi::load_state(&group).await?;
|
||||||
|
if group_state.admin_ids.contains(&user_id) {
|
||||||
|
let identity = group.identity()?;
|
||||||
|
let public_key = identity.identity_key().serialize().to_vec();
|
||||||
|
return self.remove_member(group_id, public_key, user_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
let identity = group.identity()?;
|
let identity = group.identity()?;
|
||||||
let public_key = identity.identity_key().serialize().to_vec();
|
let public_key = identity.identity_key().serialize().to_vec();
|
||||||
let append = EncryptedAppendedGroupState {
|
let append = EncryptedAppendedGroupState {
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ use crate::error::{Result, TwonlyError};
|
||||||
use crate::user_config::UserConfig;
|
use crate::user_config::UserConfig;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use libsignal_protocol::{
|
use libsignal_protocol::{
|
||||||
message_encrypt, process_prekey_bundle, CiphertextMessageType, DeviceId, GenericSignedPreKey,
|
CiphertextMessageType, DeviceId, GenericSignedPreKey, IdentityKey, IdentityKeyPair,
|
||||||
IdentityKey, IdentityKeyPair, IdentityKeyStore, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle,
|
IdentityKeyStore, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle, PreKeyId, PreKeySignalMessage,
|
||||||
PreKeyId, PreKeySignalMessage, PreKeyStore, ProtocolAddress, PublicKey, SignalMessage,
|
PreKeyStore, ProtocolAddress, PublicKey, SignalMessage, SignedPreKeyId, SignedPreKeyStore,
|
||||||
SignedPreKeyId, SignedPreKeyStore, Timestamp,
|
Timestamp, message_encrypt, process_prekey_bundle,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
@ -82,6 +82,8 @@ impl RustSignalEngine {
|
||||||
if refresh_pqc {
|
if refresh_pqc {
|
||||||
Server::upload_pqc_pre_keys(
|
Server::upload_pqc_pre_keys(
|
||||||
ctx,
|
ctx,
|
||||||
|
bundle.identity_key,
|
||||||
|
i64::from(bundle.registration_id),
|
||||||
i64::from(bundle.signed_pre_key_id),
|
i64::from(bundle.signed_pre_key_id),
|
||||||
bundle.signed_pre_key_public,
|
bundle.signed_pre_key_public,
|
||||||
bundle.signed_pre_key_signature,
|
bundle.signed_pre_key_signature,
|
||||||
|
|
@ -141,6 +143,28 @@ impl RustSignalEngine {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_identity_key(&self) -> Result<Vec<u8>> {
|
||||||
|
let store = self.store.lock().await;
|
||||||
|
let key_pair = store
|
||||||
|
.identity_store
|
||||||
|
.get_identity_key_pair()
|
||||||
|
.assert_send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||||
|
Ok(key_pair.identity_key().serialize().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_contact_identity_key(&self, name: &str) -> Result<Option<Vec<u8>>> {
|
||||||
|
let store = self.store.lock().await;
|
||||||
|
let identity = sqlx::query_scalar!(
|
||||||
|
r#"SELECT identity_key FROM signal_identities WHERE name = ?"#,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
.fetch_optional(&store.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(identity)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn generate_identity_key_pair() -> Result<Vec<u8>> {
|
fn generate_identity_key_pair() -> Result<Vec<u8>> {
|
||||||
let mut csprng = rand::rngs::StdRng::from_os_rng();
|
let mut csprng = rand::rngs::StdRng::from_os_rng();
|
||||||
|
|
@ -159,33 +183,21 @@ impl RustSignalEngine {
|
||||||
)
|
)
|
||||||
.fetch_one(&store.pool)
|
.fetch_one(&store.pool)
|
||||||
.await?;
|
.await?;
|
||||||
if id > 16_777_215 {
|
if id > 16_777_215 { 1 } else { id + 1 }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
id + 1
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let signed_pre_key_id: u32 = {
|
let signed_pre_key_id: u32 = {
|
||||||
let id = sqlx::query_scalar!(
|
let id = sqlx::query_scalar!(
|
||||||
r#"SELECT COALESCE(MAX(signed_pre_key_id), 0) AS "id!: u32" FROM signal_signed_pre_keys"#,
|
r#"SELECT COALESCE(MAX(signed_pre_key_id), 0) AS "id!: u32" FROM signal_signed_pre_keys"#,
|
||||||
).fetch_one(&store.pool).await?;
|
).fetch_one(&store.pool).await?;
|
||||||
if id > 16_777_215 {
|
if id > 16_777_215 { 1 } else { id + 1 }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
id + 1
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let kyber_pre_key_id: u32 = {
|
let kyber_pre_key_id: u32 = {
|
||||||
let id = sqlx::query_scalar!(
|
let id = sqlx::query_scalar!(
|
||||||
r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#,
|
r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#,
|
||||||
).fetch_one(&store.pool).await?;
|
).fetch_one(&store.pool).await?;
|
||||||
if id > 16_777_215 {
|
if id > 16_777_215 { 1 } else { id + 1 }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
id + 1
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let pre_key_pair = libsignal_protocol::KeyPair::generate(&mut csprng);
|
let pre_key_pair = libsignal_protocol::KeyPair::generate(&mut csprng);
|
||||||
|
|
@ -313,11 +325,7 @@ impl RustSignalEngine {
|
||||||
r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#,
|
r#"SELECT COALESCE(MAX(kyber_pre_key_id), 0) AS "id!: u32" FROM signal_kyber_pre_keys"#,
|
||||||
).fetch_one(&store.pool).await?;
|
).fetch_one(&store.pool).await?;
|
||||||
|
|
||||||
if id > 16_777_215 {
|
if id > 16_777_215 { 1 } else { id }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pre_key_id: u32 = {
|
let mut pre_key_id: u32 = {
|
||||||
|
|
@ -327,11 +335,7 @@ impl RustSignalEngine {
|
||||||
.fetch_one(&store.pool)
|
.fetch_one(&store.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if id > 16_777_215 {
|
if id > 16_777_215 { 1 } else { id }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..30 {
|
for _ in 0..30 {
|
||||||
|
|
|
||||||
|
|
@ -116,3 +116,130 @@ async fn test_add_hidden_contact() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_admin_and_non_admin_leave_group() -> anyhow::Result<()> {
|
||||||
|
let tester_a = create_authenticated_tester().await?;
|
||||||
|
let tester_b = create_authenticated_tester().await?;
|
||||||
|
let tester_c = create_authenticated_tester().await?;
|
||||||
|
|
||||||
|
// Setup contacts
|
||||||
|
ContactService::new(&tester_a.context)
|
||||||
|
.request_by_username(tester_b.username.clone(), true)
|
||||||
|
.await?;
|
||||||
|
tester_b
|
||||||
|
.wait_for_contact_state(tester_a.user_id, false, true)
|
||||||
|
.await?;
|
||||||
|
ContactService::new(&tester_b.context)
|
||||||
|
.accept_request(tester_a.user_id, true)
|
||||||
|
.await?;
|
||||||
|
tester_a
|
||||||
|
.wait_for_contact_state(tester_b.user_id, true, false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
ContactService::new(&tester_a.context)
|
||||||
|
.request_by_username(tester_c.username.clone(), true)
|
||||||
|
.await?;
|
||||||
|
tester_c
|
||||||
|
.wait_for_contact_state(tester_a.user_id, false, true)
|
||||||
|
.await?;
|
||||||
|
ContactService::new(&tester_c.context)
|
||||||
|
.accept_request(tester_a.user_id, true)
|
||||||
|
.await?;
|
||||||
|
tester_a
|
||||||
|
.wait_for_contact_state(tester_c.user_id, true, false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Tester A creates group with Tester B and Tester C
|
||||||
|
let group_name = "Leave Test Group";
|
||||||
|
GroupService::new(&tester_a.context)
|
||||||
|
.create_group(group_name.into(), vec![tester_b.user_id, tester_c.user_id])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let group_id = {
|
||||||
|
let db_a = tester_a.context.app_db.read().await.clone();
|
||||||
|
sqlx::query_scalar!(
|
||||||
|
"SELECT group_id FROM groups WHERE is_direct_chat = 0 ORDER BY rowid DESC LIMIT 1"
|
||||||
|
)
|
||||||
|
.fetch_one(&db_a.pool)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
|
tester_b.wait_for_group_exists(&group_id, group_name).await?;
|
||||||
|
tester_c.wait_for_group_exists(&group_id, group_name).await?;
|
||||||
|
|
||||||
|
// Fetch missing public key for tester_b
|
||||||
|
{
|
||||||
|
let db_a = tester_a.context.app_db.read().await.clone();
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE group_members SET last_message = CAST(strftime('%s','now') AS INTEGER) WHERE group_id = ? AND contact_id = ?",
|
||||||
|
group_id,
|
||||||
|
tester_b.user_id
|
||||||
|
)
|
||||||
|
.execute(&db_a.pool)
|
||||||
|
.await?;
|
||||||
|
GroupService::new(&tester_a.context).fetch_missing_group_public_keys().await?;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promote tester_b to admin so tester_a can leave later with an admin remaining
|
||||||
|
GroupService::new(&tester_a.context)
|
||||||
|
.manage_admin(group_id.clone(), tester_b.user_id, false)
|
||||||
|
.await?;
|
||||||
|
GroupService::new(&tester_b.context)
|
||||||
|
.fetch_group_state(group_id.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let db_b = tester_b.context.app_db.read().await.clone();
|
||||||
|
let is_admin = sqlx::query_scalar!(
|
||||||
|
"SELECT is_group_admin FROM groups WHERE group_id = ?",
|
||||||
|
group_id
|
||||||
|
)
|
||||||
|
.fetch_one(&db_b.pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(is_admin, 1, "tester_b should be admin after promotion");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Non-admin (tester_c) leaves the group
|
||||||
|
GroupService::new(&tester_c.context)
|
||||||
|
.leave_group(group_id.clone())
|
||||||
|
.await?;
|
||||||
|
tester_c.wait_for_group_left(&group_id).await?;
|
||||||
|
|
||||||
|
// Verify tester_a and tester_b see tester_c removed
|
||||||
|
GroupService::new(&tester_a.context)
|
||||||
|
.fetch_group_state(group_id.clone())
|
||||||
|
.await?;
|
||||||
|
tester_a
|
||||||
|
.wait_for_group_member_removed(&group_id, tester_c.user_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// 2. Admin (tester_a) leaves the group using leave_group
|
||||||
|
GroupService::new(&tester_a.context)
|
||||||
|
.leave_group(group_id.clone())
|
||||||
|
.await?;
|
||||||
|
tester_a.wait_for_group_left(&group_id).await?;
|
||||||
|
|
||||||
|
// Verify tester_b (remaining admin) sees tester_a removed and tester_b remains admin
|
||||||
|
GroupService::new(&tester_b.context)
|
||||||
|
.fetch_group_state(group_id.clone())
|
||||||
|
.await?;
|
||||||
|
tester_b
|
||||||
|
.wait_for_group_member_removed(&group_id, tester_a.user_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let db_b = tester_b.context.app_db.read().await.clone();
|
||||||
|
let is_admin = sqlx::query_scalar!(
|
||||||
|
"SELECT is_group_admin FROM groups WHERE group_id = ?",
|
||||||
|
group_id
|
||||||
|
)
|
||||||
|
.fetch_one(&db_b.pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(is_admin, 1, "tester_b should still be admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use libsignal_protocol::{GenericSignedPreKey, IdentityKeyPair, KeyPair, SignedPreKeyRecord};
|
use libsignal_protocol::IdentityKeyPair;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use rust_lib_twonly::api::{ApiRuntime, Server};
|
use rust_lib_twonly::api::{ApiRuntime, Server};
|
||||||
use rust_lib_twonly::bridge::api::{ApiConnectionState, ServerResult};
|
use rust_lib_twonly::bridge::api::{ApiConnectionState, ServerResult};
|
||||||
|
|
@ -6,7 +6,7 @@ use rust_lib_twonly::context::Context;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{Duration, sleep};
|
||||||
|
|
||||||
pub(crate) struct Tester {
|
pub(crate) struct Tester {
|
||||||
pub context: Arc<Context>,
|
pub context: Arc<Context>,
|
||||||
|
|
@ -378,37 +378,10 @@ impl Tester {
|
||||||
let mut csprng = rand::rngs::StdRng::from_os_rng();
|
let mut csprng = rand::rngs::StdRng::from_os_rng();
|
||||||
let identity_pair = IdentityKeyPair::generate(&mut csprng);
|
let identity_pair = IdentityKeyPair::generate(&mut csprng);
|
||||||
let registration_id: u32 = rand::Rng::random::<u32>(&mut csprng) & 0x7FFFFFFF;
|
let registration_id: u32 = rand::Rng::random::<u32>(&mut csprng) & 0x7FFFFFFF;
|
||||||
let timestamp = libsignal_protocol::Timestamp::from_epoch_millis(
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis() as u64,
|
|
||||||
);
|
|
||||||
|
|
||||||
let signed_pre_key_pair = KeyPair::generate(&mut csprng);
|
|
||||||
let signature = identity_pair
|
|
||||||
.private_key()
|
|
||||||
.calculate_signature_for_multipart_message(
|
|
||||||
&[&signed_pre_key_pair.public_key.serialize()],
|
|
||||||
&mut csprng,
|
|
||||||
)
|
|
||||||
.map_err(|e| anyhow::anyhow!("Signal error: {}", e))?;
|
|
||||||
|
|
||||||
let signed_prekey =
|
|
||||||
SignedPreKeyRecord::new(1.into(), timestamp, &signed_pre_key_pair, &signature);
|
|
||||||
let mut pre_key_store = std::collections::HashMap::new();
|
|
||||||
pre_key_store.insert(
|
|
||||||
1,
|
|
||||||
signed_prekey
|
|
||||||
.serialize()
|
|
||||||
.map_err(|e| anyhow::anyhow!("Signal error: {}", e))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.inject_test_signal_identity(
|
.inject_test_signal_identity(
|
||||||
identity_pair.serialize().to_vec(),
|
identity_pair.serialize().to_vec(),
|
||||||
registration_id as i64,
|
registration_id as i64,
|
||||||
pre_key_store,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
@ -592,7 +565,9 @@ impl Tester {
|
||||||
}
|
}
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
Err(anyhow::anyhow!("message {message_id} was not marked as opened"))
|
Err(anyhow::anyhow!(
|
||||||
|
"message {message_id} was not marked as opened"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_group_chat_deletion_time(
|
pub async fn wait_for_group_chat_deletion_time(
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ void main() {
|
||||||
Future<int> getAndCreateUserId() async {
|
Future<int> getAndCreateUserId() async {
|
||||||
return mutex.protect<int>(() async {
|
return mutex.protect<int>(() async {
|
||||||
final userId = usedUserIds += 1;
|
final userId = usedUserIds += 1;
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion(userId: Value(userId), username: Value('$userId')),
|
ContactsCompanion(userId: Value(userId), username: Value('$userId')),
|
||||||
);
|
);
|
||||||
return userId;
|
return userId;
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,7 @@ void main() {
|
||||||
url: 'https://x.com/netzpolitik_org/status/1162346968124968960',
|
url: 'https://x.com/netzpolitik_org/status/1162346968124968960',
|
||||||
siteName: 'X (formerly Twitter)',
|
siteName: 'X (formerly Twitter)',
|
||||||
desc:
|
desc:
|
||||||
'Weil unsere Datenanalyse zum Twitter-Account von Maaßen rechte Millieus und ihre Verbindungen offengelegt hat, haben wir einen rechten Shitstorm an der Backe. Klar ist: Wir lassen uns nicht einschüchtern und freuen uns auf Unterstützung! \n'
|
'Weil unsere Datenanalyse zum Twitter-Account von Maaßen rechte Millieus und ihre Verbindungen offengelegt hat, haben wir einen rechten Shitstorm an der Backe. Klar ist: Wir lassen uns nicht einsch…',
|
||||||
'\n'
|
|
||||||
'https://t.co/MQZ7ulHakF',
|
|
||||||
image: 'https://pbs.twimg.com/media/ECF8Z5KWwAIBZ6o.jpg:large',
|
image: 'https://pbs.twimg.com/media/ECF8Z5KWwAIBZ6o.jpg:large',
|
||||||
vendor: Vendor.twitterPosting,
|
vendor: Vendor.twitterPosting,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||||
as pb;
|
as pb;
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
import 'package:twonly/src/services/api/api.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.api.dart';
|
|
||||||
import 'package:twonly/src/services/api/rust_api_result.dart';
|
import 'package:twonly/src/services/api/rust_api_result.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/pow.dart';
|
import 'package:twonly/src/utils/pow.dart';
|
||||||
|
|
@ -52,19 +49,15 @@ class TestClient {
|
||||||
api = ApiService();
|
api = ApiService();
|
||||||
|
|
||||||
await run(() async {
|
await run(() async {
|
||||||
await createIfNotExistsSignalIdentity();
|
|
||||||
|
|
||||||
Log.info('Connecting to API...');
|
|
||||||
final connected = await api.connect();
|
|
||||||
Log.info('Connected: $connected');
|
|
||||||
if (!connected) throw Exception('Failed to connect to API');
|
|
||||||
|
|
||||||
Log.info('Requesting POW...');
|
Log.info('Requesting POW...');
|
||||||
final pow = await rustApiProtobuf(
|
final dynamic pow;
|
||||||
RustApi.getProofOfWork(),
|
try {
|
||||||
decodeProofOfWork,
|
final raw = await RustApi.getProofOfWork();
|
||||||
);
|
pow = decodeProofOfWork(raw);
|
||||||
if (pow == null) throw Exception('Failed to get POW');
|
} catch (e, st) {
|
||||||
|
print('POW EXCEPTION: $e\n$st');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
Log.info('POW result: $pow');
|
Log.info('POW result: $pow');
|
||||||
|
|
||||||
final prefix = pow.prefix;
|
final prefix = pow.prefix;
|
||||||
|
|
@ -87,15 +80,12 @@ class TestClient {
|
||||||
appVersion: 100,
|
appVersion: 100,
|
||||||
);
|
);
|
||||||
await UserService.save(userData);
|
await UserService.save(userData);
|
||||||
|
|
||||||
await api.authenticate();
|
|
||||||
await signalGetPreKeys();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initContact(TestClient other) async {
|
Future<void> initContact(TestClient other) async {
|
||||||
await run(() async {
|
await run(() async {
|
||||||
await env.db.contactsDao.insertContact(
|
await env.db.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(
|
ContactsCompanion.insert(
|
||||||
userId: Value(other.realUserId),
|
userId: Value(other.realUserId),
|
||||||
username: other.username,
|
username: other.username,
|
||||||
|
|
@ -107,12 +97,7 @@ class TestClient {
|
||||||
GroupsCompanion(groupName: Value(other.username)),
|
GroupsCompanion(groupName: Value(other.username)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final userData = await rustApiProtobuf(
|
await RustApi.establishSignalSession(contactId: other.realUserId);
|
||||||
RustApi.getUserById(userId: other.realUserId),
|
|
||||||
decodeUserData,
|
|
||||||
);
|
|
||||||
final sessionStarted = await processSignalUserData(userData!);
|
|
||||||
if (!sessionStarted) throw Exception('Failed to start session');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,14 +114,14 @@ class TestClient {
|
||||||
type: Value(MessageType.text.name),
|
type: Value(MessageType.text.name),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await sendCipherText(
|
await RustApi.sendEncryptedContent(
|
||||||
target.realUserId,
|
contactId: target.realUserId,
|
||||||
pb.EncryptedContent(
|
content: pb.EncryptedContent(
|
||||||
groupId: defaultGroup!.groupId,
|
groupId: defaultGroup!.groupId,
|
||||||
textMessage: pb.EncryptedContent_TextMessage()
|
textMessage: pb.EncryptedContent_TextMessage()
|
||||||
..senderMessageId = m!.messageId
|
..senderMessageId = m!.messageId
|
||||||
..text = text,
|
..text = text,
|
||||||
),
|
).writeToBuffer(),
|
||||||
messageId: m.messageId,
|
messageId: m.messageId,
|
||||||
);
|
);
|
||||||
return m;
|
return m;
|
||||||
|
|
@ -148,7 +133,10 @@ class TestClient {
|
||||||
pb.EncryptedContent content,
|
pb.EncryptedContent content,
|
||||||
) async {
|
) async {
|
||||||
await run(() async {
|
await run(() async {
|
||||||
await sendCipherText(target.realUserId, content);
|
await RustApi.sendEncryptedContent(
|
||||||
|
contactId: target.realUserId,
|
||||||
|
content: content.writeToBuffer(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
import 'package:twonly/src/services/api/api.service.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
|
|
@ -67,15 +66,11 @@ class UserEnvironment {
|
||||||
required this.username,
|
required this.username,
|
||||||
required this.db,
|
required this.db,
|
||||||
required this.userService,
|
required this.userService,
|
||||||
required this.identityKeyPair,
|
|
||||||
required this.registrationId,
|
|
||||||
});
|
});
|
||||||
final int userId;
|
final int userId;
|
||||||
final String username;
|
final String username;
|
||||||
final TwonlyDB db;
|
final TwonlyDB db;
|
||||||
final UserService userService;
|
final UserService userService;
|
||||||
final IdentityKeyPair identityKeyPair;
|
|
||||||
final int registrationId;
|
|
||||||
|
|
||||||
static Future<UserEnvironment> create(
|
static Future<UserEnvironment> create(
|
||||||
int userId,
|
int userId,
|
||||||
|
|
@ -101,16 +96,11 @@ class UserEnvironment {
|
||||||
|
|
||||||
us.isUserCreated = true;
|
us.isUserCreated = true;
|
||||||
|
|
||||||
final identityKeyPair = generateIdentityKeyPair();
|
|
||||||
final registrationId = generateRegistrationId(true);
|
|
||||||
|
|
||||||
return UserEnvironment(
|
return UserEnvironment(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
username: username,
|
username: username,
|
||||||
db: db,
|
db: db,
|
||||||
userService: us,
|
userService: us,
|
||||||
identityKeyPair: identityKeyPair,
|
|
||||||
registrationId: registrationId,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ void main() {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (var i = 0; i < users.length; i++) {
|
for (var i = 0; i < users.length; i++) {
|
||||||
await database.contactsDao.insertContact(
|
await database.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion(userId: Value(i), username: Value(users[i])),
|
ContactsCompanion(userId: Value(i), username: Value(users[i])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,8 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await clientA.run(() async => clientA.api.close(null));
|
await clientA.run(() async => clientA.api.dispose());
|
||||||
await clientB.run(() async => clientB.api.close(null));
|
await clientB.run(() async => clientB.api.dispose());
|
||||||
await clientA.env.db.close();
|
await clientA.env.db.close();
|
||||||
await clientB.env.db.close();
|
await clientB.env.db.close();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:twonly/core/bridge.dart' as bridge;
|
||||||
import 'package:twonly/core/frb_generated.dart';
|
import 'package:twonly/core/frb_generated.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
import 'package:twonly/src/services/api/api.service.dart';
|
||||||
|
|
@ -49,6 +50,7 @@ void main() {
|
||||||
} else {
|
} else {
|
||||||
await RustLib.init();
|
await RustLib.init();
|
||||||
}
|
}
|
||||||
|
await initFlutterCallbacksForRust();
|
||||||
tempDir = Directory.systemTemp.createTempSync(
|
tempDir = Directory.systemTemp.createTempSync(
|
||||||
'twonly_cloud_backup_test_',
|
'twonly_cloud_backup_test_',
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:twonly/core/frb_generated.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
|
|
||||||
import '../mocks/user_config.dart';
|
import '../mocks/user_config.dart';
|
||||||
|
|
@ -20,6 +21,18 @@ void main() {
|
||||||
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await locator.reset();
|
await locator.reset();
|
||||||
locator
|
locator
|
||||||
|
|
@ -31,8 +44,7 @@ void main() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
..registerSingleton<UserService>(UserService())
|
..registerSingleton<UserService>(UserService());
|
||||||
..registerSingleton<ApiService>(ApiService());
|
|
||||||
|
|
||||||
// isUserDiscoveryEnabled defaults to false, so no Rust bridge calls happen
|
// isUserDiscoveryEnabled defaults to false, so no Rust bridge calls happen
|
||||||
// in addKeyVerification / deleteKeyVerification.
|
// in addKeyVerification / deleteKeyVerification.
|
||||||
|
|
@ -55,7 +67,7 @@ void main() {
|
||||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Future<void> insertContact(int userId, {String? username}) async {
|
Future<void> insertContact(int userId, {String? username}) async {
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(
|
ContactsCompanion.insert(
|
||||||
userId: Value(userId),
|
userId: Value(userId),
|
||||||
username: username ?? 'user$userId',
|
username: username ?? 'user$userId',
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
import 'dart:convert' show utf8;
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/core/bridge.dart' as bridge;
|
import 'package:twonly/core/bridge.dart' as bridge;
|
||||||
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||||
import 'package:twonly/core/frb_generated.dart';
|
import 'package:twonly/core/frb_generated.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
|
||||||
as api_pb;
|
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
import 'package:twonly/src/services/api/api.service.dart';
|
||||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -89,8 +83,6 @@ void main() {
|
||||||
);
|
);
|
||||||
userService.isUserCreated = true;
|
userService.isUserCreated = true;
|
||||||
await UserService.save(userService.currentUser);
|
await UserService.save(userService.currentUser);
|
||||||
|
|
||||||
await createIfNotExistsSignalIdentity();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
|
|
@ -108,36 +100,20 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> setupSignalSession(int contactId) async {
|
Future<void> setupSignalSession(int contactId) async {
|
||||||
final identityKeyPair = generateIdentityKeyPair();
|
final bundle = await RustSignal.generateBundle();
|
||||||
final registrationId = generateRegistrationId(true);
|
await RustSignal.processPrekeyBundle(
|
||||||
final signedPreKey = generateSignedPreKey(identityKeyPair, 1);
|
name: contactId.toString(),
|
||||||
final preKey = generatePreKeys(1, 1).first;
|
deviceId: 1,
|
||||||
|
bundle: bundle,
|
||||||
final responseUserData = api_pb.Response_UserData()
|
|
||||||
..userId = Int64(contactId)
|
|
||||||
..username = utf8.encode('user_$contactId')
|
|
||||||
..registrationId = Int64(registrationId)
|
|
||||||
..publicIdentityKey = identityKeyPair.getPublicKey().serialize()
|
|
||||||
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
|
|
||||||
..signedPrekeyId = Int64(signedPreKey.id)
|
|
||||||
..signedPrekeySignature = signedPreKey.signature;
|
|
||||||
|
|
||||||
responseUserData.prekeys.add(
|
|
||||||
api_pb.Response_PreKey()
|
|
||||||
..id = Int64(preKey.id)
|
|
||||||
..prekey = preKey.getKeyPair().publicKey.serialize(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final success = await processSignalUserData(responseUserData);
|
|
||||||
expect(success, isTrue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group('PasswordlessRecoveryService - enablePasswordlessRecovery', () {
|
group('PasswordlessRecoveryService - enablePasswordlessRecovery', () {
|
||||||
test('works with SecondFactorType.none', () async {
|
test('works with SecondFactorType.none', () async {
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
||||||
);
|
);
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
|
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -163,7 +139,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('works with SecondFactorType.email', () async {
|
test('works with SecondFactorType.email', () async {
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -183,7 +159,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('works with SecondFactorType.pin', () async {
|
test('works with SecondFactorType.pin', () async {
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -203,20 +179,17 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sends delete messages to old trusted friends', () async {
|
test('sends delete messages to old trusted friends', () async {
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(
|
ContactsCompanion.insert(
|
||||||
userId: const Value(2),
|
userId: const Value(2),
|
||||||
username: 'friend_2',
|
username: 'friend_2',
|
||||||
recoveryIsTrustedFriend: const Value(true),
|
recoveryIsTrustedFriend: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
|
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
|
||||||
);
|
);
|
||||||
|
|
||||||
await setupSignalSession(2);
|
|
||||||
await setupSignalSession(3);
|
|
||||||
|
|
||||||
final success =
|
final success =
|
||||||
await PasswordlessRecoveryService.enablePasswordlessRecovery(
|
await PasswordlessRecoveryService.enablePasswordlessRecovery(
|
||||||
trustedFriendIds: [3],
|
trustedFriendIds: [3],
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('testing api', () {
|
|
||||||
test('testing api connection', () async {
|
|
||||||
const offset = 100;
|
|
||||||
const count = 400;
|
|
||||||
|
|
||||||
var prekeys = generatePreKeys(offset, count);
|
|
||||||
expect(count, prekeys.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < prekeys.length; i++) {
|
|
||||||
expect(prekeys[i].id, offset + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
prekeys += generatePreKeys(offset + count, count);
|
|
||||||
expect(count * 2, prekeys.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < (count * 2); i++) {
|
|
||||||
expect(prekeys[i].id, offset + i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
// ignore_for_file: avoid_print
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
|
||||||
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/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/tables/messages.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/services/api/api.service.dart';
|
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:workmanager/workmanager.dart';
|
|
||||||
|
|
||||||
import '../mocks/platform_channels.dart';
|
|
||||||
import '../mocks/test_client.dart';
|
|
||||||
import '../mocks/workmanager.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (!Platform.isMacOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
late Directory tempDir;
|
|
||||||
|
|
||||||
setUpAll(() async {
|
|
||||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
|
||||||
WorkmanagerPlatform.instance = MockWorkmanagerPlatform();
|
|
||||||
tempDir = Directory.systemTemp.createTempSync('twonly_pqc_c2c_test_');
|
|
||||||
AppEnvironment.initTesting(
|
|
||||||
customCacheDir: tempDir.path,
|
|
||||||
customSupportDir: tempDir.path,
|
|
||||||
);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
await bridge.initializeTwonlyFlutter(
|
|
||||||
config: bridge.InitConfig(
|
|
||||||
databaseDir: tempDir.path,
|
|
||||||
dataDir: tempDir.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (locator.isRegistered<TwonlyDB>()) await locator.unregister<TwonlyDB>();
|
|
||||||
if (locator.isRegistered<UserService>()) {
|
|
||||||
await locator.unregister<UserService>();
|
|
||||||
}
|
|
||||||
if (locator.isRegistered<ApiService>()) {
|
|
||||||
await locator.unregister<ApiService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
locator
|
|
||||||
..registerFactory<TwonlyDB>(() {
|
|
||||||
final db = Zone.current[#twonlyDB] as TwonlyDB?;
|
|
||||||
if (db != null) return db;
|
|
||||||
throw StateError('No TwonlyDB in active Zone.');
|
|
||||||
})
|
|
||||||
..registerFactory<UserService>(() {
|
|
||||||
final us = Zone.current[#userService] as UserService?;
|
|
||||||
if (us != null) return us;
|
|
||||||
throw StateError('No UserService in active Zone.');
|
|
||||||
})
|
|
||||||
..registerFactory<ApiService>(() {
|
|
||||||
final api = Zone.current[#apiService] as ApiService?;
|
|
||||||
if (api != null) return api;
|
|
||||||
throw StateError('No ApiService in active Zone.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDownAll(() async {
|
|
||||||
if (tempDir.existsSync()) {
|
|
||||||
try {
|
|
||||||
tempDir.deleteSync(recursive: true);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
group('PQC C2C Migration Protocol Tests', () {
|
|
||||||
late TestClient clientA;
|
|
||||||
late TestClient clientB;
|
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
setupPlatformChannelMocks();
|
|
||||||
HttpOverrides.global = RealHttpOverrides();
|
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
||||||
.setMockMethodCallHandler(
|
|
||||||
const MethodChannel('dev.fluttercommunity.plus/package_info'),
|
|
||||||
(call) async {
|
|
||||||
return {
|
|
||||||
'appName': 'twonly',
|
|
||||||
'packageName': 'eu.twonly.app',
|
|
||||||
'version': '1.0.0',
|
|
||||||
'buildNumber': '100',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await Workmanager().initialize(() {});
|
|
||||||
|
|
||||||
clientA = TestClient(3001);
|
|
||||||
clientB = TestClient(4002);
|
|
||||||
|
|
||||||
// Initialize BOTH clients with PQC generation disabled.
|
|
||||||
await clientA.init(disablePqc: true);
|
|
||||||
await clientB.init(disablePqc: true);
|
|
||||||
|
|
||||||
await clientA.initContact(clientB);
|
|
||||||
await clientB.initContact(clientA);
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDown(() async {
|
|
||||||
await clientA.run(() async => clientA.api.close(null));
|
|
||||||
await clientB.run(() async => clientB.api.close(null));
|
|
||||||
await clientA.env.db.close();
|
|
||||||
await clientB.env.db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'C2C: V1 to V2 PQC Migration and 100 Messages Exchange',
|
|
||||||
() async {
|
|
||||||
print('=== Starting V1 Message Exchange ===');
|
|
||||||
|
|
||||||
// Exchange 100 messages in V1
|
|
||||||
for (var i = 0; i < 100; i++) {
|
|
||||||
final textAtoB = 'Hello Bob V1 - Message $i';
|
|
||||||
await clientA.sendText(clientB, textAtoB);
|
|
||||||
final receivedByB = await clientB.expectMessage(
|
|
||||||
(m) => m.content == textAtoB && m.senderId == clientA.realUserId,
|
|
||||||
);
|
|
||||||
expect(receivedByB.content, textAtoB);
|
|
||||||
expect(receivedByB.type, MessageType.text.name);
|
|
||||||
|
|
||||||
final textBtoA = 'Hello Alice V1 - Reply $i';
|
|
||||||
await clientB.sendText(clientA, textBtoA);
|
|
||||||
final receivedByA = await clientA.expectMessage(
|
|
||||||
(m) => m.content == textBtoA && m.senderId == clientB.realUserId,
|
|
||||||
);
|
|
||||||
expect(receivedByA.content, textBtoA);
|
|
||||||
expect(receivedByA.type, MessageType.text.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
print('=== Migrating to V2 PQC ===');
|
|
||||||
|
|
||||||
// Step 2: Migrate to V2
|
|
||||||
// For Client A
|
|
||||||
await clientA.run(() async {
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.signalLastPqcPreKeysUploaded = null;
|
|
||||||
});
|
|
||||||
await createIfNotExistsSignalIdentity(); // This will upload PQC keys
|
|
||||||
});
|
|
||||||
|
|
||||||
// For Client B
|
|
||||||
await clientB.run(() async {
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.signalLastPqcPreKeysUploaded = null;
|
|
||||||
});
|
|
||||||
await createIfNotExistsSignalIdentity(); // This will upload PQC keys
|
|
||||||
});
|
|
||||||
|
|
||||||
// Both clients now fetch the updated contacts from the dev server
|
|
||||||
// This will invoke processSignalUserData and migrate the signal version to V2
|
|
||||||
await clientA.run(() async {
|
|
||||||
final userData = await rustApiProtobuf(
|
|
||||||
RustApi.getUserById(userId: clientB.realUserId),
|
|
||||||
decodeUserData,
|
|
||||||
);
|
|
||||||
if (userData != null) await processSignalUserData(userData);
|
|
||||||
});
|
|
||||||
await clientB.run(() async {
|
|
||||||
final userData = await rustApiProtobuf(
|
|
||||||
RustApi.getUserById(userId: clientA.realUserId),
|
|
||||||
decodeUserData,
|
|
||||||
);
|
|
||||||
if (userData != null) await processSignalUserData(userData);
|
|
||||||
});
|
|
||||||
|
|
||||||
print('=== Starting V2 PQC Message Exchange ===');
|
|
||||||
|
|
||||||
// Step 3: Exchange 100 messages in V2
|
|
||||||
for (var i = 0; i < 100; i++) {
|
|
||||||
final textAtoB = 'Hello Bob V2 PQC - Message $i';
|
|
||||||
await clientA.sendText(clientB, textAtoB);
|
|
||||||
final receivedByB = await clientB.expectMessage(
|
|
||||||
(m) => m.content == textAtoB && m.senderId == clientA.realUserId,
|
|
||||||
);
|
|
||||||
expect(receivedByB.content, textAtoB);
|
|
||||||
expect(receivedByB.type, MessageType.text.name);
|
|
||||||
|
|
||||||
final textBtoA = 'Hello Alice V2 PQC - Reply $i';
|
|
||||||
await clientB.sendText(clientA, textBtoA);
|
|
||||||
final receivedByA = await clientA.expectMessage(
|
|
||||||
(m) => m.content == textBtoA && m.senderId == clientB.realUserId,
|
|
||||||
);
|
|
||||||
expect(receivedByA.content, textBtoA);
|
|
||||||
expect(receivedByA.type, MessageType.text.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
print('=== PQC Migration and Exchange Completed Successfully ===');
|
|
||||||
},
|
|
||||||
timeout: const Timeout(Duration(minutes: 5)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
import 'dart:convert' show utf8;
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
|
||||||
import 'package:drift/native.dart';
|
|
||||||
import 'package:fixnum/fixnum.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/key_manager.dart';
|
|
||||||
import 'package:twonly/core/bridge/wrapper/signal.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/tables/contacts.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
|
||||||
as api_pb;
|
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|
||||||
as msg_pb;
|
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (!Platform.isMacOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
late Directory tempDir;
|
|
||||||
late TwonlyDB db;
|
|
||||||
|
|
||||||
setUpAll(() async {
|
|
||||||
Log.init();
|
|
||||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
|
||||||
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_pqc_migration_test_');
|
|
||||||
AppEnvironment.initTesting(
|
|
||||||
customCacheDir: tempDir.path,
|
|
||||||
customSupportDir: tempDir.path,
|
|
||||||
);
|
|
||||||
|
|
||||||
await bridge.initializeTwonlyFlutter(
|
|
||||||
config: bridge.InitConfig(
|
|
||||||
databaseDir: tempDir.path,
|
|
||||||
dataDir: tempDir.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
db = TwonlyDB(NativeDatabase.memory());
|
|
||||||
locator.registerFactory<TwonlyDB>(() => db);
|
|
||||||
});
|
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
await createIfNotExistsSignalIdentity();
|
|
||||||
await RustKeyManager.setUserId(userId: 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDownAll(() async {
|
|
||||||
await db.close();
|
|
||||||
if (tempDir.existsSync()) {
|
|
||||||
try {
|
|
||||||
tempDir.deleteSync(recursive: true);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PQC Migration Test: V1 to V2', () async {
|
|
||||||
const contactId = 999;
|
|
||||||
|
|
||||||
// 1. Setup contact as V1
|
|
||||||
await db.contactsDao.insertContact(
|
|
||||||
ContactsCompanion.insert(
|
|
||||||
userId: const Value(contactId),
|
|
||||||
username: 'test_pqc_user',
|
|
||||||
accepted: const Value(true),
|
|
||||||
signalVersion: const Value(SignalVersion.v1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
var contact = await db.contactsDao.getContactById(contactId);
|
|
||||||
expect(contact, isNotNull);
|
|
||||||
expect(contact!.signalVersion, SignalVersion.v1);
|
|
||||||
|
|
||||||
// 2. Generate a valid PQC bundle via RustSignal
|
|
||||||
final generatedBundle = await RustSignal.generateBundle();
|
|
||||||
|
|
||||||
final userData = api_pb.Response_UserData(
|
|
||||||
userId: Int64(contactId),
|
|
||||||
username: utf8.encode('test_pqc_user'),
|
|
||||||
registrationId: Int64(generatedBundle.registrationId),
|
|
||||||
publicIdentityKey: generatedBundle.identityKey,
|
|
||||||
pqcBundle: api_pb.Response_PqcBundle(
|
|
||||||
prekey: api_pb.Response_PqcPreKey(
|
|
||||||
eccPreKeyId: generatedBundle.preKeyId != null
|
|
||||||
? Int64(generatedBundle.preKeyId!)
|
|
||||||
: null,
|
|
||||||
eccPreKey: generatedBundle.preKeyPublic,
|
|
||||||
kyberPreKeyId: Int64(generatedBundle.kyberPreKeyId),
|
|
||||||
kyberPreKey: generatedBundle.kyberPreKeyPublic,
|
|
||||||
kyberPreKeySignature: generatedBundle.kyberPreKeySignature,
|
|
||||||
),
|
|
||||||
eccSignedPrekeyId: Int64(generatedBundle.signedPreKeyId),
|
|
||||||
eccSignedPrekey: generatedBundle.signedPreKeyPublic,
|
|
||||||
eccSignedPrekeySignature: generatedBundle.signedPreKeySignature,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Process the UserData containing the PQC bundle
|
|
||||||
final success = await processSignalUserData(userData);
|
|
||||||
expect(success, isTrue, reason: 'Failed to process PQC user data');
|
|
||||||
|
|
||||||
// 4. Verify contact was upgraded to V2
|
|
||||||
contact = await db.contactsDao.getContactById(contactId);
|
|
||||||
expect(contact, isNotNull);
|
|
||||||
expect(contact!.signalVersion, SignalVersion.v2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PQC Encryption and Decryption V2', () async {
|
|
||||||
const contactId = 888;
|
|
||||||
|
|
||||||
// Setup contact as V2
|
|
||||||
await db.contactsDao.insertContact(
|
|
||||||
ContactsCompanion.insert(
|
|
||||||
userId: const Value(contactId),
|
|
||||||
username: 'test_pqc_user_2',
|
|
||||||
accepted: const Value(true),
|
|
||||||
signalVersion: const Value(SignalVersion.v2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate bundle and process it for the contact to establish a session
|
|
||||||
final generatedBundle = await RustSignal.generateBundle();
|
|
||||||
|
|
||||||
final userData = api_pb.Response_UserData(
|
|
||||||
userId: Int64(contactId),
|
|
||||||
username: utf8.encode('test_pqc_user_2'),
|
|
||||||
registrationId: Int64(generatedBundle.registrationId),
|
|
||||||
publicIdentityKey: generatedBundle.identityKey,
|
|
||||||
pqcBundle: api_pb.Response_PqcBundle(
|
|
||||||
prekey: api_pb.Response_PqcPreKey(
|
|
||||||
eccPreKeyId: generatedBundle.preKeyId != null
|
|
||||||
? Int64(generatedBundle.preKeyId!)
|
|
||||||
: null,
|
|
||||||
eccPreKey: generatedBundle.preKeyPublic,
|
|
||||||
kyberPreKeyId: Int64(generatedBundle.kyberPreKeyId),
|
|
||||||
kyberPreKey: generatedBundle.kyberPreKeyPublic,
|
|
||||||
kyberPreKeySignature: generatedBundle.kyberPreKeySignature,
|
|
||||||
),
|
|
||||||
eccSignedPrekeyId: Int64(generatedBundle.signedPreKeyId),
|
|
||||||
eccSignedPrekey: generatedBundle.signedPreKeyPublic,
|
|
||||||
eccSignedPrekeySignature: generatedBundle.signedPreKeySignature,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final success = await processSignalUserData(userData);
|
|
||||||
expect(success, isTrue);
|
|
||||||
|
|
||||||
// Encrypt a message
|
|
||||||
final plaintext = msg_pb.EncryptedContent(
|
|
||||||
textMessage: msg_pb.EncryptedContent_TextMessage(
|
|
||||||
text: 'Hello PQC World!',
|
|
||||||
),
|
|
||||||
).writeToBuffer();
|
|
||||||
|
|
||||||
final encryptedResult = await signalEncryptMessageV2(contactId, plaintext);
|
|
||||||
expect(encryptedResult, isNotNull);
|
|
||||||
expect(encryptedResult!.type, msg_pb.Message_Type.CIPHERTEXT_V2);
|
|
||||||
expect(encryptedResult.ciphertext, isNotEmpty);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue