mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 17:32:54 +00:00
user discovery database store works
This commit is contained in:
parent
252e7653db
commit
eb22acacee
36 changed files with 18939 additions and 666 deletions
|
|
@ -8,7 +8,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
import 'database/contact.dart';
|
import 'database/contact.dart';
|
||||||
import 'frb_generated.dart';
|
import 'frb_generated.dart';
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_instance`
|
// These functions are ignored because they are not marked as `pub`: `get_workspace`
|
||||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Twonly`
|
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Twonly`
|
||||||
|
|
||||||
Future<void> initializeTwonly({required TwonlyConfig config}) =>
|
Future<void> initializeTwonly({required TwonlyConfig config}) =>
|
||||||
|
|
@ -30,7 +30,7 @@ class OtherPromotion {
|
||||||
this.publicKeyVerifiedTimestamp,
|
this.publicKeyVerifiedTimestamp,
|
||||||
});
|
});
|
||||||
final int promotionId;
|
final int promotionId;
|
||||||
final BigInt publicId;
|
final PlatformInt64 publicId;
|
||||||
final PlatformInt64 fromContactId;
|
final PlatformInt64 fromContactId;
|
||||||
final int threshold;
|
final int threshold;
|
||||||
final Uint8List announcementShare;
|
final Uint8List announcementShare;
|
||||||
|
|
@ -61,16 +61,19 @@ class OtherPromotion {
|
||||||
class TwonlyConfig {
|
class TwonlyConfig {
|
||||||
const TwonlyConfig({
|
const TwonlyConfig({
|
||||||
required this.databasePath,
|
required this.databasePath,
|
||||||
|
required this.dataDirectory,
|
||||||
});
|
});
|
||||||
final String databasePath;
|
final String databasePath;
|
||||||
|
final String dataDirectory;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => databasePath.hashCode;
|
int get hashCode => databasePath.hashCode ^ dataDirectory.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is TwonlyConfig &&
|
other is TwonlyConfig &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
databasePath == other.databasePath;
|
databasePath == other.databasePath &&
|
||||||
|
dataDirectory == other.dataDirectory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
lib/core/bridge/user_discovery.dart
Normal file
25
lib/core/bridge/user_discovery.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// This file is automatically generated, so please do not edit it.
|
||||||
|
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||||
|
|
||||||
|
// ignore_for_file: invalid_use_of_internal_member, unused_import
|
||||||
|
|
||||||
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
|
||||||
|
import '../frb_generated.dart';
|
||||||
|
|
||||||
|
// 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`, `get_all_announced_users`, `get_announced_user_by_public_id`, `get_config`, `get_contact_version`, `get_other_promotions_by_public_id`, `get_own_promotions_after_version`, `get_share_for_contact`, `push_new_user_relation`, `push_own_promotion`, `set_contact_version`, `set_shares`, `store_other_promotion`, `update_config`
|
||||||
|
|
||||||
|
class UserDiscoveryDatabaseStore {
|
||||||
|
const UserDiscoveryDatabaseStore();
|
||||||
|
|
||||||
|
static Future<UserDiscoveryDatabaseStore> default_() => RustLib.instance.api
|
||||||
|
.crateBridgeUserDiscoveryUserDiscoveryDatabaseStoreDefault();
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is UserDiscoveryDatabaseStore && runtimeType == other.runtimeType;
|
||||||
|
}
|
||||||
|
|
@ -253,7 +253,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
|
throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
|
||||||
return OtherPromotion(
|
return OtherPromotion(
|
||||||
promotionId: dco_decode_u_32(arr[0]),
|
promotionId: dco_decode_u_32(arr[0]),
|
||||||
publicId: dco_decode_u_64(arr[1]),
|
publicId: dco_decode_i_64(arr[1]),
|
||||||
fromContactId: dco_decode_i_64(arr[2]),
|
fromContactId: dco_decode_i_64(arr[2]),
|
||||||
threshold: dco_decode_u_8(arr[3]),
|
threshold: dco_decode_u_8(arr[3]),
|
||||||
announcementShare: dco_decode_list_prim_u_8_strict(arr[4]),
|
announcementShare: dco_decode_list_prim_u_8_strict(arr[4]),
|
||||||
|
|
@ -265,10 +265,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
TwonlyConfig dco_decode_twonly_config(dynamic raw) {
|
TwonlyConfig dco_decode_twonly_config(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
final arr = raw as List<dynamic>;
|
final arr = raw as List<dynamic>;
|
||||||
if (arr.length != 1)
|
if (arr.length != 2)
|
||||||
throw Exception('unexpected arr length: expect 1 but see ${arr.length}');
|
throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
|
||||||
return TwonlyConfig(
|
return TwonlyConfig(
|
||||||
databasePath: dco_decode_String(arr[0]),
|
databasePath: dco_decode_String(arr[0]),
|
||||||
|
dataDirectory: dco_decode_String(arr[1]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,12 +279,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return raw as int;
|
return raw as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt dco_decode_u_64(dynamic raw) {
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
return dcoDecodeU64(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int dco_decode_u_8(dynamic raw) {
|
int dco_decode_u_8(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
|
@ -372,7 +367,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer) {
|
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
final var_promotionId = sse_decode_u_32(deserializer);
|
final var_promotionId = sse_decode_u_32(deserializer);
|
||||||
final var_publicId = sse_decode_u_64(deserializer);
|
final var_publicId = sse_decode_i_64(deserializer);
|
||||||
final var_fromContactId = sse_decode_i_64(deserializer);
|
final var_fromContactId = sse_decode_i_64(deserializer);
|
||||||
final var_threshold = sse_decode_u_8(deserializer);
|
final var_threshold = sse_decode_u_8(deserializer);
|
||||||
final var_announcementShare = sse_decode_list_prim_u_8_strict(deserializer);
|
final var_announcementShare = sse_decode_list_prim_u_8_strict(deserializer);
|
||||||
|
|
@ -393,7 +388,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer) {
|
TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
final var_databasePath = sse_decode_String(deserializer);
|
final var_databasePath = sse_decode_String(deserializer);
|
||||||
return TwonlyConfig(databasePath: var_databasePath);
|
final var_dataDirectory = sse_decode_String(deserializer);
|
||||||
|
return TwonlyConfig(
|
||||||
|
databasePath: var_databasePath,
|
||||||
|
dataDirectory: var_dataDirectory,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
|
|
@ -402,12 +401,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return deserializer.buffer.getUint32();
|
return deserializer.buffer.getUint32();
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt sse_decode_u_64(SseDeserializer deserializer) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
return deserializer.buffer.getBigUint64();
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_u_8(SseDeserializer deserializer) {
|
int sse_decode_u_8(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
@ -516,7 +509,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
) {
|
) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
sse_encode_u_32(self.promotionId, serializer);
|
sse_encode_u_32(self.promotionId, serializer);
|
||||||
sse_encode_u_64(self.publicId, serializer);
|
sse_encode_i_64(self.publicId, serializer);
|
||||||
sse_encode_i_64(self.fromContactId, serializer);
|
sse_encode_i_64(self.fromContactId, serializer);
|
||||||
sse_encode_u_8(self.threshold, serializer);
|
sse_encode_u_8(self.threshold, serializer);
|
||||||
sse_encode_list_prim_u_8_strict(self.announcementShare, serializer);
|
sse_encode_list_prim_u_8_strict(self.announcementShare, serializer);
|
||||||
|
|
@ -530,6 +523,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer) {
|
void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
sse_encode_String(self.databasePath, serializer);
|
sse_encode_String(self.databasePath, serializer);
|
||||||
|
sse_encode_String(self.dataDirectory, serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
|
|
@ -538,12 +532,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
serializer.buffer.putUint32(self);
|
serializer.buffer.putUint32(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_64(BigInt self, SseSerializer serializer) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
serializer.buffer.putBigUint64(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_u_8(int self, SseSerializer serializer) {
|
void sse_encode_u_8(int self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
int dco_decode_u_32(dynamic raw);
|
int dco_decode_u_32(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt dco_decode_u_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int dco_decode_u_8(dynamic raw);
|
int dco_decode_u_8(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -104,9 +101,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_u_32(SseDeserializer deserializer);
|
int sse_decode_u_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_u_8(SseDeserializer deserializer);
|
int sse_decode_u_8(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -173,9 +167,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
void sse_encode_u_32(int self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
void sse_encode_u_8(int self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
int dco_decode_u_32(dynamic raw);
|
int dco_decode_u_32(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt dco_decode_u_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int dco_decode_u_8(dynamic raw);
|
int dco_decode_u_8(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -105,9 +102,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_u_32(SseDeserializer deserializer);
|
int sse_decode_u_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_u_8(SseDeserializer deserializer);
|
int sse_decode_u_8(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -174,9 +168,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
void sse_encode_u_32(int self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
void sse_encode_u_8(int self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ void main() async {
|
||||||
await bridge.initializeTwonly(
|
await bridge.initializeTwonly(
|
||||||
config: bridge.TwonlyConfig(
|
config: bridge.TwonlyConfig(
|
||||||
databasePath: '$globalApplicationSupportDirectory/twonly.sqlite',
|
databasePath: '$globalApplicationSupportDirectory/twonly.sqlite',
|
||||||
|
dataDirectory: globalApplicationSupportDirectory,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
21
lib/src/database/daos/user_discovery.dao.dart
Normal file
21
lib/src/database/daos/user_discovery.dao.dart
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:twonly/src/database/tables/user_discovery.table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
|
part 'user_discovery.dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(
|
||||||
|
tables: [
|
||||||
|
UserDiscoveryAnnouncedUsers,
|
||||||
|
UserDiscoveryUserRelations,
|
||||||
|
UserDiscoveryOwnPromotions,
|
||||||
|
UserDiscoveryShares,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
|
||||||
|
with _$UserDiscoveryDaoMixin {
|
||||||
|
// this constructor is required so that the main database can create an instance
|
||||||
|
// of this object.
|
||||||
|
// ignore: matching_super_parameters
|
||||||
|
UserDiscoveryDao(super.db);
|
||||||
|
}
|
||||||
47
lib/src/database/daos/user_discovery.dao.g.dart
Normal file
47
lib/src/database/daos/user_discovery.dao.g.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'user_discovery.dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$UserDiscoveryDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
|
$UserDiscoveryAnnouncedUsersTable get userDiscoveryAnnouncedUsers =>
|
||||||
|
attachedDatabase.userDiscoveryAnnouncedUsers;
|
||||||
|
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||||
|
$UserDiscoveryUserRelationsTable get userDiscoveryUserRelations =>
|
||||||
|
attachedDatabase.userDiscoveryUserRelations;
|
||||||
|
$UserDiscoveryOwnPromotionsTable get userDiscoveryOwnPromotions =>
|
||||||
|
attachedDatabase.userDiscoveryOwnPromotions;
|
||||||
|
$UserDiscoverySharesTable get userDiscoveryShares =>
|
||||||
|
attachedDatabase.userDiscoveryShares;
|
||||||
|
UserDiscoveryDaoManager get managers => UserDiscoveryDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserDiscoveryDaoManager {
|
||||||
|
final _$UserDiscoveryDaoMixin _db;
|
||||||
|
UserDiscoveryDaoManager(this._db);
|
||||||
|
$$UserDiscoveryAnnouncedUsersTableTableManager
|
||||||
|
get userDiscoveryAnnouncedUsers =>
|
||||||
|
$$UserDiscoveryAnnouncedUsersTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.userDiscoveryAnnouncedUsers,
|
||||||
|
);
|
||||||
|
$$ContactsTableTableManager get contacts =>
|
||||||
|
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||||
|
$$UserDiscoveryUserRelationsTableTableManager
|
||||||
|
get userDiscoveryUserRelations =>
|
||||||
|
$$UserDiscoveryUserRelationsTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.userDiscoveryUserRelations,
|
||||||
|
);
|
||||||
|
$$UserDiscoveryOwnPromotionsTableTableManager
|
||||||
|
get userDiscoveryOwnPromotions =>
|
||||||
|
$$UserDiscoveryOwnPromotionsTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.userDiscoveryOwnPromotions,
|
||||||
|
);
|
||||||
|
$$UserDiscoverySharesTableTableManager get userDiscoveryShares =>
|
||||||
|
$$UserDiscoverySharesTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.userDiscoveryShares,
|
||||||
|
);
|
||||||
|
}
|
||||||
2655
lib/src/database/schemas/twonly_db/drift_schema_v12.json
Normal file
2655
lib/src/database/schemas/twonly_db/drift_schema_v12.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
@DataClassName('Contact')
|
||||||
class Contacts extends Table {
|
class Contacts extends Table {
|
||||||
IntColumn get userId => integer()();
|
IntColumn get userId => integer()();
|
||||||
|
|
||||||
|
|
@ -22,6 +23,37 @@ class Contacts extends Table {
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
// contact_versions: HashMap<UserID, Vec<u8>>,
|
||||||
|
BlobColumn get userDiscoveryVersion => blob().nullable()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {userId};
|
Set<Column> get primaryKey => {userId};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum VerificationType {
|
||||||
|
qr,
|
||||||
|
link,
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName('KeyVerification')
|
||||||
|
class KeyVerifications extends Table {
|
||||||
|
IntColumn get contactId => integer().references(
|
||||||
|
Contacts,
|
||||||
|
#userId,
|
||||||
|
onDelete: KeyAction.cascade,
|
||||||
|
)();
|
||||||
|
|
||||||
|
TextColumn get type => textEnum<VerificationType>()();
|
||||||
|
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {contactId};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName('VerificationToken')
|
||||||
|
class VerificationTokens extends Table {
|
||||||
|
IntColumn get tokenId => integer().autoIncrement()();
|
||||||
|
BlobColumn get token => blob()();
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,39 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
|
|
||||||
// contact_versions: HashMap<UserID, Vec<u8>>,
|
|
||||||
// -> New Column in Contacts
|
|
||||||
|
|
||||||
// config: Option<Vec<u8>>,
|
// config: Option<Vec<u8>>,
|
||||||
|
|
||||||
// announced_users: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>,
|
// announced_users: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>,
|
||||||
|
@DataClassName('UserDiscoveryAnnouncedUser')
|
||||||
|
class UserDiscoveryAnnouncedUsers extends Table {
|
||||||
|
IntColumn get announcedUserId => integer()();
|
||||||
|
BlobColumn get announcedPublicKey => blob()();
|
||||||
|
IntColumn get publicId => integer().unique()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {announcedUserId};
|
||||||
|
}
|
||||||
|
|
||||||
|
// announced_users: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>,
|
||||||
|
@DataClassName('UserDiscoveryUserRelation')
|
||||||
|
class UserDiscoveryUserRelations extends Table {
|
||||||
|
IntColumn get announcedUserId => integer().references(
|
||||||
|
UserDiscoveryAnnouncedUsers,
|
||||||
|
#announcedUserId,
|
||||||
|
onDelete: KeyAction.cascade,
|
||||||
|
)();
|
||||||
|
|
||||||
|
IntColumn get fromContactId => integer().references(
|
||||||
|
Contacts,
|
||||||
|
#userId,
|
||||||
|
onDelete: KeyAction.cascade,
|
||||||
|
)();
|
||||||
|
|
||||||
|
DateTimeColumn get publicKeyVerifiedTimestamp => dateTime().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {announcedUserId, fromContactId};
|
||||||
|
}
|
||||||
|
|
||||||
// own_promotions: Vec<(UserID, Vec<u8>)>,
|
// own_promotions: Vec<(UserID, Vec<u8>)>,
|
||||||
@DataClassName('UserDiscoveryOwnPromotion')
|
@DataClassName('UserDiscoveryOwnPromotion')
|
||||||
|
|
@ -17,21 +44,26 @@ class UserDiscoveryOwnPromotions extends Table {
|
||||||
#userId,
|
#userId,
|
||||||
onDelete: KeyAction.cascade,
|
onDelete: KeyAction.cascade,
|
||||||
)();
|
)();
|
||||||
|
|
||||||
BlobColumn get promotion => blob()();
|
BlobColumn get promotion => blob()();
|
||||||
}
|
}
|
||||||
|
|
||||||
// other_promotions: Vec<OtherPromotion>,
|
// other_promotions: Vec<OtherPromotion>,
|
||||||
@DataClassName('UserDiscoveryOtherPromotion')
|
@DataClassName('UserDiscoveryOtherPromotion')
|
||||||
class UserDiscoveryOtherPromotions extends Table {
|
class UserDiscoveryOtherPromotions extends Table {
|
||||||
IntColumn get versionId => integer().autoIncrement()();
|
IntColumn get fromContactId => integer().references(
|
||||||
IntColumn get contactId => integer().references(
|
|
||||||
Contacts,
|
Contacts,
|
||||||
#userId,
|
#userId,
|
||||||
onDelete: KeyAction.cascade,
|
onDelete: KeyAction.cascade,
|
||||||
)();
|
)();
|
||||||
|
|
||||||
BlobColumn get promotion => blob()();
|
IntColumn get promotionId => integer()();
|
||||||
|
IntColumn get publicId => integer()();
|
||||||
|
IntColumn get threshold => integer()();
|
||||||
|
BlobColumn get announcementShare => blob()();
|
||||||
|
DateTimeColumn get publicKeyVerifiedTimestamp => dateTime().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {fromContactId, promotionId};
|
||||||
}
|
}
|
||||||
|
|
||||||
// unused_shares: Vec<Vec<u8>>,
|
// unused_shares: Vec<Vec<u8>>,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/messages.dao.dart';
|
import 'package:twonly/src/database/daos/messages.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/receipts.dao.dart';
|
import 'package:twonly/src/database/daos/receipts.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/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
@ -19,6 +20,7 @@ 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_pre_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_sender_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_session_store.table.dart';
|
||||||
|
import 'package:twonly/src/database/tables/user_discovery.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.steps.dart';
|
import 'package:twonly/src/database/twonly.db.steps.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -42,6 +44,13 @@ part 'twonly.db.g.dart';
|
||||||
SignalSessionStores,
|
SignalSessionStores,
|
||||||
MessageActions,
|
MessageActions,
|
||||||
GroupHistories,
|
GroupHistories,
|
||||||
|
KeyVerifications,
|
||||||
|
VerificationTokens,
|
||||||
|
UserDiscoveryAnnouncedUsers,
|
||||||
|
UserDiscoveryUserRelations,
|
||||||
|
UserDiscoveryOtherPromotions,
|
||||||
|
UserDiscoveryOwnPromotions,
|
||||||
|
UserDiscoveryShares,
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
MessagesDao,
|
MessagesDao,
|
||||||
|
|
@ -50,6 +59,7 @@ part 'twonly.db.g.dart';
|
||||||
GroupsDao,
|
GroupsDao,
|
||||||
ReactionsDao,
|
ReactionsDao,
|
||||||
MediaFilesDao,
|
MediaFilesDao,
|
||||||
|
UserDiscoveryDao,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class TwonlyDB extends _$TwonlyDB {
|
class TwonlyDB extends _$TwonlyDB {
|
||||||
|
|
@ -62,7 +72,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 11;
|
int get schemaVersion => 12;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -158,6 +168,19 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
schema.groupMembers.lastTypeIndicator,
|
schema.groupMembers.lastTypeIndicator,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
from11To12: (m, schema) async {
|
||||||
|
await m.createTable(schema.verificationTokens);
|
||||||
|
await m.createTable(schema.keyVerifications);
|
||||||
|
await m.createTable(schema.userDiscoveryAnnouncedUsers);
|
||||||
|
await m.createTable(schema.userDiscoveryOwnPromotions);
|
||||||
|
await m.createTable(schema.userDiscoveryOtherPromotions);
|
||||||
|
await m.createTable(schema.userDiscoveryShares);
|
||||||
|
await m.createTable(schema.userDiscoveryUserRelations);
|
||||||
|
await m.addColumn(
|
||||||
|
schema.contacts,
|
||||||
|
schema.contacts.userDiscoveryVersion,
|
||||||
|
);
|
||||||
|
},
|
||||||
)(m, from, to);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -5823,6 +5823,662 @@ i1.GeneratedColumn<int> _column_210(String aliasedName) =>
|
||||||
type: i1.DriftSqlType.int,
|
type: i1.DriftSqlType.int,
|
||||||
$customConstraints: 'NULL',
|
$customConstraints: 'NULL',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final class Schema12 extends i0.VersionedSchema {
|
||||||
|
Schema12({required super.database}) : super(version: 12);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
contacts,
|
||||||
|
groups,
|
||||||
|
mediaFiles,
|
||||||
|
messages,
|
||||||
|
messageHistories,
|
||||||
|
reactions,
|
||||||
|
groupMembers,
|
||||||
|
receipts,
|
||||||
|
receivedReceipts,
|
||||||
|
signalIdentityKeyStores,
|
||||||
|
signalPreKeyStores,
|
||||||
|
signalSenderKeyStores,
|
||||||
|
signalSessionStores,
|
||||||
|
messageActions,
|
||||||
|
groupHistories,
|
||||||
|
keyVerifications,
|
||||||
|
verificationTokens,
|
||||||
|
userDiscoveryAnnouncedUsers,
|
||||||
|
userDiscoveryUserRelations,
|
||||||
|
userDiscoveryOtherPromotions,
|
||||||
|
userDiscoveryOwnPromotions,
|
||||||
|
userDiscoveryShares,
|
||||||
|
];
|
||||||
|
late final Shape39 contacts = Shape39(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'contacts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_106,
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_211,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape23 groups = Shape23(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'groups',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
_column_130,
|
||||||
|
_column_131,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_118,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
_column_138,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
_column_142,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape36 mediaFiles = Shape36(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'media_files',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(media_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
_column_146,
|
||||||
|
_column_147,
|
||||||
|
_column_148,
|
||||||
|
_column_149,
|
||||||
|
_column_207,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
_column_153,
|
||||||
|
_column_154,
|
||||||
|
_column_155,
|
||||||
|
_column_156,
|
||||||
|
_column_157,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape25 messages = Shape25(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_144,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_165,
|
||||||
|
_column_153,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_169,
|
||||||
|
_column_118,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape26 messageHistories = Shape26(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_161,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape27 reactions = Shape27(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'reactions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
||||||
|
columns: [_column_174, _column_176, _column_177, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 groupMembers = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_178,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_209,
|
||||||
|
_column_210,
|
||||||
|
_column_181,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape37 receipts = Shape37(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
_column_208,
|
||||||
|
_column_187,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape30 receivedReceipts = Shape30(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'received_receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [_column_182, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape31 signalIdentityKeyStores = Shape31(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_identity_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_194, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 signalPreKeyStores = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_pre_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
||||||
|
columns: [_column_195, _column_196, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 signalSenderKeyStores = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_sender_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
||||||
|
columns: [_column_197, _column_198],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape33 signalSessionStores = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_session_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_199, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape34 messageActions = Shape34(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_actions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
||||||
|
columns: [_column_174, _column_183, _column_144, _column_200],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 groupHistories = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_201,
|
||||||
|
_column_158,
|
||||||
|
_column_202,
|
||||||
|
_column_203,
|
||||||
|
_column_204,
|
||||||
|
_column_205,
|
||||||
|
_column_206,
|
||||||
|
_column_144,
|
||||||
|
_column_200,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape40 keyVerifications = Shape40(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'key_verifications',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(contact_id)'],
|
||||||
|
columns: [_column_183, _column_144, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 verificationTokens = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'verification_tokens',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_212, _column_213, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape42 userDiscoveryAnnouncedUsers = Shape42(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_announced_users',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
|
||||||
|
columns: [_column_214, _column_215, _column_216],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 userDiscoveryUserRelations = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_user_relations',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
|
||||||
|
columns: [_column_217, _column_218, _column_219],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 userDiscoveryOtherPromotions = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_other_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(from_contact_id, promotion_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_218,
|
||||||
|
_column_220,
|
||||||
|
_column_221,
|
||||||
|
_column_222,
|
||||||
|
_column_223,
|
||||||
|
_column_219,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 userDiscoveryOwnPromotions = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_own_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_224, _column_183, _column_225],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 userDiscoveryShares = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_shares',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_226, _column_227, _column_175],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape39 extends i0.VersionedTable {
|
||||||
|
Shape39({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get userId =>
|
||||||
|
columnsByName['user_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get username =>
|
||||||
|
columnsByName['username']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get displayName =>
|
||||||
|
columnsByName['display_name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get nickName =>
|
||||||
|
columnsByName['nick_name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get avatarSvgCompressed =>
|
||||||
|
columnsByName['avatar_svg_compressed']!
|
||||||
|
as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get senderProfileCounter =>
|
||||||
|
columnsByName['sender_profile_counter']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get accepted =>
|
||||||
|
columnsByName['accepted']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get deletedByUser =>
|
||||||
|
columnsByName['deleted_by_user']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get requested =>
|
||||||
|
columnsByName['requested']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get blocked =>
|
||||||
|
columnsByName['blocked']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get verified =>
|
||||||
|
columnsByName['verified']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get accountDeleted =>
|
||||||
|
columnsByName['account_deleted']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get userDiscoveryVersion =>
|
||||||
|
columnsByName['user_discovery_version']!
|
||||||
|
as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_211(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'user_discovery_version',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NULL',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape40 extends i0.VersionedTable {
|
||||||
|
Shape40({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get contactId =>
|
||||||
|
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape41 extends i0.VersionedTable {
|
||||||
|
Shape41({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get tokenId =>
|
||||||
|
columnsByName['token_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get token =>
|
||||||
|
columnsByName['token']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_212(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'token_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_213(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'token',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape42 extends i0.VersionedTable {
|
||||||
|
Shape42({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get announcedUserId =>
|
||||||
|
columnsByName['announced_user_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get announcedPublicKey =>
|
||||||
|
columnsByName['announced_public_key']!
|
||||||
|
as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get publicId =>
|
||||||
|
columnsByName['public_id']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_214(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'announced_user_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_215(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'announced_public_key',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_216(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'public_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL UNIQUE',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape43 extends i0.VersionedTable {
|
||||||
|
Shape43({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get announcedUserId =>
|
||||||
|
columnsByName['announced_user_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get fromContactId =>
|
||||||
|
columnsByName['from_contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get publicKeyVerifiedTimestamp =>
|
||||||
|
columnsByName['public_key_verified_timestamp']!
|
||||||
|
as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_217(
|
||||||
|
String aliasedName,
|
||||||
|
) => i1.GeneratedColumn<int>(
|
||||||
|
'announced_user_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints:
|
||||||
|
'NOT NULL REFERENCES user_discovery_announced_users(announced_user_id)ON DELETE CASCADE',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_218(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'from_contact_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints:
|
||||||
|
'NOT NULL REFERENCES contacts(user_id)ON DELETE CASCADE',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_219(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'public_key_verified_timestamp',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NULL',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape44 extends i0.VersionedTable {
|
||||||
|
Shape44({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get fromContactId =>
|
||||||
|
columnsByName['from_contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get promotionId =>
|
||||||
|
columnsByName['promotion_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get publicId =>
|
||||||
|
columnsByName['public_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get threshold =>
|
||||||
|
columnsByName['threshold']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get announcementShare =>
|
||||||
|
columnsByName['announcement_share']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get publicKeyVerifiedTimestamp =>
|
||||||
|
columnsByName['public_key_verified_timestamp']!
|
||||||
|
as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_220(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'promotion_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_221(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'public_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_222(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'threshold',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_223(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'announcement_share',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape45 extends i0.VersionedTable {
|
||||||
|
Shape45({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get versionId =>
|
||||||
|
columnsByName['version_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get contactId =>
|
||||||
|
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get promotion =>
|
||||||
|
columnsByName['promotion']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_224(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'version_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_225(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'promotion',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape46 extends i0.VersionedTable {
|
||||||
|
Shape46({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get shareId =>
|
||||||
|
columnsByName['share_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get share =>
|
||||||
|
columnsByName['share']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get contactId =>
|
||||||
|
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_226(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'share_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_227(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'share',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.blob,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
|
@ -5834,6 +6490,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -5887,6 +6544,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from10To11(migrator, schema);
|
await from10To11(migrator, schema);
|
||||||
return 11;
|
return 11;
|
||||||
|
case 11:
|
||||||
|
final schema = Schema12(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from11To12(migrator, schema);
|
||||||
|
return 12;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -5904,6 +6566,7 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
|
|
@ -5916,5 +6579,6 @@ i1.OnUpgrade stepByStep({
|
||||||
from8To9: from8To9,
|
from8To9: from8To9,
|
||||||
from9To10: from9To10,
|
from9To10: from9To10,
|
||||||
from10To11: from10To11,
|
from10To11: from10To11,
|
||||||
|
from11To12: from11To12,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
87
rust/Cargo.lock
generated
87
rust/Cargo.lock
generated
|
|
@ -90,15 +90,6 @@ version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic-polyfill"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -244,15 +235,6 @@ version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
|
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cobs"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
@ -323,12 +305,6 @@ version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "critical-section"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
|
|
@ -466,18 +442,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-io"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-io"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
|
@ -771,15 +735,6 @@ version = "0.32.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
|
@ -823,20 +778,6 @@ dependencies = [
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.7.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-polyfill",
|
|
||||||
"hash32",
|
|
||||||
"rustc_version",
|
|
||||||
"serde",
|
|
||||||
"spin",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -1390,19 +1331,6 @@ version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "postcard"
|
|
||||||
version = "1.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
|
||||||
dependencies = [
|
|
||||||
"cobs",
|
|
||||||
"embedded-io 0.4.0",
|
|
||||||
"embedded-io 0.6.1",
|
|
||||||
"heapless",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
@ -1508,14 +1436,16 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"blahaj",
|
"blahaj",
|
||||||
"hmac 0.13.0",
|
"hmac 0.13.0",
|
||||||
"postcard",
|
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1653,11 +1583,13 @@ name = "rust_lib_twonly"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
|
"parking_lot",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"protocols",
|
"protocols",
|
||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -1669,15 +1601,6 @@ version = "0.1.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
|
||||||
dependencies = [
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ tokio = { version = "1.44", features = ["full"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
protocols = { path = "../protocols" }
|
protocols = { path = "../protocols" }
|
||||||
|
parking_lot = "0.12.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
|
tempfile = "3.27.0"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use protocols::user_discovery::error::UserDiscoveryError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, TwonlyError>;
|
pub type Result<T> = core::result::Result<T, TwonlyError>;
|
||||||
|
|
@ -8,6 +9,12 @@ pub enum TwonlyError {
|
||||||
Initialization,
|
Initialization,
|
||||||
#[error("Could not find the given database")]
|
#[error("Could not find the given database")]
|
||||||
DatabaseNotFound,
|
DatabaseNotFound,
|
||||||
#[error("sqlx error")]
|
#[error("{0}")]
|
||||||
SqliteError(#[from] sqlx::Error),
|
SqliteError(#[from] sqlx::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TwonlyError> for UserDiscoveryError {
|
||||||
|
fn from(error: TwonlyError) -> Self {
|
||||||
|
UserDiscoveryError::Store(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
|
#![allow(unexpected_cfgs)]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod user_discovery;
|
mod user_discovery_utils;
|
||||||
|
use crate::bridge::user_discovery_utils::UserDiscoveryUtilsFlutter;
|
||||||
use crate::database::contact::Contact;
|
use crate::database::contact::Contact;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
|
use crate::user_discovery_store::UserDiscoveryDatabaseStore;
|
||||||
|
use crate::utils::Shared;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use error::TwonlyError;
|
use error::TwonlyError;
|
||||||
use flutter_rust_bridge::frb;
|
use flutter_rust_bridge::frb;
|
||||||
|
use protocols::user_discovery::UserDiscovery;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
use protocols::user_discovery::traits::OtherPromotion;
|
pub use protocols::user_discovery::traits::OtherPromotion;
|
||||||
|
|
||||||
#[frb(mirror(OtherPromotion))]
|
#[frb(mirror(OtherPromotion))]
|
||||||
pub struct _OtherPromotion {
|
pub struct _OtherPromotion {
|
||||||
pub promotion_id: u32,
|
pub promotion_id: u32,
|
||||||
pub public_id: u64,
|
pub public_id: i64,
|
||||||
pub from_contact_id: i64,
|
pub from_contact_id: i64,
|
||||||
pub threshold: u8,
|
pub threshold: u8,
|
||||||
pub announcement_share: Vec<u8>,
|
pub announcement_share: Vec<u8>,
|
||||||
|
|
@ -22,26 +27,33 @@ pub struct _OtherPromotion {
|
||||||
|
|
||||||
pub struct TwonlyConfig {
|
pub struct TwonlyConfig {
|
||||||
pub database_path: String,
|
pub database_path: String,
|
||||||
|
pub data_directory: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Twonly {
|
pub(crate) struct Twonly {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) config: TwonlyConfig,
|
pub(crate) config: TwonlyConfig,
|
||||||
pub(crate) database: Arc<Database>,
|
pub(crate) database: Arc<Database>,
|
||||||
|
pub(crate) user_discovery:
|
||||||
|
Shared<Option<UserDiscovery<UserDiscoveryDatabaseStore, UserDiscoveryUtilsFlutter>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLOBAL_TWONLY: OnceCell<Twonly> = OnceCell::const_new();
|
static GLOBAL_TWONLY: OnceCell<Twonly> = OnceCell::const_new();
|
||||||
|
|
||||||
fn get_instance() -> Result<&'static Twonly> {
|
pub(crate) fn get_workspace() -> Result<&'static Twonly> {
|
||||||
GLOBAL_TWONLY.get().ok_or(TwonlyError::Initialization)
|
GLOBAL_TWONLY.get().ok_or(TwonlyError::Initialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
||||||
println!("initialized twonly");
|
tracing::debug!("Initialized twonly workspace.");
|
||||||
let twonly_res: Result<&'static Twonly> = GLOBAL_TWONLY
|
let twonly_res: Result<&'static Twonly> = GLOBAL_TWONLY
|
||||||
.get_or_try_init(|| async {
|
.get_or_try_init(|| async {
|
||||||
let database = Arc::new(Database::new(&config.database_path).await?);
|
let database = Arc::new(Database::new(&config.database_path).await?);
|
||||||
Ok(Twonly { config, database })
|
Ok(Twonly {
|
||||||
|
config,
|
||||||
|
database,
|
||||||
|
user_discovery: Shared::default(),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -51,7 +63,7 @@ pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_contacts() -> Result<Vec<Contact>> {
|
pub async fn get_all_contacts() -> Result<Vec<Contact>> {
|
||||||
let twonly = get_instance()?;
|
let twonly = get_workspace()?;
|
||||||
Contact::get_all_contacts(twonly.database.as_ref()).await
|
Contact::get_all_contacts(twonly.database.as_ref()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,35 +73,90 @@ pub fn load_promotions() -> OtherPromotion {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||||
|
use tempfile::{NamedTempFile, TempDir};
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
use crate::{database::Database, utils::Shared};
|
||||||
|
|
||||||
use super::error::Result;
|
use super::error::Result;
|
||||||
use super::Twonly;
|
use super::Twonly;
|
||||||
use std::path::PathBuf;
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::{get_instance, initialize_twonly, TwonlyConfig};
|
use super::{get_workspace, initialize_twonly, TwonlyConfig};
|
||||||
|
|
||||||
pub(crate) async fn initialize_twonly_for_testing() -> Result<&'static Twonly> {
|
static TWONLY_TESTING: [OnceCell<Twonly>; 10] = [
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
OnceCell::const_new(),
|
||||||
|
];
|
||||||
|
|
||||||
|
static TWONLY_TESTING_INDEX: OnceCell<Arc<Mutex<usize>>> = OnceCell::const_new();
|
||||||
|
|
||||||
|
pub(crate) async fn initialize_twonly_for_testing(use_global: bool) -> Result<&'static Twonly> {
|
||||||
let default_twonly_database = PathBuf::from("tests/testing.db");
|
let default_twonly_database = PathBuf::from("tests/testing.db");
|
||||||
|
|
||||||
if !default_twonly_database.is_file() {
|
if !default_twonly_database.is_file() {
|
||||||
panic!("{} not found!", default_twonly_database.display())
|
panic!("{} not found!", default_twonly_database.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
let copied_twonly_database = default_twonly_database
|
let temp_file = NamedTempFile::new().unwrap().path().to_owned();
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.join("tmp_testing.db");
|
|
||||||
if copied_twonly_database.exists() {
|
|
||||||
std::fs::remove_file(&copied_twonly_database).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::copy(default_twonly_database, &copied_twonly_database).unwrap();
|
tracing::info!("Crated db copy: {}", temp_file.display());
|
||||||
|
|
||||||
initialize_twonly(TwonlyConfig {
|
let conn = SqlitePoolOptions::new()
|
||||||
database_path: copied_twonly_database.display().to_string(),
|
.connect_with(
|
||||||
})
|
format!("sqlite://{}", default_twonly_database.display())
|
||||||
|
.parse::<SqliteConnectOptions>()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
get_instance()
|
let path_str = temp_file.display().to_string();
|
||||||
|
sqlx::query("VACUUM INTO $1")
|
||||||
|
.bind(path_str)
|
||||||
|
.execute(&conn)
|
||||||
|
.await
|
||||||
|
.expect("Failed to backup database");
|
||||||
|
|
||||||
|
let tmp_dir = TempDir::new().unwrap().path().to_owned();
|
||||||
|
std::fs::create_dir_all(&tmp_dir).unwrap();
|
||||||
|
let config = TwonlyConfig {
|
||||||
|
database_path: temp_file.display().to_string(),
|
||||||
|
data_directory: tmp_dir.to_str().unwrap().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if use_global {
|
||||||
|
initialize_twonly(config).await.unwrap();
|
||||||
|
|
||||||
|
get_workspace()
|
||||||
|
} else {
|
||||||
|
let index = TWONLY_TESTING_INDEX
|
||||||
|
.get_or_init(|| async { Arc::default() })
|
||||||
|
.await;
|
||||||
|
let mut index = index.lock().await;
|
||||||
|
let res: Result<&'static Twonly> = TWONLY_TESTING[*index]
|
||||||
|
.get_or_try_init(|| async {
|
||||||
|
let database = Arc::new(Database::new(&config.database_path).await?);
|
||||||
|
Ok(Twonly {
|
||||||
|
config,
|
||||||
|
database,
|
||||||
|
user_discovery: Shared::default(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tracing::debug!("TWONLY_TESTING_INDEX: {index}");
|
||||||
|
*index += 1;
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
|
||||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
|
||||||
use protocols::user_discovery::UserID;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
struct UserDiscoveryDatabaseStore {}
|
|
||||||
|
|
||||||
impl UserDiscoveryStore for UserDiscoveryDatabaseStore {
|
|
||||||
fn get_config(&self) -> Result<Vec<u8>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_config(&self, update: Vec<u8>) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_own_promotion(
|
|
||||||
&self,
|
|
||||||
contact_id: UserID,
|
|
||||||
version: u32,
|
|
||||||
promotion: Vec<u8>,
|
|
||||||
) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_other_promotions_by_public_id(&self, public_id: u64) -> Vec<OtherPromotion> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_announced_user_by_public_id(&self, public_id: u64) -> Result<Option<AnnouncedUser>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_new_user_relation(
|
|
||||||
&self,
|
|
||||||
from_contact_id: UserID,
|
|
||||||
announced_user: AnnouncedUser,
|
|
||||||
public_key_verified_timestamp: Option<i64>,
|
|
||||||
) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_announced_users(
|
|
||||||
&self,
|
|
||||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
27
rust/core/src/bridge/user_discovery_utils.rs
Normal file
27
rust/core/src/bridge/user_discovery_utils.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use protocols::user_discovery::error::Result;
|
||||||
|
use protocols::user_discovery::traits::UserDiscoveryUtils;
|
||||||
|
|
||||||
|
pub(crate) struct UserDiscoveryUtilsFlutter {}
|
||||||
|
|
||||||
|
impl UserDiscoveryUtils for UserDiscoveryUtilsFlutter {
|
||||||
|
async fn sign_data(&self, input_data: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_signature(
|
||||||
|
&self,
|
||||||
|
input_data: &[u8],
|
||||||
|
pubkey: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Result<bool> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_stored_pubkey(
|
||||||
|
&self,
|
||||||
|
from_contact_id: protocols::user_discovery::UserID,
|
||||||
|
pubkey: &[u8],
|
||||||
|
) -> Result<bool> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,11 +43,11 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_all_contacts() {
|
async fn test_get_all_contacts() {
|
||||||
let twonly = initialize_twonly_for_testing().await.unwrap();
|
let twonly = initialize_twonly_for_testing(true).await.unwrap();
|
||||||
|
|
||||||
let contacts = Contact::get_all_contacts(&twonly.database).await.unwrap();
|
let contacts = Contact::get_all_contacts(&twonly.database).await.unwrap();
|
||||||
|
|
||||||
let users = vec!["alice", "bob", "charlie", "diana", "eve", "frank", "grace"];
|
let users = vec!["alice", "bob", "charlie", "david", "frank"];
|
||||||
|
|
||||||
assert_eq!(contacts.len(), users.len());
|
assert_eq!(contacts.len(), users.len());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ fn wire__crate__bridge__load_promotions_impl(
|
||||||
const _: fn() = || {
|
const _: fn() = || {
|
||||||
let OtherPromotion = None::<crate::bridge::OtherPromotion>.unwrap();
|
let OtherPromotion = None::<crate::bridge::OtherPromotion>.unwrap();
|
||||||
let _: u32 = OtherPromotion.promotion_id;
|
let _: u32 = OtherPromotion.promotion_id;
|
||||||
let _: u64 = OtherPromotion.public_id;
|
let _: i64 = OtherPromotion.public_id;
|
||||||
let _: i64 = OtherPromotion.from_contact_id;
|
let _: i64 = OtherPromotion.from_contact_id;
|
||||||
let _: u8 = OtherPromotion.threshold;
|
let _: u8 = OtherPromotion.threshold;
|
||||||
let _: Vec<u8> = OtherPromotion.announcement_share;
|
let _: Vec<u8> = OtherPromotion.announcement_share;
|
||||||
|
|
@ -241,7 +241,7 @@ impl SseDecode for crate::bridge::OtherPromotion {
|
||||||
// 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 {
|
||||||
let mut var_promotionId = <u32>::sse_decode(deserializer);
|
let mut var_promotionId = <u32>::sse_decode(deserializer);
|
||||||
let mut var_publicId = <u64>::sse_decode(deserializer);
|
let mut var_publicId = <i64>::sse_decode(deserializer);
|
||||||
let mut var_fromContactId = <i64>::sse_decode(deserializer);
|
let mut var_fromContactId = <i64>::sse_decode(deserializer);
|
||||||
let mut var_threshold = <u8>::sse_decode(deserializer);
|
let mut var_threshold = <u8>::sse_decode(deserializer);
|
||||||
let mut var_announcementShare = <Vec<u8>>::sse_decode(deserializer);
|
let mut var_announcementShare = <Vec<u8>>::sse_decode(deserializer);
|
||||||
|
|
@ -261,8 +261,10 @@ impl SseDecode for crate::bridge::TwonlyConfig {
|
||||||
// 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 {
|
||||||
let mut var_databasePath = <String>::sse_decode(deserializer);
|
let mut var_databasePath = <String>::sse_decode(deserializer);
|
||||||
|
let mut var_dataDirectory = <String>::sse_decode(deserializer);
|
||||||
return crate::bridge::TwonlyConfig {
|
return crate::bridge::TwonlyConfig {
|
||||||
database_path: var_databasePath,
|
database_path: var_databasePath,
|
||||||
|
data_directory: var_dataDirectory,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,13 +276,6 @@ impl SseDecode for u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for u64 {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
|
||||||
deserializer.cursor.read_u64::<NativeEndian>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for u8 {
|
impl SseDecode for u8 {
|
||||||
// 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 {
|
||||||
|
|
@ -389,7 +384,11 @@ impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<crate::bridge::OtherPromotion>
|
||||||
// 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::TwonlyConfig {
|
impl flutter_rust_bridge::IntoDart for crate::bridge::TwonlyConfig {
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
[self.database_path.into_into_dart().into_dart()].into_dart()
|
[
|
||||||
|
self.database_path.into_into_dart().into_dart(),
|
||||||
|
self.data_directory.into_into_dart().into_dart(),
|
||||||
|
]
|
||||||
|
.into_dart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::TwonlyConfig {}
|
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::TwonlyConfig {}
|
||||||
|
|
@ -464,7 +463,7 @@ impl SseEncode for crate::bridge::OtherPromotion {
|
||||||
// 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) {
|
||||||
<u32>::sse_encode(self.promotion_id, serializer);
|
<u32>::sse_encode(self.promotion_id, serializer);
|
||||||
<u64>::sse_encode(self.public_id, serializer);
|
<i64>::sse_encode(self.public_id, serializer);
|
||||||
<i64>::sse_encode(self.from_contact_id, serializer);
|
<i64>::sse_encode(self.from_contact_id, serializer);
|
||||||
<u8>::sse_encode(self.threshold, serializer);
|
<u8>::sse_encode(self.threshold, serializer);
|
||||||
<Vec<u8>>::sse_encode(self.announcement_share, serializer);
|
<Vec<u8>>::sse_encode(self.announcement_share, serializer);
|
||||||
|
|
@ -476,6 +475,7 @@ impl SseEncode for crate::bridge::TwonlyConfig {
|
||||||
// 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) {
|
||||||
<String>::sse_encode(self.database_path, serializer);
|
<String>::sse_encode(self.database_path, serializer);
|
||||||
|
<String>::sse_encode(self.data_directory, serializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,13 +486,6 @@ impl SseEncode for u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for u64 {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
serializer.cursor.write_u64::<NativeEndian>(self).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for u8 {
|
impl SseEncode for u8 {
|
||||||
// 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) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
pub mod bridge;
|
pub mod bridge;
|
||||||
mod database;
|
mod database;
|
||||||
mod frb_generated;
|
mod frb_generated;
|
||||||
|
mod user_discovery_store;
|
||||||
|
mod utils;
|
||||||
|
|
|
||||||
349
rust/core/src/user_discovery_store.rs
Normal file
349
rust/core/src/user_discovery_store.rs
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
||||||
|
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||||
|
use protocols::user_discovery::UserID;
|
||||||
|
use sqlx::{QueryBuilder, Row, Sqlite, Transaction};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::bridge::error::TwonlyError;
|
||||||
|
use crate::bridge::{get_workspace, Twonly};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct UserDiscoveryDatabaseStore {
|
||||||
|
ws: Arc<&'static Twonly>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDiscoveryStore for UserDiscoveryDatabaseStore {
|
||||||
|
async fn new() -> Self {
|
||||||
|
#[cfg(test)]
|
||||||
|
return Self {
|
||||||
|
ws: Arc::new(
|
||||||
|
crate::bridge::tests::initialize_twonly_for_testing(false)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Self {
|
||||||
|
ws: Arc::new(get_workspace().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn get_config(&self) -> Result<String> {
|
||||||
|
let config_path =
|
||||||
|
PathBuf::from(&self.ws.config.data_directory).join("user_discovery_config.json");
|
||||||
|
|
||||||
|
if !config_path.is_file() {
|
||||||
|
return Err(UserDiscoveryError::NotInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Loading Config from {}", config_path.display());
|
||||||
|
Ok(std::fs::read_to_string(&config_path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_config(&self, update: String) -> Result<()> {
|
||||||
|
tracing::debug!("Updating configuration file.");
|
||||||
|
let config_path =
|
||||||
|
PathBuf::from(&self.ws.config.data_directory).join("user_discovery_config.json");
|
||||||
|
std::fs::write(config_path, &update)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
||||||
|
let mut query_builder: QueryBuilder<Sqlite> =
|
||||||
|
QueryBuilder::new("INSERT INTO user_discovery_shares (share) ");
|
||||||
|
|
||||||
|
query_builder.push_values(shares, |mut b, share| {
|
||||||
|
b.push_bind(share);
|
||||||
|
});
|
||||||
|
|
||||||
|
query_builder
|
||||||
|
.build()
|
||||||
|
.execute(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>> {
|
||||||
|
let mut tx: Transaction<'_, Sqlite> = self
|
||||||
|
.ws
|
||||||
|
.database
|
||||||
|
.pool
|
||||||
|
.begin()
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
// 1. Check if the user already has a share assigned
|
||||||
|
let existing: Option<Vec<u8>> =
|
||||||
|
sqlx::query_scalar("SELECT share FROM user_discovery_shares WHERE contact_id = ?")
|
||||||
|
.bind(contact_id)
|
||||||
|
.fetch_optional(&mut *tx)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
if let Some(share) = existing {
|
||||||
|
tx.commit().await.map_err(TwonlyError::from)?;
|
||||||
|
return Ok(share);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. No share found. Try to assign an available one (where contact_id is NULL)
|
||||||
|
let rows_affected = sqlx::query(
|
||||||
|
"UPDATE user_discovery_shares
|
||||||
|
SET contact_id = ?
|
||||||
|
WHERE share_id = (
|
||||||
|
SELECT share_id FROM user_discovery_shares
|
||||||
|
WHERE contact_id IS NULL
|
||||||
|
LIMIT 1
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.bind(contact_id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?
|
||||||
|
.rows_affected();
|
||||||
|
|
||||||
|
if rows_affected == 0 {
|
||||||
|
return Err(UserDiscoveryError::NoSharesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Retrieve the newly assigned share
|
||||||
|
let assigned_share: Vec<u8> =
|
||||||
|
sqlx::query_scalar("SELECT share FROM user_discovery_shares WHERE contact_id = ?")
|
||||||
|
.bind(contact_id)
|
||||||
|
.fetch_one(&mut *tx)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
tx.commit().await.map_err(TwonlyError::from)?;
|
||||||
|
Ok(assigned_share)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn push_own_promotion(
|
||||||
|
&self,
|
||||||
|
contact_id: UserID,
|
||||||
|
version: u32,
|
||||||
|
promotion: Vec<u8>,
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO user_discovery_own_promotions (contact_id, promotion, version_id)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(contact_id)
|
||||||
|
.bind(promotion)
|
||||||
|
.bind(version as i64) // SQLite integers are usually i32/i64
|
||||||
|
.execute(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||||
|
let promotions: Vec<Vec<u8>> = sqlx::query_scalar(
|
||||||
|
"SELECT promotion FROM user_discovery_own_promotions
|
||||||
|
WHERE version_id > ?
|
||||||
|
ORDER BY version_id ASC",
|
||||||
|
)
|
||||||
|
.bind(version as i64)
|
||||||
|
.fetch_all(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
Ok(promotions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||||
|
sqlx::query(
|
||||||
|
r"
|
||||||
|
INSERT INTO user_discovery_other_promotions (
|
||||||
|
from_contact_id,
|
||||||
|
promotion_id,
|
||||||
|
public_id,
|
||||||
|
threshold,
|
||||||
|
announcement_share,
|
||||||
|
public_key_verified_timestamp
|
||||||
|
)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(promotion.from_contact_id)
|
||||||
|
.bind(promotion.promotion_id as i64)
|
||||||
|
.bind(promotion.public_id)
|
||||||
|
.bind(promotion.threshold as i64)
|
||||||
|
.bind(promotion.announcement_share)
|
||||||
|
.bind(promotion.public_key_verified_timestamp) // Option<i64> maps to NULLable
|
||||||
|
.execute(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_other_promotions_by_public_id(
|
||||||
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> Result<Vec<OtherPromotion>> {
|
||||||
|
let promotions = sqlx::query_as::<_, OtherPromotion>(
|
||||||
|
"SELECT * FROM user_discovery_other_promotions WHERE public_id = ?",
|
||||||
|
)
|
||||||
|
.bind(public_id)
|
||||||
|
.fetch_all(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
Ok(promotions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_announced_user_by_public_id(
|
||||||
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> Result<Option<AnnouncedUser>> {
|
||||||
|
let row = sqlx::query("SELECT * FROM user_discovery_announced_users WHERE public_id = ?")
|
||||||
|
.bind(public_id)
|
||||||
|
.fetch_optional(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
match row {
|
||||||
|
Some(r) => Ok(Some(AnnouncedUser {
|
||||||
|
user_id: r.get::<i64, _>("announced_user_id"),
|
||||||
|
public_key: r.get::<Vec<u8>, _>("announced_public_key"),
|
||||||
|
public_id: r.get::<i64, _>("public_id"),
|
||||||
|
})),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn push_new_user_relation(
|
||||||
|
&self,
|
||||||
|
from_contact_id: UserID,
|
||||||
|
announced_user: AnnouncedUser,
|
||||||
|
public_key_verified_timestamp: Option<i64>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut tx = self
|
||||||
|
.ws
|
||||||
|
.database
|
||||||
|
.pool
|
||||||
|
.begin()
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
r"
|
||||||
|
INSERT INTO user_discovery_announced_users (announced_user_id, announced_public_key, public_id)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
ON CONFLICT(announced_user_id) DO NOTHING
|
||||||
|
")
|
||||||
|
.bind(announced_user.user_id)
|
||||||
|
.bind(announced_user.public_key)
|
||||||
|
.bind(announced_user.public_id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
if from_contact_id != announced_user.user_id {
|
||||||
|
tracing::debug!(
|
||||||
|
"INSERTING THAT {} KNOWS {}",
|
||||||
|
from_contact_id,
|
||||||
|
announced_user.user_id
|
||||||
|
);
|
||||||
|
sqlx::query(
|
||||||
|
r"INSERT INTO user_discovery_user_relations (
|
||||||
|
announced_user_id,
|
||||||
|
from_contact_id,
|
||||||
|
public_key_verified_timestamp
|
||||||
|
)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
ON CONFLICT(announced_user_id, from_contact_id) DO UPDATE SET
|
||||||
|
public_key_verified_timestamp = excluded.public_key_verified_timestamp
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(announced_user.user_id)
|
||||||
|
.bind(from_contact_id)
|
||||||
|
.bind(public_key_verified_timestamp)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit().await.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_all_announced_users(
|
||||||
|
&self,
|
||||||
|
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||||
|
let rows = sqlx::query(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
u.announced_user_id,
|
||||||
|
u.announced_public_key,
|
||||||
|
u.public_id,
|
||||||
|
r.from_contact_id,
|
||||||
|
r.public_key_verified_timestamp
|
||||||
|
FROM user_discovery_announced_users u
|
||||||
|
LEFT JOIN user_discovery_user_relations r
|
||||||
|
ON u.announced_user_id = r.announced_user_id
|
||||||
|
ORDER BY u.announced_user_id
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
let mut map: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>> = HashMap::new();
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
let announced_user = AnnouncedUser {
|
||||||
|
user_id: row.get::<i64, _>("announced_user_id"),
|
||||||
|
public_key: row.get::<Vec<u8>, _>("announced_public_key"),
|
||||||
|
public_id: row.get::<i64, _>("public_id"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let relations_list = map.entry(announced_user).or_insert_with(Vec::new);
|
||||||
|
|
||||||
|
// SQLX returns NULL for columns in a LEFT JOIN where no match is found.
|
||||||
|
if let Ok(Some(contact_id)) = row.try_get::<Option<i64>, _>("from_contact_id") {
|
||||||
|
let timestamp = row.get::<Option<i64>, _>("public_key_verified_timestamp");
|
||||||
|
relations_list.push((contact_id, timestamp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
||||||
|
let version: Option<Vec<u8>> =
|
||||||
|
sqlx::query_scalar("SELECT user_discovery_version FROM contacts WHERE user_id = ?")
|
||||||
|
.bind(contact_id)
|
||||||
|
.fetch_optional(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()> {
|
||||||
|
sqlx::query("UPDATE contacts SET user_discovery_version = ? WHERE user_id = ?")
|
||||||
|
.bind(update)
|
||||||
|
.bind(contact_id)
|
||||||
|
.execute(&self.ws.database.pool)
|
||||||
|
.await
|
||||||
|
.map_err(TwonlyError::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::user_discovery_store::UserDiscoveryDatabaseStore;
|
||||||
|
use protocols::user_discovery::tests::test_initialize_user_discovery;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_initialize_user_discovery_database_store() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
test_initialize_user_discovery::<UserDiscoveryDatabaseStore>().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
rust/core/src/utils.rs
Normal file
22
rust/core/src/utils.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use parking_lot::{RwLock, RwLockReadGuard};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub(crate) struct Shared<T>(Arc<RwLock<T>>);
|
||||||
|
impl<T> Shared<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(value: T) -> Self {
|
||||||
|
Self(Arc::new(RwLock::new(value)))
|
||||||
|
}
|
||||||
|
pub(crate) fn get(&self) -> RwLockReadGuard<'_, T> {
|
||||||
|
self.0.read()
|
||||||
|
}
|
||||||
|
pub(crate) fn cloned(&self) -> T {
|
||||||
|
self.0.read().clone()
|
||||||
|
}
|
||||||
|
pub(crate) fn set(&self, value: T) {
|
||||||
|
*self.0.write() = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -13,10 +13,14 @@ serde = "1.0.228"
|
||||||
prost = "0.14.1"
|
prost = "0.14.1"
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
blahaj = "0.6.0"
|
blahaj = "0.6.0"
|
||||||
postcard = { version = "1.1.3", features = ["alloc"] }
|
serde_json = "1.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
hmac = "0.13.0"
|
hmac = "0.13.0"
|
||||||
sha2 = "0.11.0"
|
sha2 = "0.11.0"
|
||||||
|
tokio = { version = "1.44", features = ["full"] }
|
||||||
|
sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [
|
||||||
|
"derive",
|
||||||
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod stores;
|
pub mod stores;
|
||||||
|
pub mod tests;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::u8;
|
||||||
use blahaj::{Share, Sharks};
|
use blahaj::{Share, Sharks};
|
||||||
use postcard::{from_bytes, to_allocvec};
|
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::user_discovery::error::{Result, UserDiscoveryError};
|
use crate::user_discovery::error::{Result, UserDiscoveryError};
|
||||||
|
|
@ -14,10 +15,6 @@ use crate::user_discovery::user_discovery_message::user_discovery_promotion::Ann
|
||||||
use crate::user_discovery::user_discovery_message::user_discovery_promotion::announcement_share_decrypted::SignedData;
|
use crate::user_discovery::user_discovery_message::user_discovery_promotion::announcement_share_decrypted::SignedData;
|
||||||
pub use traits::UserDiscoveryStore;
|
pub use traits::UserDiscoveryStore;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
static TRANSMITTED_NETWORK_BYTES: std::sync::OnceLock<std::sync::Mutex<usize>> =
|
|
||||||
std::sync::OnceLock::new();
|
|
||||||
|
|
||||||
/// Type of the user id, this must be consistent with the user id defined in
|
/// Type of the user id, this must be consistent with the user id defined in
|
||||||
/// the types.proto
|
/// the types.proto
|
||||||
pub type UserID = i64;
|
pub type UserID = i64;
|
||||||
|
|
@ -29,13 +26,13 @@ struct UserDiscoveryConfig {
|
||||||
/// The number of required shares to get the secret
|
/// The number of required shares to get the secret
|
||||||
threshold: u8,
|
threshold: u8,
|
||||||
/// Currently limited to <= 255 as GF 256 is used
|
/// Currently limited to <= 255 as GF 256 is used
|
||||||
total_number_of_shares: usize,
|
total_number_of_shares: u8,
|
||||||
/// Version of announcements
|
/// Version of announcements
|
||||||
announcement_version: u32,
|
announcement_version: u32,
|
||||||
/// Version of promotions
|
/// Version of promotions
|
||||||
promotion_version: u32,
|
promotion_version: u32,
|
||||||
/// This is a random public_id associated with a single announcement.
|
/// This is a random public_id associated with a single announcement.
|
||||||
public_id: u64,
|
public_id: i64,
|
||||||
/// Verification shares
|
/// Verification shares
|
||||||
verification_shares: Vec<Vec<u8>>,
|
verification_shares: Vec<Vec<u8>>,
|
||||||
// The users' id:
|
// The users' id:
|
||||||
|
|
@ -77,15 +74,15 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(())` - If the user discovery was initialized or updated successfully
|
/// * `Ok(())` - If the user discovery was initialized or updated successfully
|
||||||
/// * `Err(UserDiscoveryError)` - If the user discovery was not initialized or updated successfully
|
/// * `Err(UserDiscoveryError)` - If the user discovery was not initialized or updated successfully
|
||||||
///
|
///
|
||||||
pub fn initialize_or_update(
|
pub async fn initialize_or_update(
|
||||||
&self,
|
&self,
|
||||||
threshold: u8,
|
threshold: u8,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
public_key: Vec<u8>,
|
public_key: Vec<u8>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut config = match self.store.get_config() {
|
let mut config = match self.store.get_config().await {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
let mut config: UserDiscoveryConfig = from_bytes(&config)?;
|
let mut config: UserDiscoveryConfig = serde_json::from_str(&config)?;
|
||||||
config.threshold = threshold;
|
config.threshold = threshold;
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
@ -104,15 +101,19 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
public_key,
|
public_key,
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = self.utils.sign_data(&signed_data.encode_to_vec())?;
|
let signature = self.utils.sign_data(&signed_data.encode_to_vec()).await?;
|
||||||
|
|
||||||
let verification_shares = self.setup_announcements(&config, signed_data, signature)?;
|
let verification_shares = self
|
||||||
|
.setup_announcements(&config, signed_data, signature)
|
||||||
|
.await?;
|
||||||
|
|
||||||
config.public_id = public_id;
|
config.public_id = public_id;
|
||||||
config.announcement_version += 1;
|
config.announcement_version += 1;
|
||||||
config.verification_shares = verification_shares;
|
config.verification_shares = verification_shares;
|
||||||
|
|
||||||
self.store.update_config(to_allocvec(&config)?)?;
|
self.store
|
||||||
|
.update_config(serde_json::to_string_pretty(&config)?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -131,8 +132,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(Vec<u8>)` - The current version of the user discovery
|
/// * `Ok(Vec<u8>)` - The current version of the user discovery
|
||||||
/// * `Err(UserDiscoveryError)` - If there where errors in the store.
|
/// * `Err(UserDiscoveryError)` - If there where errors in the store.
|
||||||
///
|
///
|
||||||
pub fn get_current_version(&self) -> Result<Vec<u8>> {
|
pub async fn get_current_version(&self) -> Result<Vec<u8>> {
|
||||||
let config = self.get_config()?;
|
let config = self.get_config().await?;
|
||||||
Ok(UserDiscoveryVersion {
|
Ok(UserDiscoveryVersion {
|
||||||
announcement: config.announcement_version,
|
announcement: config.announcement_version,
|
||||||
promotion: config.promotion_version,
|
promotion: config.promotion_version,
|
||||||
|
|
@ -148,10 +149,10 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>)` - All connections the user has discovered
|
/// * `Ok(HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>)` - All connections the user has discovered
|
||||||
/// * `Err(UserDiscoveryError)` - If there where erros in the store.
|
/// * `Err(UserDiscoveryError)` - If there where erros in the store.
|
||||||
///
|
///
|
||||||
pub fn get_all_announced_users(
|
pub async fn get_all_announced_users(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||||
self.store.get_all_announced_users()
|
self.store.get_all_announced_users().await
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -167,7 +168,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(Vec<Vec<u8>>)` - The new user discovery messages
|
/// * `Ok(Vec<Vec<u8>>)` - The new user discovery messages
|
||||||
/// * `Err(UserDiscoveryError)` - If there where errors in the store or if the received version is invalid.
|
/// * `Err(UserDiscoveryError)` - If there where errors in the store or if the received version is invalid.
|
||||||
///
|
///
|
||||||
pub fn get_new_messages(
|
pub async fn get_new_messages(
|
||||||
&self,
|
&self,
|
||||||
contact_id: UserID,
|
contact_id: UserID,
|
||||||
received_version: &[u8],
|
received_version: &[u8],
|
||||||
|
|
@ -175,7 +176,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
let received_version = UserDiscoveryVersion::decode(received_version)?;
|
let received_version = UserDiscoveryVersion::decode(received_version)?;
|
||||||
|
|
||||||
let config = self.get_config()?;
|
let config = self.get_config().await?;
|
||||||
let version = Some(UserDiscoveryVersion {
|
let version = Some(UserDiscoveryVersion {
|
||||||
announcement: config.announcement_version,
|
announcement: config.announcement_version,
|
||||||
promotion: config.promotion_version,
|
promotion: config.promotion_version,
|
||||||
|
|
@ -184,7 +185,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
if received_version.announcement < config.announcement_version {
|
if received_version.announcement < config.announcement_version {
|
||||||
tracing::info!("New announcement message available for {}", contact_id);
|
tracing::info!("New announcement message available for {}", contact_id);
|
||||||
|
|
||||||
let announcement_share = self.store.get_share_for_contact(contact_id)?;
|
let announcement_share = self.store.get_share_for_contact(contact_id).await?;
|
||||||
|
|
||||||
let user_discovery_announcement = Some(UserDiscoveryAnnouncement {
|
let user_discovery_announcement = Some(UserDiscoveryAnnouncement {
|
||||||
public_id: config.public_id,
|
public_id: config.public_id,
|
||||||
|
|
@ -206,16 +207,10 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
tracing::info!("New promotion message available for user {}", contact_id);
|
tracing::info!("New promotion message available for user {}", contact_id);
|
||||||
let promoting_messages = self
|
let promoting_messages = self
|
||||||
.store
|
.store
|
||||||
.get_own_promotions_after_version(received_version.promotion)?;
|
.get_own_promotions_after_version(received_version.promotion)
|
||||||
|
.await?;
|
||||||
messages.extend_from_slice(&promoting_messages);
|
messages.extend_from_slice(&promoting_messages);
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
let mut count = TRANSMITTED_NETWORK_BYTES.get().unwrap().lock().unwrap();
|
|
||||||
for message in &messages {
|
|
||||||
*count += message.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,27 +227,37 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(bool)` - True if the user has new announcements
|
/// * `Ok(bool)` - True if the user has new announcements
|
||||||
/// * `Err(UserDiscoveryError)` - If there where errors in the store or if the received version is invalid.
|
/// * `Err(UserDiscoveryError)` - If there where errors in the store or if the received version is invalid.
|
||||||
///
|
///
|
||||||
pub fn should_request_new_messages(&self, contact_id: UserID, version: &[u8]) -> Result<bool> {
|
pub async fn should_request_new_messages(
|
||||||
|
&self,
|
||||||
|
contact_id: UserID,
|
||||||
|
version: &[u8],
|
||||||
|
) -> Result<bool> {
|
||||||
let received_version = UserDiscoveryVersion::decode(version)?;
|
let received_version = UserDiscoveryVersion::decode(version)?;
|
||||||
let stored_version = match self.store.get_contact_version(contact_id)? {
|
let stored_version = match self.store.get_contact_version(contact_id).await? {
|
||||||
Some(buf) => UserDiscoveryVersion::decode(buf.as_slice())?,
|
Some(buf) => UserDiscoveryVersion::decode(buf.as_slice())?,
|
||||||
None => UserDiscoveryVersion {
|
None => UserDiscoveryVersion {
|
||||||
announcement: 0,
|
announcement: 0,
|
||||||
promotion: 0,
|
promotion: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
tracing::debug!(
|
||||||
|
received.announcement = %received_version.announcement,
|
||||||
|
received.promotion = %received_version.promotion,
|
||||||
|
stored.announcement = %stored_version.announcement,
|
||||||
|
stored.promotion = %stored_version.promotion,
|
||||||
|
"Comparing version numbers"
|
||||||
|
);
|
||||||
Ok(received_version.announcement > stored_version.announcement
|
Ok(received_version.announcement > stored_version.announcement
|
||||||
|| received_version.promotion > stored_version.promotion)
|
|| received_version.promotion > stored_version.promotion)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
pub(crate) async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
||||||
pub(crate) fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
self.store.get_contact_version(contact_id).await
|
||||||
self.store.get_contact_version(contact_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the latest version for this discovery.
|
/// Returns the latest version for this discovery.
|
||||||
/// Before calling this function the application must sure that contact_id is qualified to be announced.
|
/// Before calling this function the application must sure that contact_id is qualified to be announced.
|
||||||
pub fn handle_user_discovery_messages(
|
pub async fn handle_user_discovery_messages(
|
||||||
&self,
|
&self,
|
||||||
contact_id: UserID,
|
contact_id: UserID,
|
||||||
messages: Vec<Vec<u8>>,
|
messages: Vec<Vec<u8>>,
|
||||||
|
|
@ -264,22 +269,25 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(uda) = message.user_discovery_announcement {
|
if let Some(uda) = message.user_discovery_announcement {
|
||||||
self.handle_user_discovery_announcement(contact_id, uda)?;
|
self.handle_user_discovery_announcement(contact_id, uda)
|
||||||
|
.await?;
|
||||||
} else if let Some(udp) = message.user_discovery_promotion {
|
} else if let Some(udp) = message.user_discovery_promotion {
|
||||||
self.handle_user_discovery_promotion(contact_id, udp)?;
|
self.handle_user_discovery_promotion(contact_id, udp)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("Got unknown user discovery messaging. Ignoring it.");
|
tracing::warn!("Got unknown user discovery messaging. Ignoring it.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.store
|
self.store
|
||||||
.set_contact_version(contact_id, version.encode_to_vec())?;
|
.set_contact_version(contact_id, version.encode_to_vec())
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_announcements(
|
async fn setup_announcements(
|
||||||
&self,
|
&self,
|
||||||
config: &UserDiscoveryConfig,
|
config: &UserDiscoveryConfig,
|
||||||
signed_data: SignedData,
|
signed_data: SignedData,
|
||||||
|
|
@ -301,7 +309,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
let dealer = sharks.dealer(&encrypted_announcement);
|
let dealer = sharks.dealer(&encrypted_announcement);
|
||||||
|
|
||||||
let mut shares: Vec<Vec<u8>> = dealer
|
let mut shares: Vec<Vec<u8>> = dealer
|
||||||
.take(config.total_number_of_shares)
|
.take(config.total_number_of_shares as usize)
|
||||||
.map(|x| Vec::from(&x))
|
.map(|x| Vec::from(&x))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -325,16 +333,16 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
let split_index = shares.len() - (config.threshold - 1) as usize;
|
let split_index = shares.len() - (config.threshold - 1) as usize;
|
||||||
verification_shares.extend(shares.drain(split_index..));
|
verification_shares.extend(shares.drain(split_index..));
|
||||||
|
|
||||||
self.store.set_shares(shares)?;
|
self.store.set_shares(shares).await?;
|
||||||
|
|
||||||
Ok(verification_shares)
|
Ok(verification_shares)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config(&self) -> Result<UserDiscoveryConfig> {
|
async fn get_config(&self) -> Result<UserDiscoveryConfig> {
|
||||||
Ok(from_bytes(&self.store.get_config()?)?)
|
Ok(serde_json::from_str(&self.store.get_config().await?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_user_discovery_announcement(
|
async fn handle_user_discovery_announcement(
|
||||||
&self,
|
&self,
|
||||||
contact_id: UserID,
|
contact_id: UserID,
|
||||||
uda: UserDiscoveryAnnouncement,
|
uda: UserDiscoveryAnnouncement,
|
||||||
|
|
@ -378,27 +386,34 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
|
|
||||||
if !self
|
if !self
|
||||||
.utils
|
.utils
|
||||||
.verify_stored_pubkey(contact_id, &signed_data.public_key)?
|
.verify_stored_pubkey(contact_id, &signed_data.public_key)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||||
"public key does not match with stored one",
|
"public key does not match with stored one",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.utils.verify_signature(
|
if !self
|
||||||
|
.utils
|
||||||
|
.verify_signature(
|
||||||
&signed_data.encode_to_vec(),
|
&signed_data.encode_to_vec(),
|
||||||
&signed_data.public_key,
|
&signed_data.public_key,
|
||||||
&asd.signature,
|
&asd.signature,
|
||||||
)? {
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||||
"signature invalid",
|
"signature invalid",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("Increased promotion version id.");
|
tracing::debug!("Increased promotion version id.");
|
||||||
let mut config = self.get_config()?;
|
let mut config = self.get_config().await?;
|
||||||
config.promotion_version += 1;
|
config.promotion_version += 1;
|
||||||
self.store.update_config(to_allocvec(&config)?)?;
|
self.store
|
||||||
|
.update_config(serde_json::to_string_pretty(&config)?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let message = UserDiscoveryMessage {
|
let message = UserDiscoveryMessage {
|
||||||
version: Some(UserDiscoveryVersion {
|
version: Some(UserDiscoveryVersion {
|
||||||
|
|
@ -415,11 +430,13 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.store.push_own_promotion(
|
self.store
|
||||||
|
.push_own_promotion(
|
||||||
contact_id,
|
contact_id,
|
||||||
config.promotion_version,
|
config.promotion_version,
|
||||||
message.encode_to_vec(),
|
message.encode_to_vec(),
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let announced_user = AnnouncedUser {
|
let announced_user = AnnouncedUser {
|
||||||
user_id: signed_data.user_id,
|
user_id: signed_data.user_id,
|
||||||
|
|
@ -428,16 +445,18 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"NEW PROMOTION: {} knows {}",
|
"NEW PROMOTION 3: {} knows {}",
|
||||||
contact_id,
|
contact_id,
|
||||||
announced_user.user_id
|
announced_user.user_id
|
||||||
);
|
);
|
||||||
// User is known, so add him to thr users relations
|
// User is known, so add him to thr users relations
|
||||||
self.store.push_new_user_relation(
|
self.store
|
||||||
|
.push_new_user_relation(
|
||||||
contact_id,
|
contact_id,
|
||||||
announced_user,
|
announced_user,
|
||||||
None, // This flag mus be handled by the applications as this comes from an announcement.
|
None, // This flag mus be handled by the applications as this comes from an announcement.
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -445,38 +464,45 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_user_discovery_promotion(
|
async fn handle_user_discovery_promotion(
|
||||||
&self,
|
&self,
|
||||||
from_contact_id: UserID,
|
from_contact_id: UserID,
|
||||||
udp: UserDiscoveryPromotion,
|
udp: UserDiscoveryPromotion,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::debug!("Received a new UDP with public_id = {}.", &udp.public_id);
|
tracing::debug!("Received a new UDP with public_id = {}.", &udp.public_id);
|
||||||
|
|
||||||
self.store.store_other_promotion(OtherPromotion {
|
self.store
|
||||||
|
.store_other_promotion(OtherPromotion {
|
||||||
from_contact_id,
|
from_contact_id,
|
||||||
promotion_id: udp.promotion_id,
|
promotion_id: udp.promotion_id,
|
||||||
threshold: udp.threshold as u8,
|
threshold: udp.threshold as u8,
|
||||||
public_id: udp.public_id,
|
public_id: udp.public_id,
|
||||||
announcement_share: udp.announcement_share,
|
announcement_share: udp.announcement_share,
|
||||||
public_key_verified_timestamp: udp.public_key_verified_timestamp,
|
public_key_verified_timestamp: udp.public_key_verified_timestamp,
|
||||||
})?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(contact) = self.store.get_announced_user_by_public_id(udp.public_id)? {
|
if let Some(contact) = self
|
||||||
|
.store
|
||||||
|
.get_announced_user_by_public_id(udp.public_id)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"NEW PROMOTION 2: {} knows {}",
|
"NEW PROMOTION 2: {} knows {}",
|
||||||
from_contact_id,
|
from_contact_id,
|
||||||
contact.user_id
|
contact.user_id
|
||||||
);
|
);
|
||||||
// The user is already known, just propagate the relation ship
|
// The user is already known, just propagate the relation ship
|
||||||
self.store.push_new_user_relation(
|
self.store
|
||||||
from_contact_id,
|
.push_new_user_relation(from_contact_id, contact, udp.public_key_verified_timestamp)
|
||||||
contact,
|
.await?;
|
||||||
udp.public_key_verified_timestamp,
|
|
||||||
)?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let promotions = self.store.get_other_promotions_by_public_id(udp.public_id);
|
let promotions = self
|
||||||
|
.store
|
||||||
|
.get_other_promotions_by_public_id(udp.public_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if promotions.len() < udp.threshold as usize {
|
if promotions.len() < udp.threshold as usize {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
|
@ -507,11 +533,15 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.utils.verify_signature(
|
if !self
|
||||||
|
.utils
|
||||||
|
.verify_signature(
|
||||||
&signed_data.encode_to_vec(),
|
&signed_data.encode_to_vec(),
|
||||||
&signed_data.public_key,
|
&signed_data.public_key,
|
||||||
&asd.signature,
|
&asd.signature,
|
||||||
)? {
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
||||||
"signature is invalid",
|
"signature is invalid",
|
||||||
)));
|
)));
|
||||||
|
|
@ -525,7 +555,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
public_id: udp.public_id,
|
public_id: udp.public_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = self.get_config()?.user_id;
|
let user_id = self.get_config().await?.user_id;
|
||||||
for promotion in promotions {
|
for promotion in promotions {
|
||||||
// Do not store the announcement of the users itself.
|
// Do not store the announcement of the users itself.
|
||||||
// Or in case the promotion promotes myself
|
// Or in case the promotion promotes myself
|
||||||
|
|
@ -535,15 +565,17 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"NEW PROMOTION: {} knows {}",
|
"NEW PROMOTION: {:x} knows {:x}",
|
||||||
promotion.from_contact_id,
|
promotion.from_contact_id,
|
||||||
announced_user.user_id
|
announced_user.user_id
|
||||||
);
|
);
|
||||||
self.store.push_new_user_relation(
|
self.store
|
||||||
|
.push_new_user_relation(
|
||||||
promotion.from_contact_id,
|
promotion.from_contact_id,
|
||||||
announced_user.clone(),
|
announced_user.clone(),
|
||||||
promotion.public_key_verified_timestamp,
|
promotion.public_key_verified_timestamp,
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -557,7 +589,7 @@ impl Default for UserDiscoveryConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
threshold: 2,
|
threshold: 2,
|
||||||
total_number_of_shares: 255,
|
total_number_of_shares: u8::MAX,
|
||||||
announcement_version: 0,
|
announcement_version: 0,
|
||||||
promotion_version: 0,
|
promotion_version: 0,
|
||||||
verification_shares: vec![],
|
verification_shares: vec![],
|
||||||
|
|
@ -566,271 +598,3 @@ impl Default for UserDiscoveryConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use crate::user_discovery::stores::InMemoryStore;
|
|
||||||
use crate::user_discovery::traits::tests::TestingUtils;
|
|
||||||
use crate::user_discovery::{
|
|
||||||
UserDiscovery, UserDiscoveryVersion, UserID, TRANSMITTED_NETWORK_BYTES,
|
|
||||||
};
|
|
||||||
use prost::Message;
|
|
||||||
|
|
||||||
fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> {
|
|
||||||
UserDiscoveryVersion {
|
|
||||||
announcement,
|
|
||||||
promotion,
|
|
||||||
}
|
|
||||||
.encode_to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ud(user_id: usize) -> UserDiscovery<InMemoryStore, TestingUtils> {
|
|
||||||
let store = InMemoryStore::default();
|
|
||||||
let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap();
|
|
||||||
|
|
||||||
ud.initialize_or_update(2, user_id as UserID, vec![user_id as u8; 32])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let version = ud.get_current_version().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(version, get_version_bytes(1, 0));
|
|
||||||
ud
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_new_messages(
|
|
||||||
from: (usize, &UserDiscovery<InMemoryStore, TestingUtils>),
|
|
||||||
to: (usize, &UserDiscovery<InMemoryStore, TestingUtils>),
|
|
||||||
has_new_messages: bool,
|
|
||||||
) {
|
|
||||||
// From sends a message with his current version to To
|
|
||||||
let to_received_version = &from.1.get_current_version().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
to.1.should_request_new_messages(from.0 as UserID, to_received_version)
|
|
||||||
.unwrap(),
|
|
||||||
has_new_messages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_and_handle_messages(
|
|
||||||
from: (usize, &UserDiscovery<InMemoryStore, TestingUtils>),
|
|
||||||
to: (usize, &UserDiscovery<InMemoryStore, TestingUtils>),
|
|
||||||
messages_count: usize,
|
|
||||||
) {
|
|
||||||
// From sends a message with his current version to To
|
|
||||||
let to_received_version = &from.1.get_current_version().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
to.1.should_request_new_messages(from.0 as UserID, to_received_version)
|
|
||||||
.unwrap(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
// As To has a older version stored he sends a request to From: Give me all messages since version.
|
|
||||||
let from_request_version_from_to =
|
|
||||||
to.1.get_contact_version(from.0 as UserID)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap_or(get_version_bytes(0, 0));
|
|
||||||
|
|
||||||
let new_messages = from
|
|
||||||
.1
|
|
||||||
.get_new_messages(to.0 as UserID, &from_request_version_from_to)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(new_messages.len() <= messages_count);
|
|
||||||
|
|
||||||
to.1.handle_user_discovery_messages(from.0 as UserID, new_messages)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
to.1.should_request_new_messages(
|
|
||||||
from.0 as UserID,
|
|
||||||
&from.1.get_current_version().unwrap()
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ALICE: usize = 0;
|
|
||||||
const BOB: usize = 1;
|
|
||||||
const CHARLIE: usize = 2;
|
|
||||||
const DAVID: usize = 3;
|
|
||||||
const FRANK: usize = 4;
|
|
||||||
const TEST_USER_COUNT: usize = 5;
|
|
||||||
struct TestUsers {
|
|
||||||
names: [&'static str; TEST_USER_COUNT],
|
|
||||||
friends: [Vec<usize>; TEST_USER_COUNT],
|
|
||||||
uds: Vec<UserDiscovery<InMemoryStore, TestingUtils>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestUsers {
|
|
||||||
fn get() -> Self {
|
|
||||||
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
|
|
||||||
let mut uds = vec![];
|
|
||||||
for index in 0..names.len() {
|
|
||||||
uds.push(get_ud(index));
|
|
||||||
}
|
|
||||||
let friends = [
|
|
||||||
vec![BOB, CHARLIE],
|
|
||||||
vec![ALICE, CHARLIE, DAVID],
|
|
||||||
vec![ALICE, BOB, DAVID, FRANK],
|
|
||||||
vec![BOB, CHARLIE],
|
|
||||||
vec![CHARLIE],
|
|
||||||
];
|
|
||||||
Self {
|
|
||||||
names,
|
|
||||||
uds,
|
|
||||||
friends,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_initialize_user_discovery() {
|
|
||||||
let _ = pretty_env_logger::try_init();
|
|
||||||
let _ = TRANSMITTED_NETWORK_BYTES.get_or_init(|| std::sync::Mutex::new(0));
|
|
||||||
|
|
||||||
let users = TestUsers::get();
|
|
||||||
|
|
||||||
fn to_all_friends(from: usize, message_count: usize, users: &TestUsers) {
|
|
||||||
for friend in &users.friends[from] {
|
|
||||||
tracing::debug!("From {} to {}", users.names[from], users.names[*friend]);
|
|
||||||
|
|
||||||
if message_count == 0 {
|
|
||||||
assert_new_messages(
|
|
||||||
(from, &users.uds[from]),
|
|
||||||
(*friend, &users.uds[*friend]),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
request_and_handle_messages(
|
|
||||||
(from, &users.uds[from]),
|
|
||||||
(*friend, &users.uds[*friend]),
|
|
||||||
message_count,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let message_flows = [
|
|
||||||
// ALICE: own announcement sending to BOB and CHARLIE
|
|
||||||
(ALICE, 1),
|
|
||||||
// BOB: own announcement + promotion for ALICE
|
|
||||||
(BOB, 2),
|
|
||||||
// BOBs version should not have any new messages for his friends
|
|
||||||
(BOB, 0),
|
|
||||||
// ALICE: promotion for BOB
|
|
||||||
(ALICE, 1),
|
|
||||||
// CHARLIE: own announcement + promotion for ALICE, BOB
|
|
||||||
(CHARLIE, 3),
|
|
||||||
// DAVID: own announcement + promotion for BOB, CHARLIE
|
|
||||||
(DAVID, 3),
|
|
||||||
// BOB: promotion for CHARLIE, DAVID
|
|
||||||
(BOB, 2),
|
|
||||||
// CHARLIE: promotion for DAVID
|
|
||||||
(CHARLIE, 1),
|
|
||||||
// FRANK: own announcement + promotion for CHARLIE
|
|
||||||
(FRANK, 2),
|
|
||||||
// CHARLIE: promotion for FRANK
|
|
||||||
(CHARLIE, 1),
|
|
||||||
// ALICE: promotion for CHARLIE
|
|
||||||
(ALICE, 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (i, (from, count)) in message_flows.into_iter().enumerate() {
|
|
||||||
tracing::debug!("MESSAGE FLOW: {i}");
|
|
||||||
to_all_friends(from, count, &users);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("Now all users should have the newest version.");
|
|
||||||
|
|
||||||
for from in 0..TEST_USER_COUNT {
|
|
||||||
for to in &users.friends[from] {
|
|
||||||
tracing::debug!(
|
|
||||||
"Does {} has open messages for {}?",
|
|
||||||
&users.names[from],
|
|
||||||
&users.names[*to]
|
|
||||||
);
|
|
||||||
assert_new_messages((from, &users.uds[from]), (*to, &users.uds[*to]), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("Test if all exchanges where successful.");
|
|
||||||
|
|
||||||
let announced_users_expected = [
|
|
||||||
// ALICE should now know that BOB and CHARLIE, BOB and DAVID and CHARLIE and DAVID are friends.
|
|
||||||
// Alice should also have one protected share from Frank.
|
|
||||||
(
|
|
||||||
ALICE,
|
|
||||||
vec![
|
|
||||||
(BOB, vec![CHARLIE]), // ALICE knows Bob and that CHARLIE is connected with BOB
|
|
||||||
(CHARLIE, vec![BOB]), // ALICE knows CHARLIE and that BOB is connected with CHARLIE
|
|
||||||
(DAVID, vec![BOB, CHARLIE]), // ALICE knows DAVID and that BOB and CHARLIE are connected with DAVID
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
BOB,
|
|
||||||
vec![
|
|
||||||
(ALICE, vec![CHARLIE]),
|
|
||||||
(CHARLIE, vec![ALICE, DAVID]),
|
|
||||||
(DAVID, vec![CHARLIE]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
CHARLIE,
|
|
||||||
vec![
|
|
||||||
(ALICE, vec![BOB]),
|
|
||||||
(BOB, vec![ALICE, DAVID]),
|
|
||||||
(DAVID, vec![BOB]),
|
|
||||||
(FRANK, vec![]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
DAVID,
|
|
||||||
vec![
|
|
||||||
(ALICE, vec![BOB, CHARLIE]),
|
|
||||||
(BOB, vec![CHARLIE]),
|
|
||||||
(CHARLIE, vec![BOB]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(FRANK, vec![(CHARLIE, vec![])]),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (user, announcements) in announced_users_expected {
|
|
||||||
let announced_users2 = users.uds[user].get_all_announced_users().unwrap();
|
|
||||||
let mut announced_users = HashMap::new();
|
|
||||||
for a in announced_users2 {
|
|
||||||
announced_users.insert(a.0.user_id, a.1.iter().map(|x| x.0).collect::<Vec<_>>());
|
|
||||||
}
|
|
||||||
tracing::debug!("{} knows now: {}", users.names[user], announced_users.len());
|
|
||||||
assert_eq!(announced_users.len(), announcements.len());
|
|
||||||
for (contact_id, announced_users_expected) in announcements {
|
|
||||||
let announced_users = announced_users.get(&(contact_id as i64)).unwrap();
|
|
||||||
tracing::debug!(
|
|
||||||
"{} knows now that {} has the following friends: {}",
|
|
||||||
users.names[user],
|
|
||||||
users.names[contact_id],
|
|
||||||
announced_users
|
|
||||||
.iter()
|
|
||||||
.map(|x| users.names[*x as usize])
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
);
|
|
||||||
let announced_users: HashSet<i64> = announced_users.iter().cloned().collect();
|
|
||||||
let announced_users_expected: HashSet<i64> = announced_users_expected
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|x| x as i64)
|
|
||||||
.collect();
|
|
||||||
assert_eq!(announced_users, announced_users_expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = TRANSMITTED_NETWORK_BYTES.get().unwrap().lock().unwrap();
|
|
||||||
|
|
||||||
tracing::info!("Transmitted a total of {} bytes.", *count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,7 +18,10 @@ pub enum UserDiscoveryError {
|
||||||
NotInitialized,
|
NotInitialized,
|
||||||
|
|
||||||
#[error("`{0}`")]
|
#[error("`{0}`")]
|
||||||
PostcardError(#[from] postcard::Error),
|
JsonError(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("`{0}`")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("error while calculating shamirs secret shares: `{0}`")]
|
#[error("error while calculating shamirs secret shares: `{0}`")]
|
||||||
ShamirsSecret(String),
|
ShamirsSecret(String),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct Storage {
|
pub(crate) struct Storage {
|
||||||
config: Option<Vec<u8>>,
|
config: Option<String>,
|
||||||
unused_shares: Vec<Vec<u8>>,
|
unused_shares: Vec<Vec<u8>>,
|
||||||
used_shares: HashMap<UserID, Vec<u8>>,
|
used_shares: HashMap<UserID, Vec<u8>>,
|
||||||
contact_versions: HashMap<UserID, Vec<u8>>,
|
contact_versions: HashMap<UserID, Vec<u8>>,
|
||||||
|
|
@ -27,24 +27,27 @@ impl InMemoryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDiscoveryStore for InMemoryStore {
|
impl UserDiscoveryStore for InMemoryStore {
|
||||||
fn get_config(&self) -> Result<Vec<u8>> {
|
async fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
async fn get_config(&self) -> Result<String> {
|
||||||
if let Some(storage) = self.storage().config.clone() {
|
if let Some(storage) = self.storage().config.clone() {
|
||||||
return Ok(storage);
|
return Ok(storage);
|
||||||
}
|
}
|
||||||
Err(UserDiscoveryError::NotInitialized)
|
Err(UserDiscoveryError::NotInitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_config(&self, update: Vec<u8>) -> Result<()> {
|
async fn update_config(&self, update: String) -> Result<()> {
|
||||||
self.storage().config = Some(update);
|
self.storage().config = Some(update);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
async fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
||||||
self.storage().unused_shares = shares;
|
self.storage().unused_shares = shares;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>> {
|
async fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>> {
|
||||||
let mut storage = self.storage();
|
let mut storage = self.storage();
|
||||||
if let Some(share) = storage.used_shares.get(&contact_id) {
|
if let Some(share) = storage.used_shares.get(&contact_id) {
|
||||||
return Ok(share.to_vec());
|
return Ok(share.to_vec());
|
||||||
|
|
@ -56,16 +59,16 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
Err(UserDiscoveryError::NoSharesLeft)
|
Err(UserDiscoveryError::NoSharesLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
||||||
Ok(self.storage().contact_versions.get(&contact_id).cloned())
|
Ok(self.storage().contact_versions.get(&contact_id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()> {
|
async fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()> {
|
||||||
self.storage().contact_versions.insert(contact_id, update);
|
self.storage().contact_versions.insert(contact_id, update);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_own_promotion(
|
async fn push_own_promotion(
|
||||||
&self,
|
&self,
|
||||||
contact_id: UserID,
|
contact_id: UserID,
|
||||||
version: u32,
|
version: u32,
|
||||||
|
|
@ -80,7 +83,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||||
let storage = self.storage();
|
let storage = self.storage();
|
||||||
let elements = storage.own_promotions[(version as usize)..]
|
let elements = storage.own_promotions[(version as usize)..]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -89,21 +92,28 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
Ok(elements)
|
Ok(elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||||
self.storage().other_promotions.push(promotion);
|
self.storage().other_promotions.push(promotion);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_other_promotions_by_public_id(&self, public_id: u64) -> Vec<OtherPromotion> {
|
async fn get_other_promotions_by_public_id(
|
||||||
self.storage()
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> Result<Vec<OtherPromotion>> {
|
||||||
|
Ok(self
|
||||||
|
.storage()
|
||||||
.other_promotions
|
.other_promotions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|other| other.public_id == public_id)
|
.filter(|other| other.public_id == public_id)
|
||||||
.map(OtherPromotion::to_owned)
|
.map(OtherPromotion::to_owned)
|
||||||
.collect()
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_announced_user_by_public_id(&self, public_id: u64) -> Result<Option<AnnouncedUser>> {
|
async fn get_announced_user_by_public_id(
|
||||||
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> Result<Option<AnnouncedUser>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.storage()
|
.storage()
|
||||||
.announced_users
|
.announced_users
|
||||||
|
|
@ -112,13 +122,13 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
.map(|u| u.0.to_owned()))
|
.map(|u| u.0.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_announced_users(
|
async fn get_all_announced_users(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||||
Ok(self.storage().announced_users.clone())
|
Ok(self.storage().announced_users.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_new_user_relation(
|
async fn push_new_user_relation(
|
||||||
&self,
|
&self,
|
||||||
from_contact_id: UserID,
|
from_contact_id: UserID,
|
||||||
announced_user: AnnouncedUser,
|
announced_user: AnnouncedUser,
|
||||||
|
|
|
||||||
272
rust/protocols/src/user_discovery/tests.rs
Normal file
272
rust/protocols/src/user_discovery/tests.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
use crate::user_discovery::traits::tests::TestingUtils;
|
||||||
|
use crate::user_discovery::{UserDiscovery, UserDiscoveryStore, UserDiscoveryVersion, UserID};
|
||||||
|
use prost::Message;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> {
|
||||||
|
UserDiscoveryVersion {
|
||||||
|
announcement,
|
||||||
|
promotion,
|
||||||
|
}
|
||||||
|
.encode_to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_ud<S: UserDiscoveryStore + Clone>(user_id: usize) -> UserDiscovery<S, TestingUtils> {
|
||||||
|
let store = S::new().await;
|
||||||
|
let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap();
|
||||||
|
|
||||||
|
ud.initialize_or_update(2, user_id as UserID, vec![user_id as u8; 32])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let version = ud.get_current_version().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version, get_version_bytes(1, 0));
|
||||||
|
ud
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_new_messages<S: UserDiscoveryStore>(
|
||||||
|
from: (usize, &UserDiscovery<S, TestingUtils>),
|
||||||
|
to: (usize, &UserDiscovery<S, TestingUtils>),
|
||||||
|
has_new_messages: bool,
|
||||||
|
) {
|
||||||
|
// From sends a message with his current version to To
|
||||||
|
let to_received_version = &from.1.get_current_version().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
to.1.should_request_new_messages(from.0 as UserID, to_received_version)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
has_new_messages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_and_handle_messages<S: UserDiscoveryStore>(
|
||||||
|
from: (usize, &UserDiscovery<S, TestingUtils>),
|
||||||
|
to: (usize, &UserDiscovery<S, TestingUtils>),
|
||||||
|
messages_count: usize,
|
||||||
|
) {
|
||||||
|
// From sends a message with his current version to To
|
||||||
|
let to_received_version = &from.1.get_current_version().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
to.1.should_request_new_messages(from.0 as UserID, to_received_version)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// As To has a older version stored he sends a request to From: Give me all messages since version.
|
||||||
|
let from_request_version_from_to =
|
||||||
|
to.1.get_contact_version(from.0 as UserID)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap_or(get_version_bytes(0, 0));
|
||||||
|
|
||||||
|
let new_messages = from
|
||||||
|
.1
|
||||||
|
.get_new_messages(to.0 as UserID, &from_request_version_from_to)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(new_messages.len() <= messages_count);
|
||||||
|
|
||||||
|
to.1.handle_user_discovery_messages(from.0 as UserID, new_messages)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to.1.should_request_new_messages(
|
||||||
|
from.0 as UserID,
|
||||||
|
&from.1.get_current_version().await.unwrap()
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALICE: usize = 0;
|
||||||
|
const BOB: usize = 1;
|
||||||
|
const CHARLIE: usize = 2;
|
||||||
|
const DAVID: usize = 3;
|
||||||
|
const FRANK: usize = 4;
|
||||||
|
const TEST_USER_COUNT: usize = 5;
|
||||||
|
struct TestUsers<S: UserDiscoveryStore> {
|
||||||
|
names: [&'static str; TEST_USER_COUNT],
|
||||||
|
friends: [Vec<usize>; TEST_USER_COUNT],
|
||||||
|
uds: Vec<UserDiscovery<S, TestingUtils>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: UserDiscoveryStore + Clone> TestUsers<S> {
|
||||||
|
async fn get() -> Self {
|
||||||
|
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
|
||||||
|
let mut uds = vec![];
|
||||||
|
for index in 0..names.len() {
|
||||||
|
uds.push(get_ud(index).await);
|
||||||
|
}
|
||||||
|
let friends = [
|
||||||
|
vec![BOB, CHARLIE],
|
||||||
|
vec![ALICE, CHARLIE, DAVID],
|
||||||
|
vec![ALICE, BOB, DAVID, FRANK],
|
||||||
|
vec![BOB, CHARLIE],
|
||||||
|
vec![CHARLIE],
|
||||||
|
];
|
||||||
|
Self {
|
||||||
|
names,
|
||||||
|
uds,
|
||||||
|
friends,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_initialize_user_discovery<S: UserDiscoveryStore + Clone>() {
|
||||||
|
#[cfg(test)]
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let users = TestUsers::<S>::get().await;
|
||||||
|
|
||||||
|
async fn to_all_friends<S: UserDiscoveryStore + Clone>(
|
||||||
|
from: usize,
|
||||||
|
message_count: usize,
|
||||||
|
users: &TestUsers<S>,
|
||||||
|
) {
|
||||||
|
for friend in &users.friends[from] {
|
||||||
|
tracing::debug!("From {} to {}", users.names[from], users.names[*friend]);
|
||||||
|
|
||||||
|
if message_count == 0 {
|
||||||
|
assert_new_messages(
|
||||||
|
(from, &users.uds[from]),
|
||||||
|
(*friend, &users.uds[*friend]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
request_and_handle_messages(
|
||||||
|
(from, &users.uds[from]),
|
||||||
|
(*friend, &users.uds[*friend]),
|
||||||
|
message_count,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_flows = [
|
||||||
|
// ALICE: own announcement sending to BOB and CHARLIE
|
||||||
|
(ALICE, 1),
|
||||||
|
// BOB: own announcement + promotion for ALICE
|
||||||
|
(BOB, 2),
|
||||||
|
// BOBs version should not have any new messages for his friends
|
||||||
|
(BOB, 0),
|
||||||
|
// ALICE: promotion for BOB
|
||||||
|
(ALICE, 1),
|
||||||
|
// CHARLIE: own announcement + promotion for ALICE, BOB
|
||||||
|
(CHARLIE, 3),
|
||||||
|
// DAVID: own announcement + promotion for BOB, CHARLIE
|
||||||
|
(DAVID, 3),
|
||||||
|
// BOB: promotion for CHARLIE, DAVID
|
||||||
|
(BOB, 2),
|
||||||
|
// CHARLIE: promotion for DAVID
|
||||||
|
(CHARLIE, 1),
|
||||||
|
// FRANK: own announcement + promotion for CHARLIE
|
||||||
|
(FRANK, 2),
|
||||||
|
// CHARLIE: promotion for FRANK
|
||||||
|
(CHARLIE, 1),
|
||||||
|
// ALICE: promotion for CHARLIE
|
||||||
|
(ALICE, 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i, (from, count)) in message_flows.into_iter().enumerate() {
|
||||||
|
tracing::debug!("MESSAGE FLOW: {i}");
|
||||||
|
to_all_friends(from, count, &users).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Now all users should have the newest version.");
|
||||||
|
|
||||||
|
for from in 0..TEST_USER_COUNT {
|
||||||
|
for to in &users.friends[from] {
|
||||||
|
tracing::debug!(
|
||||||
|
"Does {} has open messages for {}?",
|
||||||
|
&users.names[from],
|
||||||
|
&users.names[*to]
|
||||||
|
);
|
||||||
|
assert_new_messages((from, &users.uds[from]), (*to, &users.uds[*to]), false).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Test if all exchanges where successful.");
|
||||||
|
|
||||||
|
let announced_users_expected = [
|
||||||
|
// ALICE should now know that BOB and CHARLIE, BOB and DAVID and CHARLIE and DAVID are friends.
|
||||||
|
// Alice should also have one protected share from Frank.
|
||||||
|
(
|
||||||
|
ALICE,
|
||||||
|
vec![
|
||||||
|
(BOB, vec![CHARLIE]), // ALICE knows Bob and that CHARLIE is connected with BOB
|
||||||
|
(CHARLIE, vec![BOB]), // ALICE knows CHARLIE and that BOB is connected with CHARLIE
|
||||||
|
(DAVID, vec![BOB, CHARLIE]), // ALICE knows DAVID and that BOB and CHARLIE are connected with DAVID
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
BOB,
|
||||||
|
vec![
|
||||||
|
(ALICE, vec![CHARLIE]),
|
||||||
|
(CHARLIE, vec![ALICE, DAVID]),
|
||||||
|
(DAVID, vec![CHARLIE]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
CHARLIE,
|
||||||
|
vec![
|
||||||
|
(ALICE, vec![BOB]),
|
||||||
|
(BOB, vec![ALICE, DAVID]),
|
||||||
|
(DAVID, vec![BOB]),
|
||||||
|
(FRANK, vec![]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
DAVID,
|
||||||
|
vec![
|
||||||
|
(ALICE, vec![BOB, CHARLIE]),
|
||||||
|
(BOB, vec![CHARLIE]),
|
||||||
|
(CHARLIE, vec![BOB]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(FRANK, vec![(CHARLIE, vec![])]),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (user, announcements) in announced_users_expected {
|
||||||
|
let announced_users2 = users.uds[user].get_all_announced_users().await.unwrap();
|
||||||
|
let mut announced_users = HashMap::new();
|
||||||
|
for a in announced_users2 {
|
||||||
|
announced_users.insert(a.0.user_id, a.1.iter().map(|x| x.0).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
|
tracing::debug!("{} knows now: {}", users.names[user], announced_users.len());
|
||||||
|
assert_eq!(announced_users.len(), announcements.len());
|
||||||
|
for (contact_id, announced_users_expected) in announcements {
|
||||||
|
let announced_users = announced_users.get(&(contact_id as i64)).unwrap();
|
||||||
|
tracing::debug!(
|
||||||
|
"{} knows now that {} has the following friends: {}",
|
||||||
|
users.names[user],
|
||||||
|
users.names[contact_id],
|
||||||
|
announced_users
|
||||||
|
.iter()
|
||||||
|
.map(|x| users.names[*x as usize])
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
let announced_users: HashSet<i64> = announced_users.iter().cloned().collect();
|
||||||
|
let announced_users_expected: HashSet<i64> = announced_users_expected
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|x| x as i64)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(announced_users, announced_users_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_initialize_user_discovery_in_memory_store() {
|
||||||
|
test_initialize_user_discovery::<crate::user_discovery::stores::InMemoryStore>().await;
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,12 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::user_discovery::error::Result;
|
use crate::user_discovery::error::Result;
|
||||||
use crate::user_discovery::UserID;
|
use crate::user_discovery::UserID;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, sqlx::FromRow)]
|
||||||
pub struct OtherPromotion {
|
pub struct OtherPromotion {
|
||||||
pub promotion_id: u32,
|
pub promotion_id: u32,
|
||||||
pub public_id: u64,
|
pub public_id: i64,
|
||||||
pub from_contact_id: UserID,
|
pub from_contact_id: UserID,
|
||||||
pub threshold: u8,
|
pub threshold: u8,
|
||||||
pub announcement_share: Vec<u8>,
|
pub announcement_share: Vec<u8>,
|
||||||
|
|
@ -17,62 +18,97 @@ pub struct OtherPromotion {
|
||||||
pub struct AnnouncedUser {
|
pub struct AnnouncedUser {
|
||||||
pub user_id: UserID,
|
pub user_id: UserID,
|
||||||
pub public_key: Vec<u8>,
|
pub public_key: Vec<u8>,
|
||||||
pub public_id: u64,
|
pub public_id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UserDiscoveryStore {
|
pub trait UserDiscoveryStore {
|
||||||
fn get_config(&self) -> Result<Vec<u8>>;
|
fn new() -> impl std::future::Future<Output = Self> + Send;
|
||||||
fn update_config(&self, update: Vec<u8>) -> Result<()>;
|
fn get_config(&self) -> impl Future<Output = Result<String>> + Send;
|
||||||
fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()>;
|
fn update_config(&self, update: String) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
fn set_shares(&self, shares: Vec<Vec<u8>>) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
||||||
fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>>;
|
fn get_share_for_contact(
|
||||||
|
&self,
|
||||||
|
contact_id: UserID,
|
||||||
|
) -> impl Future<Output = Result<Vec<u8>>> + Send;
|
||||||
|
|
||||||
fn push_own_promotion(
|
fn push_own_promotion(
|
||||||
&self,
|
&self,
|
||||||
contact_id: UserID,
|
contact_id: UserID,
|
||||||
version: u32,
|
version: u32,
|
||||||
promotion: Vec<u8>,
|
promotion: Vec<u8>,
|
||||||
) -> Result<()>;
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
||||||
fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>>;
|
fn get_own_promotions_after_version(
|
||||||
|
&self,
|
||||||
|
version: u32,
|
||||||
|
) -> impl Future<Output = Result<Vec<Vec<u8>>>> + Send;
|
||||||
|
|
||||||
fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()>;
|
fn store_other_promotion(
|
||||||
fn get_other_promotions_by_public_id(&self, public_id: u64) -> Vec<OtherPromotion>;
|
&self,
|
||||||
|
promotion: OtherPromotion,
|
||||||
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
fn get_other_promotions_by_public_id(
|
||||||
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> impl Future<Output = Result<Vec<OtherPromotion>>> + Send;
|
||||||
|
|
||||||
fn get_announced_user_by_public_id(&self, public_id: u64) -> Result<Option<AnnouncedUser>>;
|
fn get_announced_user_by_public_id(
|
||||||
|
&self,
|
||||||
|
public_id: i64,
|
||||||
|
) -> impl Future<Output = Result<Option<AnnouncedUser>>> + Send;
|
||||||
|
|
||||||
fn push_new_user_relation(
|
fn push_new_user_relation(
|
||||||
&self,
|
&self,
|
||||||
from_contact_id: UserID,
|
from_contact_id: UserID,
|
||||||
announced_user: AnnouncedUser,
|
announced_user: AnnouncedUser,
|
||||||
public_key_verified_timestamp: Option<i64>,
|
public_key_verified_timestamp: Option<i64>,
|
||||||
) -> Result<()>;
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
||||||
fn get_all_announced_users(&self)
|
fn get_all_announced_users(
|
||||||
-> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>>;
|
&self,
|
||||||
|
) -> impl Future<Output = Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>>> + Send;
|
||||||
|
|
||||||
fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>>;
|
fn get_contact_version(
|
||||||
fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()>;
|
&self,
|
||||||
|
contact_id: UserID,
|
||||||
|
) -> impl Future<Output = Result<Option<Vec<u8>>>> + Send;
|
||||||
|
fn set_contact_version(
|
||||||
|
&self,
|
||||||
|
contact_id: UserID,
|
||||||
|
update: Vec<u8>,
|
||||||
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UserDiscoveryUtils {
|
pub trait UserDiscoveryUtils {
|
||||||
fn sign_data(&self, input_data: &[u8]) -> Result<Vec<u8>>;
|
fn sign_data(&self, input_data: &[u8]) -> impl Future<Output = Result<Vec<u8>>> + Send;
|
||||||
fn verify_signature(&self, input_data: &[u8], pubkey: &[u8], signature: &[u8]) -> Result<bool>;
|
fn verify_signature(
|
||||||
fn verify_stored_pubkey(&self, from_contact_id: UserID, pubkey: &[u8]) -> Result<bool>;
|
&self,
|
||||||
|
input_data: &[u8],
|
||||||
|
pubkey: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> impl Future<Output = Result<bool>> + Send;
|
||||||
|
fn verify_stored_pubkey(
|
||||||
|
&self,
|
||||||
|
from_contact_id: UserID,
|
||||||
|
pubkey: &[u8],
|
||||||
|
) -> impl Future<Output = Result<bool>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use crate::user_discovery::traits::UserDiscoveryUtils;
|
use crate::user_discovery::traits::UserDiscoveryUtils;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct TestingUtils {}
|
pub(crate) struct TestingUtils {}
|
||||||
impl UserDiscoveryUtils for TestingUtils {
|
impl UserDiscoveryUtils for TestingUtils {
|
||||||
fn sign_data(&self, _input_data: &[u8]) -> crate::user_discovery::error::Result<Vec<u8>> {
|
async fn sign_data(
|
||||||
|
&self,
|
||||||
|
_input_data: &[u8],
|
||||||
|
) -> crate::user_discovery::error::Result<Vec<u8>> {
|
||||||
Ok(vec![0; 64])
|
Ok(vec![0; 64])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_signature(
|
async fn verify_signature(
|
||||||
&self,
|
&self,
|
||||||
_data: &[u8],
|
_data: &[u8],
|
||||||
_pubkey: &[u8],
|
_pubkey: &[u8],
|
||||||
|
|
@ -81,7 +117,7 @@ pub(crate) mod tests {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_stored_pubkey(
|
async fn verify_stored_pubkey(
|
||||||
&self,
|
&self,
|
||||||
_from_contact_id: crate::user_discovery::UserID,
|
_from_contact_id: crate::user_discovery::UserID,
|
||||||
_pubkey: &[u8],
|
_pubkey: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ message UserDiscoveryMessage {
|
||||||
optional UserDiscoveryRecall user_discovery_recall = 4;
|
optional UserDiscoveryRecall user_discovery_recall = 4;
|
||||||
|
|
||||||
message UserDiscoveryAnnouncement {
|
message UserDiscoveryAnnouncement {
|
||||||
uint64 public_id = 1;
|
int64 public_id = 1;
|
||||||
uint32 threshold = 2;
|
uint32 threshold = 2;
|
||||||
bytes announcement_share = 4;
|
bytes announcement_share = 4;
|
||||||
repeated bytes verification_shares = 6;
|
repeated bytes verification_shares = 6;
|
||||||
|
|
@ -23,7 +23,7 @@ message UserDiscoveryMessage {
|
||||||
|
|
||||||
message UserDiscoveryPromotion {
|
message UserDiscoveryPromotion {
|
||||||
uint32 promotion_id = 1;
|
uint32 promotion_id = 1;
|
||||||
uint64 public_id = 2;
|
int64 public_id = 2;
|
||||||
uint32 threshold = 3;
|
uint32 threshold = 3;
|
||||||
bytes announcement_share = 5;
|
bytes announcement_share = 5;
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ message UserDiscoveryMessage {
|
||||||
|
|
||||||
message AnnouncementShareDecrypted {
|
message AnnouncementShareDecrypted {
|
||||||
message SignedData {
|
message SignedData {
|
||||||
uint64 public_id = 1;
|
int64 public_id = 1;
|
||||||
int64 user_id = 2;
|
int64 user_id = 2;
|
||||||
bytes public_key = 3;
|
bytes public_key = 3;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import 'schema_v8.dart' as v8;
|
||||||
import 'schema_v9.dart' as v9;
|
import 'schema_v9.dart' as v9;
|
||||||
import 'schema_v10.dart' as v10;
|
import 'schema_v10.dart' as v10;
|
||||||
import 'schema_v11.dart' as v11;
|
import 'schema_v11.dart' as v11;
|
||||||
|
import 'schema_v12.dart' as v12;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
|
|
@ -42,10 +43,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
return v10.DatabaseAtV10(db);
|
return v10.DatabaseAtV10(db);
|
||||||
case 11:
|
case 11:
|
||||||
return v11.DatabaseAtV11(db);
|
return v11.DatabaseAtV11(db);
|
||||||
|
case 12:
|
||||||
|
return v12.DatabaseAtV12(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9358
test/drift/twonly_db/generated/schema_v12.dart
Normal file
9358
test/drift/twonly_db/generated/schema_v12.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,7 @@ void main() {
|
||||||
late File dbFile;
|
late File dbFile;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
dbFile = File('rust/tests/testing.db');
|
dbFile = File('rust/core/tests/testing.db');
|
||||||
if (dbFile.existsSync()) {
|
if (dbFile.existsSync()) {
|
||||||
dbFile.deleteSync();
|
dbFile.deleteSync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue