remove old version and fixes duplicated shares

This commit is contained in:
otsmr 2026-04-21 22:47:21 +02:00
parent 954eedd40e
commit e1f28e1b87
21 changed files with 567 additions and 9608 deletions

View file

@ -21,7 +21,7 @@ Future<void> initFlutterCallbacks({
required FutureOr<Uint8List?> Function(PlatformInt64)
userDiscoveryGetShareForContact,
required FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion,
required FutureOr<List<Uint8List>?> Function(PlatformInt64)
userDiscoveryGetOwnPromotionsAfterVersion,
required FutureOr<bool> Function(OtherPromotion)
@ -43,7 +43,8 @@ Future<void> initFlutterCallbacks({
userDiscoveryVerifyStoredPubkey: userDiscoveryVerifyStoredPubkey,
userDiscoverySetShares: userDiscoverySetShares,
userDiscoveryGetShareForContact: userDiscoveryGetShareForContact,
userDiscoveryPushOwnPromotion: userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion:
userDiscoveryPushOwnPromotionAndClearOldVersion,
userDiscoveryGetOwnPromotionsAfterVersion:
userDiscoveryGetOwnPromotionsAfterVersion,
userDiscoveryStoreOtherPromotion: userDiscoveryStoreOtherPromotion,

View file

@ -121,7 +121,7 @@ abstract class RustLibApi extends BaseApi {
required FutureOr<Uint8List?> Function(PlatformInt64)
userDiscoveryGetShareForContact,
required FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion,
required FutureOr<List<Uint8List>?> Function(PlatformInt64)
userDiscoveryGetOwnPromotionsAfterVersion,
required FutureOr<bool> Function(OtherPromotion)
@ -354,7 +354,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required FutureOr<Uint8List?> Function(PlatformInt64)
userDiscoveryGetShareForContact,
required FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion,
required FutureOr<List<Uint8List>?> Function(PlatformInt64)
userDiscoveryGetOwnPromotionsAfterVersion,
required FutureOr<bool> Function(OtherPromotion)
@ -403,7 +403,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer,
);
sse_encode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion,
serializer,
);
sse_encode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
@ -453,7 +453,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
userDiscoveryVerifyStoredPubkey,
userDiscoverySetShares,
userDiscoveryGetShareForContact,
userDiscoveryPushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion,
userDiscoveryGetOwnPromotionsAfterVersion,
userDiscoveryStoreOtherPromotion,
userDiscoveryGetOtherPromotionsByPublicId,
@ -477,7 +477,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
"userDiscoveryVerifyStoredPubkey",
"userDiscoverySetShares",
"userDiscoveryGetShareForContact",
"userDiscoveryPushOwnPromotion",
"userDiscoveryPushOwnPromotionAndClearOldVersion",
"userDiscoveryGetOwnPromotionsAfterVersion",
"userDiscoveryStoreOtherPromotion",
"userDiscoveryGetOtherPromotionsByPublicId",

View file

@ -8,7 +8,8 @@ Future<void> initFlutterCallbacksForRust() async {
userDiscoverySetShares: UserDiscoveryCallbacks.setShares,
userDiscoveryGetShareForContact:
UserDiscoveryCallbacks.userDiscoveryGetShareForContact,
userDiscoveryPushOwnPromotion: UserDiscoveryCallbacks.pushOwnPromotion,
userDiscoveryPushOwnPromotionAndClearOldVersion:
UserDiscoveryCallbacks.userDiscoveryPushOwnPromotionAndClearOldVersion,
userDiscoveryPushNewUserRelation:
UserDiscoveryCallbacks.pushNewUserRelation,
userDiscoveryGetOwnPromotionsAfterVersion:

View file

@ -110,12 +110,18 @@ class UserDiscoveryCallbacks {
});
}
static Future<bool> pushOwnPromotion(
static Future<bool> userDiscoveryPushOwnPromotionAndClearOldVersion(
int contactId,
int version, // Maps to versionId or logic control
int version,
Uint8List promotion,
) async {
try {
// Old promotions from this users should be removed...
await (twonlyDB.update(
twonlyDB.userDiscoveryOwnPromotions,
)..where((t) => t.contactId.equals(contactId))).write(
UserDiscoveryOwnPromotionsCompanion(promotion: Value(Uint8List(0))),
);
await twonlyDB
.into(twonlyDB.userDiscoveryOwnPromotions)
.insert(

View file

@ -178,6 +178,40 @@
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "user_discovery_excluded",
"getter_name": "userDiscoveryExcluded",
"moor_type": "bool",
"nullable": false,
"customConstraints": null,
"defaultConstraints": "CHECK (\"user_discovery_excluded\" IN (0, 1))",
"dialectAwareDefaultConstraints": {
"sqlite": "CHECK (\"user_discovery_excluded\" IN (0, 1))"
},
"default_dart": "const CustomExpression('0')",
"default_client_dart": null,
"dsl_features": []
},
{
"name": "media_send_counter",
"getter_name": "mediaSendCounter",
"moor_type": "int",
"nullable": false,
"customConstraints": null,
"default_dart": "const CustomExpression('0')",
"default_client_dart": null,
"dsl_features": []
},
{
"name": "media_received_counter",
"getter_name": "mediaReceivedCounter",
"moor_type": "int",
"nullable": false,
"customConstraints": null,
"default_dart": "const CustomExpression('0')",
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
@ -2126,6 +2160,44 @@
"dsl_features": [
"unique"
]
},
{
"name": "username",
"getter_name": "username",
"moor_type": "string",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "was_shown_to_the_user",
"getter_name": "wasShownToTheUser",
"moor_type": "bool",
"nullable": false,
"customConstraints": null,
"defaultConstraints": "CHECK (\"was_shown_to_the_user\" IN (0, 1))",
"dialectAwareDefaultConstraints": {
"sqlite": "CHECK (\"was_shown_to_the_user\" IN (0, 1))"
},
"default_dart": "const CustomExpression('0')",
"default_client_dart": null,
"dsl_features": []
},
{
"name": "is_hidden",
"getter_name": "isHidden",
"moor_type": "bool",
"nullable": false,
"customConstraints": null,
"defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))",
"dialectAwareDefaultConstraints": {
"sqlite": "CHECK (\"is_hidden\" IN (0, 1))"
},
"default_dart": "const CustomExpression('0')",
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
@ -2311,7 +2383,7 @@
"constraints": [],
"explicit_pk": [
"from_contact_id",
"promotion_id"
"public_id"
]
}
},
@ -2458,7 +2530,7 @@
"sql": [
{
"dialect": "sqlite",
"sql": "CREATE TABLE IF NOT EXISTS \"contacts\" (\"user_id\" INTEGER NOT NULL, \"username\" TEXT NOT NULL, \"display_name\" TEXT NULL, \"nick_name\" TEXT NULL, \"avatar_svg_compressed\" BLOB NULL, \"sender_profile_counter\" INTEGER NOT NULL DEFAULT 0, \"accepted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"accepted\" IN (0, 1)), \"deleted_by_user\" INTEGER NOT NULL DEFAULT 0 CHECK (\"deleted_by_user\" IN (0, 1)), \"requested\" INTEGER NOT NULL DEFAULT 0 CHECK (\"requested\" IN (0, 1)), \"blocked\" INTEGER NOT NULL DEFAULT 0 CHECK (\"blocked\" IN (0, 1)), \"verified\" INTEGER NOT NULL DEFAULT 0 CHECK (\"verified\" IN (0, 1)), \"account_deleted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"account_deleted\" IN (0, 1)), \"created_at\" INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), \"user_discovery_version\" BLOB NULL, PRIMARY KEY (\"user_id\"));"
"sql": "CREATE TABLE IF NOT EXISTS \"contacts\" (\"user_id\" INTEGER NOT NULL, \"username\" TEXT NOT NULL, \"display_name\" TEXT NULL, \"nick_name\" TEXT NULL, \"avatar_svg_compressed\" BLOB NULL, \"sender_profile_counter\" INTEGER NOT NULL DEFAULT 0, \"accepted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"accepted\" IN (0, 1)), \"deleted_by_user\" INTEGER NOT NULL DEFAULT 0 CHECK (\"deleted_by_user\" IN (0, 1)), \"requested\" INTEGER NOT NULL DEFAULT 0 CHECK (\"requested\" IN (0, 1)), \"blocked\" INTEGER NOT NULL DEFAULT 0 CHECK (\"blocked\" IN (0, 1)), \"verified\" INTEGER NOT NULL DEFAULT 0 CHECK (\"verified\" IN (0, 1)), \"account_deleted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"account_deleted\" IN (0, 1)), \"created_at\" INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), \"user_discovery_version\" BLOB NULL, \"user_discovery_excluded\" INTEGER NOT NULL DEFAULT 0 CHECK (\"user_discovery_excluded\" IN (0, 1)), \"media_send_counter\" INTEGER NOT NULL DEFAULT 0, \"media_received_counter\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"user_id\"));"
}
]
},
@ -2611,7 +2683,7 @@
"sql": [
{
"dialect": "sqlite",
"sql": "CREATE TABLE IF NOT EXISTS \"user_discovery_announced_users\" (\"announced_user_id\" INTEGER NOT NULL, \"announced_public_key\" BLOB NOT NULL, \"public_id\" INTEGER NOT NULL UNIQUE, PRIMARY KEY (\"announced_user_id\"));"
"sql": "CREATE TABLE IF NOT EXISTS \"user_discovery_announced_users\" (\"announced_user_id\" INTEGER NOT NULL, \"announced_public_key\" BLOB NOT NULL, \"public_id\" INTEGER NOT NULL UNIQUE, \"username\" TEXT NULL, \"was_shown_to_the_user\" INTEGER NOT NULL DEFAULT 0 CHECK (\"was_shown_to_the_user\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_hidden\" IN (0, 1)), PRIMARY KEY (\"announced_user_id\"));"
}
]
},
@ -2629,7 +2701,7 @@
"sql": [
{
"dialect": "sqlite",
"sql": "CREATE TABLE IF NOT EXISTS \"user_discovery_other_promotions\" (\"from_contact_id\" INTEGER NOT NULL REFERENCES contacts (user_id) ON DELETE CASCADE, \"promotion_id\" INTEGER NOT NULL, \"public_id\" INTEGER NOT NULL, \"threshold\" INTEGER NOT NULL, \"announcement_share\" BLOB NOT NULL, \"public_key_verified_timestamp\" INTEGER NULL, PRIMARY KEY (\"from_contact_id\", \"promotion_id\"));"
"sql": "CREATE TABLE IF NOT EXISTS \"user_discovery_other_promotions\" (\"from_contact_id\" INTEGER NOT NULL REFERENCES contacts (user_id) ON DELETE CASCADE, \"promotion_id\" INTEGER NOT NULL, \"public_id\" INTEGER NOT NULL, \"threshold\" INTEGER NOT NULL, \"announcement_share\" BLOB NOT NULL, \"public_key_verified_timestamp\" INTEGER NULL, PRIMARY KEY (\"from_contact_id\", \"public_id\"));"
}
]
},

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -70,7 +70,7 @@ class UserDiscoveryOtherPromotions extends Table {
DateTimeColumn get publicKeyVerifiedTimestamp => dateTime().nullable()();
@override
Set<Column> get primaryKey => {fromContactId, promotionId};
Set<Column> get primaryKey => {fromContactId, publicId};
}
// unused_shares: Vec<Vec<u8>>,

View file

@ -72,7 +72,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 15;
int get schemaVersion => 12;
static QueryExecutor _openConnection() {
return driftDatabase(
@ -177,8 +177,6 @@ class TwonlyDB extends _$TwonlyDB {
schema.contacts,
schema.contacts.userDiscoveryVersion,
);
},
from12To13: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.mediaReceivedCounter,
@ -187,22 +185,6 @@ class TwonlyDB extends _$TwonlyDB {
schema.contacts,
schema.contacts.mediaSendCounter,
);
},
from13To14: (m, schema) async {
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.wasShownToTheUser,
);
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.isHidden,
);
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.username,
);
},
from14To15: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.userDiscoveryExcluded,

