diff --git a/.gitignore b/.gitignore index 5451128a..f87ae92b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ *.sqlite-wal migrate_working_dir/ +fastlane/report.xml +fastlane/README.md + # IntelliJ related *.iml *.ipr diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d58459..c5654273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.2.18 +## 0.2.20 - New: Adds an "Ask a Friend" button to new contact suggestions. - New: Adds security profiles. diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart index 8077d76f..706f8545 100644 --- a/lib/src/services/api/client2client/contact.c2c.dart +++ b/lib/src/services/api/client2client/contact.c2c.dart @@ -28,7 +28,6 @@ Future handleNewContactRequest(int fromUserId) async { await handleContactAccept(fromUserId); } - // contact was already accepted, so just accept the request in the background. await sendCipherText( contact.userId, EncryptedContent( @@ -36,6 +35,7 @@ Future handleNewContactRequest(int fromUserId) async { type: EncryptedContent_ContactRequest_Type.ACCEPT, ), ), + blocking: false, ); return true; } @@ -238,6 +238,7 @@ Future checkForProfileUpdate( type: EncryptedContent_ContactUpdate_Type.REQUEST, ), ), + blocking: false, ); } } diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index 65876ff9..324af230 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -17,9 +17,7 @@ Future handleGroupCreate( EncryptedContent_GroupCreate newGroup, String receiptId, ) async { - final user = await twonlyDB.contactsDao - .getContactByUserId(fromUserId) - .getSingleOrNull(); + final user = await twonlyDB.contactsDao.getContactByUserId(fromUserId).getSingleOrNull(); if (user == null) { // Only contacts can invite other contacts, so this can (via the UI) not happen. Log.error( @@ -229,6 +227,7 @@ Future handleResendGroupPublicKey( groupPublicKey: keyPair.getPublicKey().serialize(), ), ), + blocking: false, ); } diff --git a/lib/src/services/api/client2client/user_discovery.c2c.dart b/lib/src/services/api/client2client/user_discovery.c2c.dart index e249ff27..df0f03ec 100644 --- a/lib/src/services/api/client2client/user_discovery.c2c.dart +++ b/lib/src/services/api/client2client/user_discovery.c2c.dart @@ -17,6 +17,7 @@ Future checkForUserDiscoveryChanges( List receivedVersion, String receiptId, ) async { + Log.info('[$receiptId] Checking for a new user discovery version.'); final currentVersion = await UserDiscoveryService.shouldRequestNewMessages( fromUserId, receivedVersion, @@ -36,6 +37,7 @@ Future checkForUserDiscoveryChanges( currentVersion: currentVersion.toList(), ), ), + blocking: false, ); } } @@ -73,6 +75,7 @@ Future handleUserDiscoveryRequest( messages: newMessages, ), ), + blocking: false, ); } else { Log.info('[$receiptId] Got update request, but there are no new updates for the user'); diff --git a/lib/src/services/api/messages.api.dart b/lib/src/services/api/messages.api.dart index 801c9e0f..7d26da32 100644 --- a/lib/src/services/api/messages.api.dart +++ b/lib/src/services/api/messages.api.dart @@ -566,5 +566,5 @@ Future sendContactMyProfileData(int contactId) async { username: userService.currentUser.username, ), ); - await sendCipherText(contactId, encryptedContent); + await sendCipherText(contactId, encryptedContent, blocking: false); } diff --git a/lib/src/services/api/server_messages.api.dart b/lib/src/services/api/server_messages.api.dart index a66bc9c9..a16b2028 100644 --- a/lib/src/services/api/server_messages.api.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -86,8 +86,18 @@ Future handleClient2ClientMessage(NewMessage newMessage) async { final receiptId = message.receiptId; final mutex = _messageLocks.putIfAbsent(receiptId, Mutex.new); + if (mutex.isLocked) { + Log.info( + '[$receiptId] Skipping — already being processed by another handler', + ); + return; + } await mutex.protect(() async { - await _handleClient2ClientMessage(newMessage, message); + try { + await _handleClient2ClientMessage(newMessage, message); + } finally { + _messageLocks.remove(receiptId); + } }); } @@ -143,7 +153,7 @@ Future _handleClient2ClientMessage( Log.info( '[$receiptId] Sending error message to the original sender with receiptId $newReceiptId.', ); - await tryToSendCompleteMessage(receiptId: newReceiptId); + await tryToSendCompleteMessage(receiptId: newReceiptId, blocking: false); } case Message_Type.CIPHERTEXT: @@ -216,7 +226,7 @@ Future _handleClient2ClientMessage( } catch (e) { Log.warn('[$receiptId] Error inserting receipt: $e'); } - await tryToSendCompleteMessage(receiptId: receiptId); + await tryToSendCompleteMessage(receiptId: receiptId, blocking: false); } case Message_Type.TEST_NOTIFICATION: break; diff --git a/lib/src/services/user_discovery.service.dart b/lib/src/services/user_discovery.service.dart index fba1a4dd..11d41f1f 100644 --- a/lib/src/services/user_discovery.service.dart +++ b/lib/src/services/user_discovery.service.dart @@ -104,7 +104,8 @@ class UserDiscoveryService { static Future getCurrentVersion() async { try { - return await FlutterUserDiscovery.getCurrentVersion(); + return await FlutterUserDiscovery.getCurrentVersion() + .timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); return null; @@ -140,7 +141,7 @@ class UserDiscoveryService { return await FlutterUserDiscovery.shouldRequestNewMessages( contactId: fromUserId, version: receivedVersion, - ); + ).timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); return null; @@ -155,7 +156,7 @@ class UserDiscoveryService { return await FlutterUserDiscovery.getNewMessages( contactId: fromUserId, receivedVersion: receivedVersion, - ); + ).timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); return null; @@ -175,7 +176,7 @@ class UserDiscoveryService { messages: messages, publicKeyVerifiedTimestamp: verifications.lastOrNull?.createdAt.millisecondsSinceEpoch, - ); + ).timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); } diff --git a/pubspec.yaml b/pubspec.yaml index 63b56ded..549b3e5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.2.17+126 +version: 0.2.19+128 environment: sdk: ^3.11.0 diff --git a/rust_dependencies/protocols/src/user_discovery/mod.rs b/rust_dependencies/protocols/src/user_discovery/mod.rs index 68bccd63..cfe0d2ac 100644 --- a/rust_dependencies/protocols/src/user_discovery/mod.rs +++ b/rust_dependencies/protocols/src/user_discovery/mod.rs @@ -10,7 +10,7 @@ use std::u8; use blahaj::{Share, Sharks}; use prost::Message; use serde::{Deserialize, Serialize}; -use tokio::sync::{Mutex, MutexGuard}; +use tokio::sync::Mutex; use crate::user_discovery::error::{Result, UserDiscoveryError}; use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryUtils}; use crate::user_discovery::user_discovery_message::{UserDiscoveryAnnouncement, UserDiscoveryPromotion}; @@ -56,7 +56,7 @@ where { store: Store, utils: Utils, - config_lock: Arc>, + config_lock: Arc>, } impl UserDiscovery { @@ -101,6 +101,7 @@ impl UserDiscovery UserDiscoveryConfig { threshold, user_id, + total_number_of_shares: 255, ..Default::default() }, }; @@ -126,24 +127,27 @@ impl UserDiscovery serde_json::from_str(&c)?, - Err(_) => UserDiscoveryConfig { - threshold, - user_id, - ..Default::default() - }, - }; - final_config.public_id = public_id; - final_config.announcement_version += 1; - final_config.verification_shares = verification_shares; - final_config.share_promotion = share_promotion; - final_config.threshold = threshold; + { + let mut final_config = match self.store.get_config().await { + Ok(c) => serde_json::from_str(&c)?, + Err(_) => UserDiscoveryConfig { + threshold, + user_id, + ..Default::default() + }, + }; - self.update_config(final_config, config_lock).await?; + final_config.public_id = public_id; + final_config.announcement_version += 1; + final_config.verification_shares = verification_shares; + final_config.share_promotion = share_promotion; + final_config.threshold = threshold; + + self.store + .update_config(serde_json::to_string_pretty(&final_config)?) + .await?; + } tracing::info!("Protocols: initialize_or_update finished"); Ok(()) @@ -164,7 +168,7 @@ impl UserDiscovery Result> { - let (config, _) = self.get_config().await?; + let config = self.get_config_snapshot().await?; Ok(UserDiscoveryVersion { announcement: config.announcement_version, promotion: config.promotion_version, @@ -207,7 +211,7 @@ impl UserDiscovery UserDiscovery, ) -> Result<()> { - let (mut config, config_lock) = self.get_config().await?; - config.promotion_version += 1; - let Some(current_promotion) = self.store.get_contact_promotion(contact_id).await? else { // User does not participate... return Ok(()); @@ -376,10 +377,19 @@ impl UserDiscovery UserDiscovery UserDiscovery Result<(UserDiscoveryConfig, MutexGuard<'_, bool>)> { - let mut lock = self.config_lock.lock().await; - *lock = true; - Ok((serde_json::from_str(&self.store.get_config().await?)?, lock)) + /// Reads the config from the store without holding any lock. + /// Use this for read-only access to the config. + async fn get_config_snapshot(&self) -> Result { + Ok(serde_json::from_str(&self.store.get_config().await?)?) } - async fn update_config( - &self, - config: UserDiscoveryConfig, - mut config_lock: MutexGuard<'_, bool>, - ) -> Result<()> { + /// Atomically reads the config, applies the mutation, and writes it back. + /// The config_lock is only held during the read-modify-write cycle, + /// NOT across any async Dart callbacks from the caller. + async fn read_modify_write_config(&self, mutate: F) -> Result<()> + where + F: FnOnce(&mut UserDiscoveryConfig), + { + let _lock = tokio::time::timeout( + std::time::Duration::from_secs(10), + self.config_lock.lock(), + ) + .await + .ok(); + let mut config: UserDiscoveryConfig = + serde_json::from_str(&self.store.get_config().await?)?; + mutate(&mut config); self.store .update_config(serde_json::to_string_pretty(&config)?) .await?; - *config_lock = false; Ok(()) } @@ -542,13 +560,19 @@ impl UserDiscovery UserDiscovery UserDiscovery Self { Self { threshold: 2, - total_number_of_shares: u8::MAX, + total_number_of_shares: 255, announcement_version: 0, promotion_version: 0, verification_shares: vec![], @@ -768,3 +790,4 @@ impl Default for UserDiscoveryConfig { } } } +