From 9c206bad64fd7c43052ea617beeb7f4316d05901 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 22 May 2023 14:59:03 +1000 Subject: [PATCH] feat: adding a force new configs flag and logic for timestamp handling / forced configs, fix issue with handling legacy messages --- .../securesms/database/Storage.kt | 8 +------- .../database/helpers/SQLCipherOpenHelper.java | 2 ++ .../securesms/dependencies/ConfigFactory.kt | 15 ++++++++------ .../securesms/home/HomeActivity.kt | 3 ++- .../util/ConfigurationMessageUtilities.kt | 10 +++++++--- .../loki/messenger/libsession_util/Config.kt | 6 +++++- .../messaging/jobs/ConfigurationSyncJob.kt | 7 ++++--- .../ReceivedMessageHandler.kt | 5 ++++- .../sending_receiving/pollers/Poller.kt | 10 +++++----- .../utilities/TextSecurePreferences.kt | 20 +++++++++++++++++++ 10 files changed, 59 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index bf877461a6..ddfd689760 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -94,14 +94,8 @@ import network.loki.messenger.libsession_util.util.Contact as LibSessionContact open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol, ThreadDatabase.ConversationThreadUpdateListener { - // TODO: maybe add time here from formation / creation message override fun threadCreated(address: Address, threadId: Long) { if (!getRecipientApproved(address)) return // don't store unapproved / message requests - if (getUserPublicKey() == address.serialize()) { - Log.d("Loki-DBG", "NTS created, context:\n${Thread.currentThread().stackTrace.joinToString("\n")}") - } else { - Log.d("Loki-DBG", "Thread created ${address.serialize()}") - } val volatile = configFactory.convoVolatile ?: return if (address.isGroup) { @@ -491,7 +485,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co private fun updateUserGroups(userGroups: UserGroupsConfig) { val threadDb = DatabaseComponent.get(context).threadDatabase() val localUserPublicKey = getUserPublicKey() ?: return Log.w( - "Loki-DBG", + "Loki", "No user public key when trying to update user groups from config" ) val communities = userGroups.allCommunityInfo() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 2a012decfe..bea4323250 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -596,6 +596,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND); db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_GROUPS); db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_ONE_TO_ONES); + // TODO: remove this for release + TextSecurePreferences.setForceNewConfig(context); } db.setTransactionSuccessful(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 0e57d8264e..3f6a7a8c3b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -6,13 +6,14 @@ import network.loki.messenger.libsession_util.Contacts import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserProfile +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryUpdateListener +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities -import java.util.concurrent.Executors class ConfigFactory( private val context: Context, @@ -39,6 +40,8 @@ class ConfigFactory( private val userGroupsLock = Object() private var _userGroups: UserGroupsConfig? = null + private val isConfigForcedOn = TextSecurePreferences.hasForcedNewConfig(context) + private val listeners: MutableList = mutableListOf() fun registerListener(listener: ConfigFactoryUpdateListener) { listeners += listener @@ -50,7 +53,7 @@ class ConfigFactory( override val user: UserProfile? get() = synchronized(userLock) { - if (!ConfigBase.isNewConfigEnabled) return null + if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (_userConfig == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val userDump = configDatabase.retrieveConfigAndHashes( @@ -70,7 +73,7 @@ class ConfigFactory( override val contacts: Contacts? get() = synchronized(contactsLock) { - if (!ConfigBase.isNewConfigEnabled) return null + if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (_contacts == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val contactsDump = configDatabase.retrieveConfigAndHashes( @@ -90,7 +93,7 @@ class ConfigFactory( override val convoVolatile: ConversationVolatileConfig? get() = synchronized(convoVolatileLock) { - if (!ConfigBase.isNewConfigEnabled) return null + if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (_convoVolatileConfig == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val convoDump = configDatabase.retrieveConfigAndHashes( @@ -111,7 +114,7 @@ class ConfigFactory( override val userGroups: UserGroupsConfig? get() = synchronized(userGroupsLock) { - if (!ConfigBase.isNewConfigEnabled) return null + if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (_userGroups == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val userGroupsDump = configDatabase.retrieveConfigAndHashes( @@ -174,7 +177,7 @@ class ConfigFactory( else -> throw UnsupportedOperationException("Can't support type of ${forConfigObject::class.simpleName} yet") } } catch (e: Exception) { - Log.e("Loki-DBG", e) + Log.e("Loki", "failed to persist ${forConfigObject.javaClass.simpleName}", e) } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index ed06c2dcfc..1d20524290 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -354,7 +354,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } private fun updateLegacyConfigView() { - binding.configOutdatedView.isVisible = ConfigBase.isNewConfigEnabled && textSecurePreferences.getHasLegacyConfig() + binding.configOutdatedView.isVisible = ConfigBase.isNewConfigEnabled(textSecurePreferences.hasForcedNewConfig(), SnodeAPI.nowWithOffset) + && textSecurePreferences.getHasLegacyConfig() } override fun onResume() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index 8c2f66dc45..3644cdc127 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -18,6 +18,7 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences @@ -25,7 +26,6 @@ import org.session.libsession.utilities.WindowDebouncer import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.database.GroupDatabase -import org.thoughtcrime.securesms.database.GroupMemberDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent import java.util.Timer @@ -53,7 +53,9 @@ object ConfigurationMessageUtilities { fun syncConfigurationIfNeeded(context: Context) { // add if check here to schedule new config job process and return early val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return - if (ConfigBase.isNewConfigEnabled) { + val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context) + val currentTime = SnodeAPI.nowWithOffset + if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) { scheduleConfigSync(userPublicKey) return } @@ -81,7 +83,9 @@ object ConfigurationMessageUtilities { fun forceSyncConfigurationNowIfNeeded(context: Context): Promise { // add if check here to schedule new config job process and return early val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null")) - if (ConfigBase.isNewConfigEnabled) { + val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context) + val currentTime = SnodeAPI.nowWithOffset + if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) { // schedule job if none exist // don't schedule job if we already have one scheduleConfigSync(userPublicKey) diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt index 77ef696f02..cbf55b2e5e 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -25,7 +25,11 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) { is UserGroupsConfig -> Kind.GROUPS } - const val isNewConfigEnabled = true + // TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release) + private const val ACTIVATE_TIME = 1704027600 + + fun isNewConfigEnabled(forced: Boolean, currentTime: Long) = + forced || currentTime >= ACTIVATE_TIME const val PRIORITY_HIDDEN = -1 const val PRIORITY_VISIBLE = 0 diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt index 75fef91900..b57d59f7ce 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt @@ -2,14 +2,13 @@ package org.session.libsession.messaging.jobs import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor -import nl.komponents.kovenant.functional.bind import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.SharedConfigurationMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.Data -import org.session.libsession.snode.RawResponse import org.session.libsession.snode.SnodeAPI +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log import java.util.concurrent.atomic.AtomicBoolean @@ -25,12 +24,14 @@ data class ConfigurationSyncJob(val destination: Destination): Job { override suspend fun execute(dispatcherName: String) { val storage = MessagingModuleConfiguration.shared.storage + val forcedConfig = TextSecurePreferences.hasForcedNewConfig(MessagingModuleConfiguration.shared.context) + val currentTime = SnodeAPI.nowWithOffset val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() val userPublicKey = storage.getUserPublicKey() val delegate = delegate if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature // if we haven't enabled the new configs don't run - || !ConfigBase.isNewConfigEnabled + || !ConfigBase.isNewConfigEnabled(forcedConfig, currentTime) // if we don't have a user ed key pair for signing updates || userEdKeyPair == null // this will be useful to not handle null delegate cases diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 9b4a2ce04c..9db1b9b79b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -149,8 +149,11 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { TextSecurePreferences.setConfigurationMessageSynced(context, true) TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) - if (ConfigBase.isNewConfigEnabled) { + val isForceSync = TextSecurePreferences.hasForcedNewConfig(context) + val currentTime = SnodeAPI.nowWithOffset + if (ConfigBase.isNewConfigEnabled(isForceSync, currentTime)) { TextSecurePreferences.setHasLegacyConfig(context, true) + if (!firstTimeSync) return } val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() for (closedGroup in message.closedGroups) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index ecb8dd8429..967d03da4c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -151,7 +151,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null) // sanity checks if (message !is SharedConfigurationMessage) { - Log.w("Loki-DBG", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}") + Log.w("Loki", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}") return@forEach } forConfigObject.merge(hash!! to message.data) @@ -213,11 +213,11 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti if (personalResponseIndex >= 0) { responseList.getOrNull(personalResponseIndex)?.let { rawResponse -> if (rawResponse["code"] as? Int != 200) { - Log.e("Loki-DBG", "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}") + Log.e("Loki", "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}") } else { val body = rawResponse["body"] as? RawResponse if (body == null) { - Log.e("Loki-DBG", "Batch sub-request for personal messages didn't contain a body") + Log.e("Loki", "Batch sub-request for personal messages didn't contain a body") } else { processPersonalMessages(snode, body) } @@ -230,12 +230,12 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) -> responseList.getOrNull(requestIndex)?.let { rawResponse -> if (rawResponse["code"] as? Int != 200) { - Log.e("Loki-DBG", "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}") + Log.e("Loki", "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}") return@forEach } val body = rawResponse["body"] as? RawResponse if (body == null) { - Log.e("Loki-DBG", "Batch sub-request didn't contain a body") + Log.e("Loki", "Batch sub-request didn't contain a body") return@forEach } if (key == Namespace.DEFAULT) { diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 756bf0777f..09feb4cc81 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -180,6 +180,8 @@ interface TextSecurePreferences { fun setThemeStyle(themeStyle: String) fun setFollowSystemSettings(followSystemSettings: Boolean) fun autoplayAudioMessages(): Boolean + fun setForceNewConfig() + fun hasForcedNewConfig(): Boolean fun hasPreference(key: String): Boolean fun clearAll() @@ -268,6 +270,7 @@ interface TextSecurePreferences { const val SELECTED_ACCENT_COLOR = "selected_accent_color" const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config" + const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config" const val GREEN_ACCENT = "accent_green" const val BLUE_ACCENT = "accent_blue" @@ -811,6 +814,16 @@ interface TextSecurePreferences { setIntegerPreference(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version) } + @JvmStatic + fun setForceNewConfig(context: Context) { + setBooleanPreference(context, HAS_FORCED_NEW_CONFIG, true) + } + + @JvmStatic + fun hasForcedNewConfig(context: Context): Boolean { + return getBooleanPreference(context, HAS_FORCED_NEW_CONFIG, false) + } + @JvmStatic fun getBooleanPreference(context: Context, key: String?, defaultValue: Boolean): Boolean { return getDefaultSharedPreferences(context).getBoolean(key, defaultValue) @@ -1447,6 +1460,13 @@ class AppTextSecurePreferences @Inject constructor( setIntegerPreference(TextSecurePreferences.NOTIFICATION_MESSAGES_CHANNEL_VERSION, version) } + override fun setForceNewConfig() { + setBooleanPreference(TextSecurePreferences.HAS_FORCED_NEW_CONFIG, true) + } + + override fun hasForcedNewConfig(): Boolean = + getBooleanPreference(TextSecurePreferences.HAS_FORCED_NEW_CONFIG, false) + override fun getBooleanPreference(key: String?, defaultValue: Boolean): Boolean { return getDefaultSharedPreferences(context).getBoolean(key, defaultValue) }