View file

@ -10478,7 +10478,7 @@ class $UserDiscoveryOtherPromotionsTable extends UserDiscoveryOtherPromotions
}
@override
Set<GeneratedColumn> get $primaryKey => {fromContactId, promotionId};
Set<GeneratedColumn> get $primaryKey => {fromContactId, publicId};
@override
UserDiscoveryOtherPromotion map(
Map<String, dynamic> data, {

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ callback_generator! {
// UserDiscoveryStore
set_shares: (Vec<Vec<u8>>) => bool,
get_share_for_contact: (i64) => Option<Vec<u8>>,
push_own_promotion: (i64, i64, Vec<u8>) => bool,
push_own_promotion_and_clear_old_version: (i64, i64, Vec<u8>) => bool,
get_own_promotions_after_version: (i64) => Option<Vec<Vec<u8>>>,
store_other_promotion: (OtherPromotion) => bool,
get_other_promotions_by_public_id: (i64) => Option<Vec<OtherPromotion>>,

View file

@ -79,13 +79,13 @@ impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
}
}
async fn push_own_promotion(
async fn push_own_promotion_and_clear_old_version(
&self,
contact_id: i64,
version: u32,
promotion: Vec<u8>,
) -> Result<()> {
(get_callbacks()?.user_discovery.push_own_promotion)(contact_id, version as i64, promotion)
(get_callbacks()?.user_discovery.push_own_promotion_and_clear_old_version)(contact_id, version as i64, promotion)
.await
.then_some(())
.ok_or(TwonlyError::DartError.into())

View file

@ -141,7 +141,7 @@ let api_user_discovery_verify_signature = decode_DartFn_Inputs_list_prim_u_8_str
let api_user_discovery_verify_stored_pubkey = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_set_shares = decode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_get_share_for_contact = decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_push_own_promotion = decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_push_own_promotion_and_clear_old_version = decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_get_own_promotions_after_version = decode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_store_other_promotion = decode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_get_other_promotions_by_public_id = decode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
@ -150,7 +150,7 @@ let api_user_discovery_get_contact_version = decode_DartFn_Inputs_i_64_Output_op
let api_user_discovery_set_contact_version = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_push_new_user_relation = decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| {
transform_result_sse::<_, ()>((move || {
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation); })?; Ok(output_ok)
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion_and_clear_old_version, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation); })?; Ok(output_ok)
})())
} })
}

View file

@ -4,7 +4,7 @@ pub mod stores;
pub mod tests;
pub mod traits;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::u8;
use blahaj::{Share, Sharks};
use prost::Message;
@ -447,7 +447,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
};
self.store
.push_own_promotion(
.push_own_promotion_and_clear_old_version(
contact_id,
config.promotion_version,
message.encode_to_vec(),
@ -525,10 +525,21 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
.get_other_promotions_by_public_id(udp.public_id)
.await?;
if promotions.len() < udp.threshold as usize {
// Deduplicate shares by their raw bytes to prevent invalid Shamir's Secret Sharing recoveries.
// Multiple identical shares (e.g. due to contact resending promotions, or DB duplicate writes)
// will cause `recover` to interpolate incorrectly and return garbage bytes.
let mut unique_shares_set = HashSet::new();
let mut unique_promotions = Vec::new();
for p in promotions {
if unique_shares_set.insert(p.announcement_share.clone()) {
unique_promotions.push(p);
}
}
if unique_promotions.len() < udp.threshold as usize {
tracing::debug!(
"Not enough shares ({} < {}) to decrypt announcement. Waiting for next share.",
promotions.len(),
"Not enough unique shares ({} < {}) to decrypt announcement. Waiting for next share.",
unique_promotions.len(),
udp.threshold
);
return Ok(());
@ -536,7 +547,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
tracing::debug!("Enough shares decrypting announcement.");
let shares: Vec<_> = promotions
let shares: Vec<_> = unique_promotions
.iter()
.map(|x| x.announcement_share.to_owned())
.filter_map(|x| Share::try_from(x.as_slice()).ok())
@ -577,7 +588,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
};
let user_id = self.get_config().await?.user_id;
for promotion in promotions {
for promotion in unique_promotions {
// Do not store the announcement of the users itself.
// Or in case the promotion promotes myself
if promotion.from_contact_id == announced_user.user_id

View file

@ -66,17 +66,24 @@ impl UserDiscoveryStore for InMemoryStore {
Ok(())
}
async fn push_own_promotion(
async fn push_own_promotion_and_clear_old_version(
&self,
contact_id: UserID,
version: u32,
promotion: Vec<u8>,
) -> Result<()> {
let mut storage = self.storage();
// println!("{} != {}", version, storage.promotions.len());
if version as usize != storage.own_promotions.len() + 1 {
return Err(UserDiscoveryError::PushedInvalidVersion);
}
for (old_contact_id, promotion) in storage.own_promotions.iter_mut() {
if *old_contact_id == contact_id {
promotion.clear();
}
}
storage.own_promotions.push((contact_id, promotion));
Ok(())
}

View file

@ -112,7 +112,7 @@ async fn get_with_five_users<S: UserDiscoveryStore + Default + Clone>() -> TestN
}
#[tokio::test]
async fn test_user_discovery_dynamic_threshold_in_memory_store() {
async fn test_user_discovery_decreased_threshold_in_memory_store() {
let _ = pretty_env_logger::try_init();
let mut network = TestNetwork::<InMemoryStore>::new();
@ -196,6 +196,84 @@ async fn test_user_discovery_dynamic_threshold_in_memory_store() {
);
}
#[tokio::test]
async fn test_user_discovery_increased_threshold_in_memory_store() {
let _ = pretty_env_logger::try_init();
let mut network = TestNetwork::<InMemoryStore>::new();
// Start ALICE with a more strict threshold of 3.
// David only has 2 paths to Alice (via Bob and Charlie). Since 2 < 3, David cannot discover Alice.
network.add_user("ALICE", 2).await;
network.add_user("BOB", 2).await;
network.add_user("CHARLIE", 2).await;
network.add_user("DAVID", 2).await;
network.add_user("FRANK", 2).await;
// Same topology as the initial 5 users
network.set_friends("ALICE", &["BOB", "CHARLIE"]);
network.set_friends("BOB", &["ALICE", "CHARLIE", "DAVID"]);
// CHARLIE IS NOT YET A FRIEND OF DAVID -> DAVID SHOULD NOT BE ABLE TO BE DECODE ALICE
network.set_friends("CHARLIE", &["ALICE", "BOB", "FRANK"]);
network.set_friends("DAVID", &["BOB", "CHARLIE"]);
network.set_friends("FRANK", &["CHARLIE"]);
let david_idx = network.ids_by_name["DAVID"];
let alice_idx = network.ids_by_name["ALICE"];
// Phase 1: Exchange with ALICE threshold = 2
step0_exchange_random::<InMemoryStore>(&network).await;
step1_verify_no_new_messages::<InMemoryStore>(&network).await;
// DAVID should NOT know ALICE yet because ALICE's threshold is 2, and David has only 1 shares.
{
let david_knows = network.uds[david_idx]
.get_all_announced_users()
.await
.unwrap();
let knows_alice = david_knows
.iter()
.any(|(u, _)| u.user_id == alice_idx as UserID);
assert!(!knows_alice, "David should not know Alice yet because Alice's threshold is 3 and David only receives 2 shares");
}
// Phase 2: Update ALICE's threshold to 3
network.uds[alice_idx]
.initialize_or_update(3, alice_idx as UserID, vec![alice_idx as u8; 32])
.await
.unwrap();
// ALICE's new announcement with threshold 3 should propagate further.
// This SHOULD REPLACE THE OLD VERSION...
step0_exchange_random::<InMemoryStore>(&network).await;
step1_verify_no_new_messages::<InMemoryStore>(&network).await;
// Now Charlie is a friend of David, so he should exchange ALICE with him
network.set_friends("CHARLIE", &["ALICE", "BOB", "DAVID", "FRANK"]);
// ALICE's new announcement with threshold 3 should propagate further.
// This SHOULD REPLACE THE OLD VERSION...
step0_exchange_random::<InMemoryStore>(&network).await;
step1_verify_no_new_messages::<InMemoryStore>(&network).await;
// DAVID should still NOT know ALICE yet because ALICE's new threshold is 3, and David has only 2 shares.
{
let david_knows = network.uds[david_idx]
.get_all_announced_users()
.await
.unwrap();
let knows_alice = david_knows
.iter()
.any(|(u, _)| u.user_id == alice_idx as UserID);
assert!(!knows_alice, "David should not know Alice yet because Alice's threshold is 3 and David only receives 2 shares");
}
}
#[tokio::test]
async fn test_user_discovery_in_memory_store() {
let _ = pretty_env_logger::try_init();

View file

@ -31,7 +31,7 @@ pub trait UserDiscoveryStore {
contact_id: UserID,
) -> impl Future<Output = Result<Vec<u8>>> + Send;
fn push_own_promotion(
fn push_own_promotion_and_clear_old_version(
&self,
contact_id: UserID,
version: u32,

View file

@ -16,9 +16,6 @@ import 'schema_v9.dart' as v9;
import 'schema_v10.dart' as v10;
import 'schema_v11.dart' as v11;
import 'schema_v12.dart' as v12;
import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14;
import 'schema_v15.dart' as v15;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -48,32 +45,10 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v11.DatabaseAtV11(db);
case 12:
return v12.DatabaseAtV12(db);
case 13:
return v13.DatabaseAtV13(db);
case 14:
return v14.DatabaseAtV14(db);
case 15:
return v15.DatabaseAtV15(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
];
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
}

View file

@ -135,6 +135,34 @@ class Contacts extends Table with TableInfo<Contacts, ContactsData> {
requiredDuringInsert: false,
$customConstraints: 'NULL',
);
late final GeneratedColumn<int> userDiscoveryExcluded = GeneratedColumn<int>(
'user_discovery_excluded',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints:
'NOT NULL DEFAULT 0 CHECK (user_discovery_excluded IN (0, 1))',
defaultValue: const CustomExpression('0'),
);
late final GeneratedColumn<int> mediaSendCounter = GeneratedColumn<int>(
'media_send_counter',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL DEFAULT 0',
defaultValue: const CustomExpression('0'),
);
late final GeneratedColumn<int> mediaReceivedCounter = GeneratedColumn<int>(
'media_received_counter',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL DEFAULT 0',
defaultValue: const CustomExpression('0'),
);
@override
List<GeneratedColumn> get $columns => [
userId,
@ -151,6 +179,9 @@ class Contacts extends Table with TableInfo<Contacts, ContactsData> {
accountDeleted,
createdAt,
userDiscoveryVersion,
userDiscoveryExcluded,
mediaSendCounter,
mediaReceivedCounter,
];
@override
String get aliasedName => _alias ?? actualTableName;
@ -219,6 +250,18 @@ class Contacts extends Table with TableInfo<Contacts, ContactsData> {
DriftSqlType.blob,
data['${effectivePrefix}user_discovery_version'],
),
userDiscoveryExcluded: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}user_discovery_excluded'],
)!,
mediaSendCounter: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}media_send_counter'],
)!,
mediaReceivedCounter: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}media_received_counter'],
)!,
);
}
@ -248,6 +291,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
final int accountDeleted;
final int createdAt;
final i2.Uint8List? userDiscoveryVersion;
final int userDiscoveryExcluded;
final int mediaSendCounter;
final int mediaReceivedCounter;
const ContactsData({
required this.userId,
required this.username,
@ -263,6 +309,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
required this.accountDeleted,
required this.createdAt,
this.userDiscoveryVersion,
required this.userDiscoveryExcluded,
required this.mediaSendCounter,
required this.mediaReceivedCounter,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -293,6 +342,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
userDiscoveryVersion,
);
}
map['user_discovery_excluded'] = Variable<int>(userDiscoveryExcluded);
map['media_send_counter'] = Variable<int>(mediaSendCounter);
map['media_received_counter'] = Variable<int>(mediaReceivedCounter);
return map;
}
@ -320,6 +372,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
userDiscoveryVersion: userDiscoveryVersion == null && nullToAbsent
? const Value.absent()
: Value(userDiscoveryVersion),
userDiscoveryExcluded: Value(userDiscoveryExcluded),
mediaSendCounter: Value(mediaSendCounter),
mediaReceivedCounter: Value(mediaReceivedCounter),
);
}
@ -349,6 +404,13 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
userDiscoveryVersion: serializer.fromJson<i2.Uint8List?>(
json['userDiscoveryVersion'],
),
userDiscoveryExcluded: serializer.fromJson<int>(
json['userDiscoveryExcluded'],
),
mediaSendCounter: serializer.fromJson<int>(json['mediaSendCounter']),
mediaReceivedCounter: serializer.fromJson<int>(
json['mediaReceivedCounter'],
),
);
}
@override
@ -373,6 +435,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
'userDiscoveryVersion': serializer.toJson<i2.Uint8List?>(
userDiscoveryVersion,
),
'userDiscoveryExcluded': serializer.toJson<int>(userDiscoveryExcluded),
'mediaSendCounter': serializer.toJson<int>(mediaSendCounter),
'mediaReceivedCounter': serializer.toJson<int>(mediaReceivedCounter),
};
}
@ -391,6 +456,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
int? accountDeleted,
int? createdAt,
Value<i2.Uint8List?> userDiscoveryVersion = const Value.absent(),
int? userDiscoveryExcluded,
int? mediaSendCounter,
int? mediaReceivedCounter,
}) => ContactsData(
userId: userId ?? this.userId,
username: username ?? this.username,
@ -410,6 +478,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
userDiscoveryVersion: userDiscoveryVersion.present
? userDiscoveryVersion.value
: this.userDiscoveryVersion,
userDiscoveryExcluded: userDiscoveryExcluded ?? this.userDiscoveryExcluded,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
);
ContactsData copyWithCompanion(ContactsCompanion data) {
return ContactsData(
@ -439,6 +510,15 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
userDiscoveryVersion: data.userDiscoveryVersion.present
? data.userDiscoveryVersion.value
: this.userDiscoveryVersion,
userDiscoveryExcluded: data.userDiscoveryExcluded.present
? data.userDiscoveryExcluded.value
: this.userDiscoveryExcluded,
mediaSendCounter: data.mediaSendCounter.present
? data.mediaSendCounter.value
: this.mediaSendCounter,
mediaReceivedCounter: data.mediaReceivedCounter.present
? data.mediaReceivedCounter.value
: this.mediaReceivedCounter,
);
}
@ -458,7 +538,10 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
..write('verified: $verified, ')
..write('accountDeleted: $accountDeleted, ')
..write('createdAt: $createdAt, ')
..write('userDiscoveryVersion: $userDiscoveryVersion')
..write('userDiscoveryVersion: $userDiscoveryVersion, ')
..write('userDiscoveryExcluded: $userDiscoveryExcluded, ')
..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter')
..write(')'))
.toString();
}
@ -479,6 +562,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
accountDeleted,
createdAt,
$driftBlobEquality.hash(userDiscoveryVersion),
userDiscoveryExcluded,
mediaSendCounter,
mediaReceivedCounter,
);
@override
bool operator ==(Object other) =>
@ -503,7 +589,10 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
$driftBlobEquality.equals(
other.userDiscoveryVersion,
this.userDiscoveryVersion,
));
) &&
other.userDiscoveryExcluded == this.userDiscoveryExcluded &&
other.mediaSendCounter == this.mediaSendCounter &&
other.mediaReceivedCounter == this.mediaReceivedCounter);
}
class ContactsCompanion extends UpdateCompanion<ContactsData> {
@ -521,6 +610,9 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
final Value<int> accountDeleted;
final Value<int> createdAt;
final Value<i2.Uint8List?> userDiscoveryVersion;
final Value<int> userDiscoveryExcluded;
final Value<int> mediaSendCounter;
final Value<int> mediaReceivedCounter;
const ContactsCompanion({
this.userId = const Value.absent(),
this.username = const Value.absent(),
@ -536,6 +628,9 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
this.accountDeleted = const Value.absent(),
this.createdAt = const Value.absent(),
this.userDiscoveryVersion = const Value.absent(),
this.userDiscoveryExcluded = const Value.absent(),
this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(),
});
ContactsCompanion.insert({
this.userId = const Value.absent(),
@ -552,6 +647,9 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
this.accountDeleted = const Value.absent(),
this.createdAt = const Value.absent(),
this.userDiscoveryVersion = const Value.absent(),
this.userDiscoveryExcluded = const Value.absent(),
this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(),
}) : username = Value(username);
static Insertable<ContactsData> custom({
Expression<int>? userId,
@ -568,6 +666,9 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
Expression<int>? accountDeleted,
Expression<int>? createdAt,
Expression<i2.Uint8List>? userDiscoveryVersion,
Expression<int>? userDiscoveryExcluded,
Expression<int>? mediaSendCounter,
Expression<int>? mediaReceivedCounter,
}) {
return RawValuesInsertable({
if (userId != null) 'user_id': userId,
@ -587,6 +688,11 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
if (createdAt != null) 'created_at': createdAt,
if (userDiscoveryVersion != null)
'user_discovery_version': userDiscoveryVersion,
if (userDiscoveryExcluded != null)
'user_discovery_excluded': userDiscoveryExcluded,
if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter,
if (mediaReceivedCounter != null)
'media_received_counter': mediaReceivedCounter,
});
}
@ -605,6 +711,9 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
Value<int>? accountDeleted,
Value<int>? createdAt,
Value<i2.Uint8List?>? userDiscoveryVersion,
Value<int>? userDiscoveryExcluded,
Value<int>? mediaSendCounter,
Value<int>? mediaReceivedCounter,
}) {
return ContactsCompanion(
userId: userId ?? this.userId,
@ -621,6 +730,10 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
accountDeleted: accountDeleted ?? this.accountDeleted,
createdAt: createdAt ?? this.createdAt,
userDiscoveryVersion: userDiscoveryVersion ?? this.userDiscoveryVersion,
userDiscoveryExcluded:
userDiscoveryExcluded ?? this.userDiscoveryExcluded,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
);
}
@ -673,6 +786,17 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
userDiscoveryVersion.value,
);
}
if (userDiscoveryExcluded.present) {
map['user_discovery_excluded'] = Variable<int>(
userDiscoveryExcluded.value,
);
}
if (mediaSendCounter.present) {
map['media_send_counter'] = Variable<int>(mediaSendCounter.value);
}
if (mediaReceivedCounter.present) {
map['media_received_counter'] = Variable<int>(mediaReceivedCounter.value);
}
return map;
}
@ -692,7 +816,10 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
..write('verified: $verified, ')
..write('accountDeleted: $accountDeleted, ')
..write('createdAt: $createdAt, ')
..write('userDiscoveryVersion: $userDiscoveryVersion')
..write('userDiscoveryVersion: $userDiscoveryVersion, ')
..write('userDiscoveryExcluded: $userDiscoveryExcluded, ')
..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter')
..write(')'))
.toString();
}
@ -7813,11 +7940,41 @@ class UserDiscoveryAnnouncedUsers extends Table
requiredDuringInsert: true,
$customConstraints: 'NOT NULL UNIQUE',
);
late final GeneratedColumn<String> username = GeneratedColumn<String>(
'username',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: 'NULL',
);
late final GeneratedColumn<int> wasShownToTheUser = GeneratedColumn<int>(
'was_shown_to_the_user',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints:
'NOT NULL DEFAULT 0 CHECK (was_shown_to_the_user IN (0, 1))',
defaultValue: const CustomExpression('0'),
);
late final GeneratedColumn<int> isHidden = GeneratedColumn<int>(
'is_hidden',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_hidden IN (0, 1))',
defaultValue: const CustomExpression('0'),
);
@override
List<GeneratedColumn> get $columns => [
announcedUserId,
announcedPublicKey,
publicId,
username,
wasShownToTheUser,
isHidden,
];
@override
String get aliasedName => _alias ?? actualTableName;
@ -7845,6 +8002,18 @@ class UserDiscoveryAnnouncedUsers extends Table
DriftSqlType.int,
data['${effectivePrefix}public_id'],
)!,
username: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}username'],
),
wasShownToTheUser: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}was_shown_to_the_user'],
)!,
isHidden: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}is_hidden'],
)!,
);
}
@ -7866,10 +8035,16 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
final int announcedUserId;
final i2.Uint8List announcedPublicKey;
final int publicId;
final String? username;
final int wasShownToTheUser;
final int isHidden;
const UserDiscoveryAnnouncedUsersData({
required this.announcedUserId,
required this.announcedPublicKey,
required this.publicId,
this.username,
required this.wasShownToTheUser,
required this.isHidden,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -7877,6 +8052,11 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
map['announced_user_id'] = Variable<int>(announcedUserId);
map['announced_public_key'] = Variable<i2.Uint8List>(announcedPublicKey);
map['public_id'] = Variable<int>(publicId);
if (!nullToAbsent || username != null) {
map['username'] = Variable<String>(username);
}
map['was_shown_to_the_user'] = Variable<int>(wasShownToTheUser);
map['is_hidden'] = Variable<int>(isHidden);
return map;
}
@ -7885,6 +8065,11 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
announcedUserId: Value(announcedUserId),
announcedPublicKey: Value(announcedPublicKey),
publicId: Value(publicId),
username: username == null && nullToAbsent
? const Value.absent()
: Value(username),
wasShownToTheUser: Value(wasShownToTheUser),
isHidden: Value(isHidden),
);
}
@ -7899,6 +8084,9 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
json['announcedPublicKey'],
),
publicId: serializer.fromJson<int>(json['publicId']),
username: serializer.fromJson<String?>(json['username']),
wasShownToTheUser: serializer.fromJson<int>(json['wasShownToTheUser']),
isHidden: serializer.fromJson<int>(json['isHidden']),
);
}
@override
@ -7908,6 +8096,9 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
'announcedUserId': serializer.toJson<int>(announcedUserId),
'announcedPublicKey': serializer.toJson<i2.Uint8List>(announcedPublicKey),
'publicId': serializer.toJson<int>(publicId),
'username': serializer.toJson<String?>(username),
'wasShownToTheUser': serializer.toJson<int>(wasShownToTheUser),
'isHidden': serializer.toJson<int>(isHidden),
};
}
@ -7915,10 +8106,16 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
int? announcedUserId,
i2.Uint8List? announcedPublicKey,
int? publicId,
Value<String?> username = const Value.absent(),
int? wasShownToTheUser,
int? isHidden,
}) => UserDiscoveryAnnouncedUsersData(
announcedUserId: announcedUserId ?? this.announcedUserId,
announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey,
publicId: publicId ?? this.publicId,
username: username.present ? username.value : this.username,
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
isHidden: isHidden ?? this.isHidden,
);
UserDiscoveryAnnouncedUsersData copyWithCompanion(
UserDiscoveryAnnouncedUsersCompanion data,
@ -7931,6 +8128,11 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
? data.announcedPublicKey.value
: this.announcedPublicKey,
publicId: data.publicId.present ? data.publicId.value : this.publicId,
username: data.username.present ? data.username.value : this.username,
wasShownToTheUser: data.wasShownToTheUser.present
? data.wasShownToTheUser.value
: this.wasShownToTheUser,
isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden,
);
}
@ -7939,7 +8141,10 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
return (StringBuffer('UserDiscoveryAnnouncedUsersData(')
..write('announcedUserId: $announcedUserId, ')
..write('announcedPublicKey: $announcedPublicKey, ')
..write('publicId: $publicId')
..write('publicId: $publicId, ')
..write('username: $username, ')
..write('wasShownToTheUser: $wasShownToTheUser, ')
..write('isHidden: $isHidden')
..write(')'))
.toString();
}
@ -7949,6 +8154,9 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
announcedUserId,
$driftBlobEquality.hash(announcedPublicKey),
publicId,
username,
wasShownToTheUser,
isHidden,
);
@override
bool operator ==(Object other) =>
@ -7959,7 +8167,10 @@ class UserDiscoveryAnnouncedUsersData extends DataClass
other.announcedPublicKey,
this.announcedPublicKey,
) &&
other.publicId == this.publicId);
other.publicId == this.publicId &&
other.username == this.username &&
other.wasShownToTheUser == this.wasShownToTheUser &&
other.isHidden == this.isHidden);
}
class UserDiscoveryAnnouncedUsersCompanion
@ -7967,27 +8178,42 @@ class UserDiscoveryAnnouncedUsersCompanion
final Value<int> announcedUserId;
final Value<i2.Uint8List> announcedPublicKey;
final Value<int> publicId;
final Value<String?> username;
final Value<int> wasShownToTheUser;
final Value<int> isHidden;
const UserDiscoveryAnnouncedUsersCompanion({
this.announcedUserId = const Value.absent(),
this.announcedPublicKey = const Value.absent(),
this.publicId = const Value.absent(),
this.username = const Value.absent(),
this.wasShownToTheUser = const Value.absent(),
this.isHidden = const Value.absent(),
});
UserDiscoveryAnnouncedUsersCompanion.insert({
this.announcedUserId = const Value.absent(),
required i2.Uint8List announcedPublicKey,
required int publicId,
this.username = const Value.absent(),
this.wasShownToTheUser = const Value.absent(),
this.isHidden = const Value.absent(),
}) : announcedPublicKey = Value(announcedPublicKey),
publicId = Value(publicId);
static Insertable<UserDiscoveryAnnouncedUsersData> custom({
Expression<int>? announcedUserId,
Expression<i2.Uint8List>? announcedPublicKey,
Expression<int>? publicId,
Expression<String>? username,
Expression<int>? wasShownToTheUser,
Expression<int>? isHidden,
}) {
return RawValuesInsertable({
if (announcedUserId != null) 'announced_user_id': announcedUserId,
if (announcedPublicKey != null)
'announced_public_key': announcedPublicKey,
if (publicId != null) 'public_id': publicId,
if (username != null) 'username': username,
if (wasShownToTheUser != null) 'was_shown_to_the_user': wasShownToTheUser,
if (isHidden != null) 'is_hidden': isHidden,
});
}
@ -7995,11 +8221,17 @@ class UserDiscoveryAnnouncedUsersCompanion
Value<int>? announcedUserId,
Value<i2.Uint8List>? announcedPublicKey,
Value<int>? publicId,
Value<String?>? username,
Value<int>? wasShownToTheUser,
Value<int>? isHidden,
}) {
return UserDiscoveryAnnouncedUsersCompanion(
announcedUserId: announcedUserId ?? this.announcedUserId,
announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey,
publicId: publicId ?? this.publicId,
username: username ?? this.username,
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
isHidden: isHidden ?? this.isHidden,
);
}
@ -8017,6 +8249,15 @@ class UserDiscoveryAnnouncedUsersCompanion
if (publicId.present) {
map['public_id'] = Variable<int>(publicId.value);
}
if (username.present) {
map['username'] = Variable<String>(username.value);
}
if (wasShownToTheUser.present) {
map['was_shown_to_the_user'] = Variable<int>(wasShownToTheUser.value);
}
if (isHidden.present) {
map['is_hidden'] = Variable<int>(isHidden.value);
}
return map;
}
@ -8025,7 +8266,10 @@ class UserDiscoveryAnnouncedUsersCompanion
return (StringBuffer('UserDiscoveryAnnouncedUsersCompanion(')
..write('announcedUserId: $announcedUserId, ')
..write('announcedPublicKey: $announcedPublicKey, ')
..write('publicId: $publicId')
..write('publicId: $publicId, ')
..write('username: $username, ')
..write('wasShownToTheUser: $wasShownToTheUser, ')
..write('isHidden: $isHidden')
..write(')'))
.toString();
}
@ -8377,7 +8621,7 @@ class UserDiscoveryOtherPromotions extends Table
String get actualTableName => $name;
static const String $name = 'user_discovery_other_promotions';
@override
Set<GeneratedColumn> get $primaryKey => {fromContactId, promotionId};
Set<GeneratedColumn> get $primaryKey => {fromContactId, publicId};
@override
UserDiscoveryOtherPromotionsData map(
Map<String, dynamic> data, {
@ -8419,7 +8663,7 @@ class UserDiscoveryOtherPromotions extends Table
@override
List<String> get customConstraints => const [
'PRIMARY KEY(from_contact_id, promotion_id)',
'PRIMARY KEY(from_contact_id, public_id)',
];
@override
bool get dontWriteConstraints => true;