diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7b96148e2e5..4f17b327cdb 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -226,7 +226,7 @@ internal class DefaultCryptoService @Inject constructor( override fun fetchDevicesList(callback: MatrixCallback) { getDevicesTask .configureWith { - // this.executionThread = TaskThread.CRYPTO + this.callbackThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onFailure(failure: Throwable) { callback.onFailure(failure) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 220f25ec800..24efe80c46c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -60,16 +60,14 @@ internal class IncomingGossipingRequestManager @Inject constructor( // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // we received in the current sync. - private val receivedGossipingRequests = ArrayList() + private val receivedGossipingRequests by lazy { + cryptoStore.getPendingIncomingGossipingRequests().toMutableList() + } private val receivedRequestCancellations = ArrayList() // the listeners private val gossipingRequestListeners: MutableSet = HashSet() - init { - receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) - } - fun close() { executor.shutdownNow() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 441dfe4a5d3..3e8d5aec57e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -53,17 +53,40 @@ internal class MXOlmDevice @Inject constructor( /** * @return the Curve25519 key for the account. */ - var deviceCurve25519Key: String? = null - private set + val deviceCurve25519Key: String? by lazy { + try { + olmAccount.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] + } catch (e: Exception) { + Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error") + null + } + } /** * @return the Ed25519 key for the account. */ - var deviceEd25519Key: String? = null - private set + val deviceEd25519Key: String? by lazy { + try { + olmAccount.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] + } catch (e: Exception) { + Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error") + null + } + } // The OLM lib utility instance. - private var olmUtility: OlmUtility? = null + private val olmUtility: OlmUtility? by lazy { + try { + OlmUtility() + } catch (e: Exception) { + Timber.e(e, "## MXOlmDevice : OlmUtility failed with error") + null + } + } + + private val olmAccount: OlmAccount by lazy { + store.getOrCreateOlmAccount() + } private data class GroupSessionCacheItem( val groupId: String, @@ -88,40 +111,12 @@ internal class MXOlmDevice @Inject constructor( // The second level keys are strings of form "||" private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap() - init { - // Retrieve the account from the store - try { - store.getOrCreateOlmAccount() - } catch (e: Exception) { - Timber.e(e, "MXOlmDevice : cannot initialize olmAccount") - } - - try { - olmUtility = OlmUtility() - } catch (e: Exception) { - Timber.e(e, "## MXOlmDevice : OlmUtility failed with error") - olmUtility = null - } - - try { - deviceCurve25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] - } catch (e: Exception) { - Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error") - } - - try { - deviceEd25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] - } catch (e: Exception) { - Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error") - } - } - /** * @return The current (unused, unpublished) one-time keys for this account. */ fun getOneTimeKeys(): Map>? { try { - return store.getOlmAccount().oneTimeKeys() + return olmAccount.oneTimeKeys() } catch (e: Exception) { Timber.e(e, "## getOneTimeKeys() : failed") } @@ -133,7 +128,7 @@ internal class MXOlmDevice @Inject constructor( * @return The maximum number of one-time keys the olm account can store. */ fun getMaxNumberOfOneTimeKeys(): Long { - return store.getOlmAccount().maxOneTimeKeys() + return olmAccount.maxOneTimeKeys() } /** @@ -155,7 +150,7 @@ internal class MXOlmDevice @Inject constructor( */ fun signMessage(message: String): String? { try { - return store.getOlmAccount().signMessage(message) + return olmAccount.signMessage(message) } catch (e: Exception) { Timber.e(e, "## signMessage() : failed") } @@ -168,7 +163,7 @@ internal class MXOlmDevice @Inject constructor( */ fun markKeysAsPublished() { try { - store.getOlmAccount().markOneTimeKeysAsPublished() + olmAccount.markOneTimeKeysAsPublished() store.saveOlmAccount() } catch (e: Exception) { Timber.e(e, "## markKeysAsPublished() : failed") @@ -182,7 +177,7 @@ internal class MXOlmDevice @Inject constructor( */ fun generateOneTimeKeys(numKeys: Int) { try { - store.getOlmAccount().generateOneTimeKeys(numKeys) + olmAccount.generateOneTimeKeys(numKeys) store.saveOlmAccount() } catch (e: Exception) { Timber.e(e, "## generateOneTimeKeys() : failed") @@ -203,7 +198,7 @@ internal class MXOlmDevice @Inject constructor( try { olmSession = OlmSession() - olmSession.initOutboundSession(store.getOlmAccount(), theirIdentityKey, theirOneTimeKey) + olmSession.initOutboundSession(olmAccount, theirIdentityKey, theirOneTimeKey) val olmSessionWrapper = OlmSessionWrapper(olmSession, 0) @@ -243,7 +238,7 @@ internal class MXOlmDevice @Inject constructor( try { try { olmSession = OlmSession() - olmSession.initInboundSessionFrom(store.getOlmAccount(), theirDeviceIdentityKey, ciphertext) + olmSession.initInboundSessionFrom(olmAccount, theirDeviceIdentityKey, ciphertext) } catch (e: Exception) { Timber.e(e, "## createInboundSession() : the session creation failed") return null @@ -252,7 +247,7 @@ internal class MXOlmDevice @Inject constructor( Timber.v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}") try { - store.getOlmAccount().removeOneTimeKeys(olmSession) + olmAccount.removeOneTimeKeys(olmSession) store.saveOlmAccount() } catch (e: Exception) { Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt index 70846515a7d..2fe5c09e8e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt @@ -36,10 +36,7 @@ internal class MyDeviceInfoHolder @Inject constructor( /** * my device info */ - val myDevice: CryptoDeviceInfo - - init { - + val myDevice: CryptoDeviceInfo by lazy { val keys = HashMap() // TODO it's a bit strange, why not load from DB? @@ -60,21 +57,21 @@ internal class MyDeviceInfoHolder @Inject constructor( val crossSigned = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.locallyVerified ?: false // myDevice.trustLevel = DeviceTrustLevel(crossSigned, true) - myDevice = CryptoDeviceInfo( + CryptoDeviceInfo( credentials.deviceId!!, credentials.userId, keys = keys, algorithms = MXCryptoAlgorithms.supportedAlgorithms(), trustLevel = DeviceTrustLevel(crossSigned, true) - ) + ).also { + // Add our own deviceinfo to the store + val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId) - // Add our own deviceinfo to the store - val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId) + val myDevices = endToEndDevicesForUser.orEmpty().toMutableMap() - val myDevices = endToEndDevicesForUser.orEmpty().toMutableMap() + myDevices[it.deviceId] = it - myDevices[myDevice.deviceId] = myDevice - - cryptoStore.storeUserDevices(credentials.userId, myDevices) + cryptoStore.storeUserDevices(credentials.userId, myDevices) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 83de06a6687..1fefa09f0df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -67,71 +67,95 @@ internal class DefaultCrossSigningService @Inject constructor( ) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { - private var olmUtility: OlmUtility? = null + private val crossSigningInfo by lazy { + cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> + mxCrossSigningInfo to cryptoStore.getCrossSigningPrivateKeys() + } + } - private var masterPkSigning: OlmPkSigning? = null - private var userPkSigning: OlmPkSigning? = null - private var selfSigningPkSigning: OlmPkSigning? = null + private var _masterPkSigning: OlmPkSigning? = null + private val initialMasterPkSigning: OlmPkSigning? by lazy { + crossSigningInfo?.let { (mxCrossSigningInfo, privateKeysInfo) -> + privateKeysInfo?.master + ?.fromBase64() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { + Timber.i("## CrossSigning - Loading master key success") + pkSigning + } else { + Timber.w("## CrossSigning - Public master key does not match the private key") + pkSigning.releaseSigning() + // TODO untrust? + null + } + } + } + } + private val masterPkSigning: OlmPkSigning? + get() = _masterPkSigning ?: initialMasterPkSigning + + private var _userPkSigning: OlmPkSigning? = null + private val initialUserPkSigning: OlmPkSigning? by lazy { + crossSigningInfo?.let { (mxCrossSigningInfo, privateKeysInfo) -> + privateKeysInfo?.user + ?.fromBase64() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { + Timber.i("## CrossSigning - Loading User Signing key success") + pkSigning + } else { + Timber.w("## CrossSigning - Public User key does not match the private key") + pkSigning.releaseSigning() + // TODO untrust? + null + } + } + } + } + private val userPkSigning: OlmPkSigning? + get() = _userPkSigning ?: initialUserPkSigning + + private var _selfSigningPkSigning: OlmPkSigning? = null + private val initialSelfSigningPkSigning: OlmPkSigning? by lazy { + crossSigningInfo?.let { (mxCrossSigningInfo, privateKeysInfo) -> + privateKeysInfo?.selfSigned + ?.fromBase64() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + Timber.i("## CrossSigning - Loading Self Signing key success") + pkSigning + } else { + Timber.w("## CrossSigning - Public Self Signing key does not match the private key") + pkSigning.releaseSigning() + // TODO untrust? + null + } + } + } + } + private val selfSigningPkSigning: OlmPkSigning? + get() = _selfSigningPkSigning ?: initialSelfSigningPkSigning - init { + private val olmUtility: OlmUtility? by lazy { try { - olmUtility = OlmUtility() - - // Try to get stored keys if they exist - cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> + OlmUtility().also { + // Try to get stored keys if they exist Timber.i("## CrossSigning - Found Existing self signed keys") Timber.i("## CrossSigning - Checking if private keys are known") - - cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo -> - privateKeysInfo.master - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading master key success") - } else { - Timber.w("## CrossSigning - Public master key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.user - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading User Signing key success") - } else { - Timber.w("## CrossSigning - Public User key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.selfSigned - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading Self Signing key success") - } else { - Timber.w("## CrossSigning - Public Self Signing key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - } - // Recover local trust in case private key are there? setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified()) } } catch (e: Throwable) { // Mmm this kind of a big issue Timber.e(e, "Failed to initialize Cross Signing") + null } + } + init { deviceListManager.addListener(this) } @@ -170,9 +194,9 @@ internal class DefaultCrossSigningService @Inject constructor( cryptoStore.setMyCrossSigningInfo(crossSigningInfo) setUserKeysAsTrusted(userId, true) cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) - masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } - userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } - selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } + _masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } + _userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } + _selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } callback.onSuccess(Unit) } @@ -192,7 +216,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning + _masterPkSigning = pkSigning Timber.i("## CrossSigning - Loading MSK success") cryptoStore.storeMSKPrivateKey(mskPrivateKey) return @@ -219,7 +243,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning + _selfSigningPkSigning = pkSigning Timber.i("## CrossSigning - Loading SSK success") cryptoStore.storeSSKPrivateKey(sskPrivateKey) return @@ -246,7 +270,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { userPkSigning?.releaseSigning() - userPkSigning = pkSigning + _userPkSigning = pkSigning Timber.i("## CrossSigning - Loading USK success") cryptoStore.storeUSKPrivateKey(uskPrivateKey) return @@ -276,7 +300,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning + _masterPkSigning = pkSigning masterKeyIsTrusted = true Timber.i("## CrossSigning - Loading master key success") } else { @@ -293,7 +317,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { userPkSigning?.releaseSigning() - userPkSigning = pkSigning + _userPkSigning = pkSigning userKeyIsTrusted = true Timber.i("## CrossSigning - Loading master key success") } else { @@ -310,7 +334,7 @@ internal class DefaultCrossSigningService @Inject constructor( try { if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning + _selfSigningPkSigning = pkSigning selfSignedKeyIsTrusted = true Timber.i("## CrossSigning - Loading master key success") } else { @@ -769,22 +793,24 @@ internal class DefaultCrossSigningService @Inject constructor( } private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { - val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() - cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) - // If it's me, recheck trust of all users and devices? - val users = ArrayList() - if (otherUserId == userId && currentTrust != trusted) { + cryptoCoroutineScope.launch { + val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() + cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) + // If it's me, recheck trust of all users and devices? + val users = ArrayList() + if (otherUserId == userId && currentTrust != trusted) { // reRequestAllPendingRoomKeyRequest() - cryptoStore.updateUsersTrust { - users.add(it) - checkUserTrust(it).isVerified() - } + cryptoStore.updateUsersTrust { + users.add(it) + checkUserTrust(it).isVerified() + } - users.forEach { - cryptoStore.getUserDeviceList(it)?.forEach { device -> - val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") - cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) + users.forEach { + cryptoStore.getUserDeviceList(it)?.forEach { device -> + val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) + Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") + cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index b20168eaa3c..2a772a79870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -877,6 +877,7 @@ internal class DefaultKeysBackupService @Inject constructor( override fun getCurrentVersion(callback: MatrixCallback) { getKeysBackupLastVersionTask .configureWith { + this.callbackThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onSuccess(data: KeysVersionResult) { callback.onSuccess(data) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 40678a6ce64..da780dc27ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -108,7 +108,8 @@ import kotlin.collections.set @SessionScope internal class RealmCryptoStore @Inject constructor( - @CryptoDatabase private val realmConfiguration: RealmConfiguration, + // Avoid using directly and instead interact with lazyRealmConfiguration + @CryptoDatabase realmConfiguration: RealmConfiguration, private val crossSigningKeysMapper: CrossSigningKeysMapper, @UserId private val userId: String, @DeviceId private val deviceId: String? @@ -140,15 +141,12 @@ internal class RealmCryptoStore @Inject constructor( newSessionListeners.remove(listener) } - private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor() - - private val monarchy = Monarchy.Builder() - .setRealmConfiguration(realmConfiguration) - .setWriteAsyncExecutor(monarchyWriteAsyncExecutor) - .build() + private val lazyRealmConfiguration by lazy { + ensureCryptoMetadataEntity(realmConfiguration) + realmConfiguration + } - init { - // Ensure CryptoMetadataEntity is inserted in DB + private fun ensureCryptoMetadataEntity(realmConfiguration: RealmConfiguration) { doRealmTransaction(realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() @@ -178,12 +176,21 @@ internal class RealmCryptoStore @Inject constructor( } } } + + private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor() + + private val monarchy by lazy { + Monarchy.Builder() + .setRealmConfiguration(lazyRealmConfiguration) + .setWriteAsyncExecutor(monarchyWriteAsyncExecutor) + .build() + } /* ========================================================================================== * Other data * ========================================================================================== */ override fun hasData(): Boolean { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { !it.isEmpty && // Check if there is a MetaData object it.where().count() > 0 @@ -191,7 +198,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteStore() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.deleteAll() } } @@ -199,7 +206,7 @@ internal class RealmCryptoStore @Inject constructor( override fun open() { synchronized(this) { if (realmLocker == null) { - realmLocker = Realm.getInstance(realmConfiguration) + realmLocker = Realm.getInstance(lazyRealmConfiguration) } } } @@ -230,19 +237,19 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeDeviceId(deviceId: String) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where().findFirst()?.deviceId = deviceId } } override fun getDeviceId(): String { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where().findFirst()?.deviceId } ?: "" } override fun saveOlmAccount() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where().findFirst()?.putOlmAccount(olmAccount) } } @@ -252,7 +259,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getOrCreateOlmAccount(): OlmAccount { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { val metaData = it.where().findFirst() val existing = metaData!!.getOlmAccount() if (existing == null) { @@ -269,7 +276,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst() @@ -280,7 +287,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deviceWithIdentityKey(identityKey: String): CryptoDeviceInfo? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(DeviceInfoEntityFields.IDENTITY_KEY, identityKey) .findFirst() @@ -291,7 +298,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeUserDevices(userId: String, devices: Map?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> if (devices == null) { Timber.d("Remove user $userId") // Remove the user @@ -332,7 +339,7 @@ internal class RealmCryptoStore @Inject constructor( masterKey: CryptoCrossSigningKey?, selfSigningKey: CryptoCrossSigningKey?, userSigningKey: CryptoCrossSigningKey?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> UserEntity.getOrCreate(realm, userId) .let { userEntity -> if (masterKey == null || selfSigningKey == null) { @@ -415,7 +422,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> realm.where() .findFirst() ?.let { @@ -449,7 +456,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk xSignUserPrivateKey = usk @@ -459,7 +466,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.apply { keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKeyVersion = version @@ -468,7 +475,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> realm.where() .findFirst() ?.let { @@ -485,7 +492,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeMSKPrivateKey(msk: String?) { Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk } @@ -494,7 +501,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeSSKPrivateKey(ssk: String?) { Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignSelfSignedPrivateKey = ssk } @@ -503,7 +510,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUSKPrivateKey(usk: String?) { Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } @@ -511,7 +518,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getUserDevices(userId: String): Map? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() @@ -526,7 +533,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getUserDeviceList(userId: String): List? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() @@ -621,7 +628,7 @@ internal class RealmCryptoStore @Inject constructor( deviceId = it.deviceId ) } - doRealmTransactionAsync(realmConfiguration) { realm -> + doRealmTransactionAsync(lazyRealmConfiguration) { realm -> realm.where().findAll().deleteAllFromRealm() entities.forEach { realm.insertOrUpdate(it) @@ -630,26 +637,26 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeRoomAlgorithm(roomId: String, algorithm: String) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm } } override fun getRoomAlgorithm(roomId: String): String? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { CryptoRoomEntity.getById(it, roomId)?.algorithm } } override fun shouldEncryptForInvitedMembers(roomId: String): Boolean { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { CryptoRoomEntity.getById(it, roomId)?.shouldEncryptForInvitedMembers } ?: false } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers } } @@ -673,7 +680,7 @@ internal class RealmCryptoStore @Inject constructor( olmSessionsToRelease[key] = olmSessionWrapper - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { val realmOlmSession = OlmSessionEntity().apply { primaryKey = key sessionId = sessionIdentifier @@ -692,7 +699,7 @@ internal class RealmCryptoStore @Inject constructor( // If not in cache (or not found), try to read it from realm if (olmSessionsToRelease[key] == null) { - doRealmQueryAndCopy(realmConfiguration) { + doRealmQueryAndCopy(lazyRealmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.PRIMARY_KEY, key) .findFirst() @@ -709,7 +716,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getLastUsedSessionId(deviceKey: String): String? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) .sort(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Sort.DESCENDING) @@ -719,7 +726,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getDeviceSessionIds(deviceKey: String): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) .findAll() @@ -734,7 +741,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> sessions.forEach { session -> var sessionIdentifier: String? = null @@ -772,7 +779,7 @@ internal class RealmCryptoStore @Inject constructor( // If not in cache (or not found), try to read it from realm if (inboundGroupSessionToRelease[key] == null) { - doWithRealm(realmConfiguration) { + doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findFirst() @@ -787,7 +794,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> realm.where() .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId) .findFirst()?.outboundSessionInfo?.let { entity -> @@ -806,7 +813,7 @@ internal class RealmCryptoStore @Inject constructor( // the olmdevice is caching the active instance // this is called for each sent message (so not high frequency), thus we can use basic realm async without // risk of reaching max async operation limit? - doRealmTransactionAsync(realmConfiguration) { realm -> + doRealmTransactionAsync(lazyRealmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId)?.let { entity -> // we should delete existing outbound session info if any entity.outboundSessionInfo?.deleteFromRealm() @@ -827,7 +834,7 @@ internal class RealmCryptoStore @Inject constructor( * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management */ override fun getInboundGroupSessions(): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .findAll() .mapNotNull { inboundGroupSessionEntity -> @@ -843,7 +850,7 @@ internal class RealmCryptoStore @Inject constructor( inboundGroupSessionToRelease[key]?.olmInboundGroupSession?.releaseSession() inboundGroupSessionToRelease.remove(key) - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findAll() @@ -856,25 +863,25 @@ internal class RealmCryptoStore @Inject constructor( * ========================================================================================== */ override fun getKeyBackupVersion(): String? { - return doRealmQueryAndCopy(realmConfiguration) { + return doRealmQueryAndCopy(lazyRealmConfiguration) { it.where().findFirst() }?.backupVersion } override fun setKeyBackupVersion(keyBackupVersion: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where().findFirst()?.backupVersion = keyBackupVersion } } override fun getKeysBackupData(): KeysBackupDataEntity? { - return doRealmQueryAndCopy(realmConfiguration) { + return doRealmQueryAndCopy(lazyRealmConfiguration) { it.where().findFirst() } } override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { if (keysBackupData == null) { // Clear the table it.where() @@ -888,7 +895,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun resetBackupMarkers() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where() .findAll() .map { inboundGroupSession -> @@ -902,7 +909,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { val key = OlmInboundGroupSessionEntity.createPrimaryKey( @@ -921,7 +928,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun inboundGroupSessionsToBackup(limit: Int): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false) .limit(limit.toLong()) @@ -933,7 +940,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .apply { if (onlyBackedUp) { @@ -946,31 +953,31 @@ internal class RealmCryptoStore @Inject constructor( } override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices = block } } override fun getGlobalBlacklistUnverifiedDevices(): Boolean { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices } ?: false } override fun setDeviceKeysUploaded(uploaded: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded } } override fun areDeviceKeysUploaded(): Boolean { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer } ?: false } override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { // Reset all it.where() .findAll() @@ -989,7 +996,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getRoomsListBlacklistUnverifiedDevices(): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true) .findAll() @@ -1000,7 +1007,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getDeviceTrackingStatuses(): Map { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .findAll() .associateBy { user -> @@ -1013,7 +1020,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction(lazyRealmConfiguration) { deviceTrackingStatuses .map { entry -> UserEntity.getOrCreate(it, entry.key) @@ -1023,7 +1030,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getDeviceTrackingStatus(userId: String, defaultValue: Int): Int { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() @@ -1119,7 +1126,7 @@ internal class RealmCryptoStore @Inject constructor( override fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>): OutgoingRoomKeyRequest? { // Insert the request and return the one passed in parameter var request: OutgoingRoomKeyRequest? = null - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) @@ -1152,7 +1159,7 @@ internal class RealmCryptoStore @Inject constructor( var request: OutgoingSecretRequest? = null // Insert the request and return the one passed in parameter - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) .equalTo(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, secretName) @@ -1291,7 +1298,7 @@ internal class RealmCryptoStore @Inject constructor( requestDeviceId: String?, requestId: String?, state: GossipingRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where() .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, requestUserId) .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, requestDeviceId) @@ -1303,7 +1310,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingGossipingRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where() .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_ID, requestId) .findAll().forEach { @@ -1313,7 +1320,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> realm.where() .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, deviceId) @@ -1327,7 +1334,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getPendingIncomingRoomKeyRequests(): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) @@ -1345,7 +1352,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getPendingIncomingGossipingRequests(): List { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where() .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) .findAll() @@ -1375,7 +1382,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) { - doRealmTransactionAsync(realmConfiguration) { realm -> + doRealmTransactionAsync(lazyRealmConfiguration) { realm -> // After a clear cache, we might have a @@ -1397,7 +1404,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeIncomingGossipingRequests(requests: List) { - doRealmTransactionAsync(realmConfiguration) { realm -> + doRealmTransactionAsync(lazyRealmConfiguration) { realm -> requests.forEach { request -> // After a clear cache, we might have a realm.createObject(IncomingGossipingRequestEntity::class.java).let { @@ -1431,7 +1438,7 @@ internal class RealmCryptoStore @Inject constructor( * Cross Signing * ========================================================================================== */ override fun getMyCrossSigningInfo(): MXCrossSigningInfo? { - return doWithRealm(realmConfiguration) { + return doWithRealm(lazyRealmConfiguration) { it.where().findFirst()?.userId }?.let { getCrossSigningInfo(it) @@ -1439,7 +1446,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { userId -> addOrUpdateCrossSigningInfo(realm, userId, info) } @@ -1447,7 +1454,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1467,7 +1474,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where(DeviceInfoEntity::class.java) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst()?.let { deviceInfoEntity -> @@ -1487,7 +1494,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun clearOtherUserTrust() { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { info -> @@ -1502,7 +1509,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateUsersTrust(check: (String) -> Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { xInfoEntity -> @@ -1573,7 +1580,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1609,13 +1616,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> addOrUpdateCrossSigningInfo(realm, userId, info) } } override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { myUserId -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> val level = xInfoEntity.trustLevelEntity @@ -1654,7 +1661,7 @@ internal class RealmCryptoStore @Inject constructor( val roomId = withHeldContent.roomId ?: return val sessionId = withHeldContent.sessionId ?: return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { it.code = withHeldContent.code it.senderKey = withHeldContent.senderKey @@ -1664,7 +1671,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> WithHeldSessionEntity.get(realm, roomId, sessionId)?.let { RoomKeyWithHeldContent( roomId = roomId, @@ -1684,7 +1691,7 @@ internal class RealmCryptoStore @Inject constructor( deviceId: String, deviceIdentityKey: String, chainIndex: Int) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, roomId = roomId, @@ -1698,7 +1705,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getSharedSessionInfo(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): IMXCryptoStore.SharedSessionResult { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> SharedSessionEntity.get( realm = realm, roomId = roomId, @@ -1713,7 +1720,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap { - return doWithRealm(realmConfiguration) { realm -> + return doWithRealm(lazyRealmConfiguration) { realm -> val result = MXUsersDevicesMap() SharedSessionEntity.get(realm, roomId, sessionId) .groupBy { it.userId } @@ -1733,7 +1740,7 @@ internal class RealmCryptoStore @Inject constructor( */ override fun tidyUpDataBase() { val prevWeekTs = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1_000 - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction(lazyRealmConfiguration) { realm -> // Only keep one week history realm.where() @@ -1765,6 +1772,6 @@ internal class RealmCryptoStore @Inject constructor( * Prints out database info */ override fun logDbUsageInfo() { - RealmDebugTools(realmConfiguration).logInfo("Crypto") + RealmDebugTools(lazyRealmConfiguration).logInfo("Crypto") } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 16c0655d85f..683cc928370 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -267,6 +267,7 @@ class HomeActivity : if (isFirstCreation()) { handleIntent(intent) } + homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) } private fun handleIntent(intent: Intent?) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt index 9df45e4553f..d94b4e929ac 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class HomeActivityViewActions : VectorViewModelAction { object PushPromptHasBeenReviewed : HomeActivityViewActions() + object ViewStarted : HomeActivityViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 59b9cafd6ef..54063c54935 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth @@ -69,15 +70,8 @@ class HomeActivityViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private var isStarted = false private var checkBootstrap = false - private var onceTrusted = false - - init { - cleanupFiles() - observeInitialSync() - checkSessionPushIsOn() - observeCrossSigningReset() - } private fun cleanupFiles() { // Mitigation: delete all cached decrypted files each time the application is started. @@ -87,28 +81,32 @@ class HomeActivityViewModel @AssistedInject constructor( private fun observeCrossSigningReset() { val safeActiveSession = activeSessionHolder.getSafeActiveSession() ?: return - onceTrusted = safeActiveSession - .cryptoService() - .crossSigningService().allPrivateKeysKnown() - - safeActiveSession - .flow() - .liveCrossSigningInfo(safeActiveSession.myUserId) - .onEach { - val isVerified = it.getOrNull()?.isTrusted() ?: false - if (!isVerified && onceTrusted) { - // cross signing keys have been reset - // Trigger a popup to re-verify - // Note: user can be null in case of logout - safeActiveSession.getUser(safeActiveSession.myUserId) - ?.toMatrixItem() - ?.let { user -> - _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) - } + viewModelScope.launch { + var onceTrusted = withContext(safeActiveSession.coroutineDispatchers.io) { + safeActiveSession + .cryptoService() + .crossSigningService().allPrivateKeysKnown() + } + + safeActiveSession + .flow() + .liveCrossSigningInfo(safeActiveSession.myUserId) + .onEach { + val isVerified = it.getOrNull()?.isTrusted() ?: false + if (!isVerified && onceTrusted) { + // cross signing keys have been reset + // Trigger a popup to re-verify + // Note: user can be null in case of logout + safeActiveSession.getUser(safeActiveSession.myUserId) + ?.toMatrixItem() + ?.let { user -> + _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) + } + } + onceTrusted = isVerified } - onceTrusted = isVerified - } - .launchIn(viewModelScope) + .launchIn(viewModelScope) + } } private fun observeInitialSync() { @@ -238,9 +236,19 @@ class HomeActivityViewModel @AssistedInject constructor( override fun handle(action: HomeActivityViewActions) { when (action) { - HomeActivityViewActions.PushPromptHasBeenReviewed -> { - vectorPreferences.setDidAskUserToEnableSessionPush() - } + HomeActivityViewActions.PushPromptHasBeenReviewed -> vectorPreferences.setDidAskUserToEnableSessionPush() + HomeActivityViewActions.ViewStarted -> initialize() }.exhaustive } + + private fun initialize() { + if (isStarted) { + return + } + isStarted = true + cleanupFiles() + observeInitialSync() + checkSessionPushIsOn() + observeCrossSigningReset() + } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 55d8e2e09ae..789bc6b1009 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -190,6 +190,7 @@ class HomeDetailFragment @Inject constructor( currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls()) invalidateOptionsMenu() } + unknownDeviceDetectorSharedViewModel.handle(UnknownDeviceDetectorSharedViewModel.Action.Start) } private fun handleCallStarted() { diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 8a36a4c19e7..8ea5b509cb0 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -26,6 +26,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction @@ -35,6 +36,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session @@ -56,12 +59,13 @@ data class DeviceDetectionInfo( ) class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState, - session: Session, + private val session: Session, private val vectorPreferences: VectorPreferences) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { sealed class Action : VectorViewModelAction { data class IgnoreDevice(val deviceIds: List) : Action() + object Start : Action() } @AssistedFactory @@ -72,66 +76,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val ignoredDeviceList = ArrayList() - - init { - - val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId) - .firstOrNull { it.deviceId == session.sessionParams.deviceId } - ?.firstTimeSeenLocalTs - ?: System.currentTimeMillis() - Timber.v("## Detector - Current Session first time seen $currentSessionTs") - - ignoredDeviceList.addAll( - vectorPreferences.getUnknownDeviceDismissedList().also { - Timber.v("## Detector - Remembered ignored list $it") - } - ) - - combine( - session.flow().liveUserCryptoDevices(session.myUserId), - session.flow().liveMyDevicesInfo(), - session.flow().liveCrossSigningPrivateKeys() - ) { cryptoList, infoList, pInfo -> - // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") -// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") - infoList - .filter { info -> - // filter verified session, by checking the crypto device info - cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() - } - // filter out ignored devices - .filter { !ignoredDeviceList.contains(it.deviceId) } - .sortedByDescending { it.lastSeenTs } - .map { deviceInfo -> - val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 - DeviceDetectionInfo( - deviceInfo, - deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, - pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change - ) - } - } - .distinctUntilChanged() - .execute { async -> - // Timber.v("## Detector trigger passed distinct") - copy( - myMatrixItem = session.getUser(session.myUserId)?.toMatrixItem(), - unknownSessions = async - ) - } - - session.flow().liveUserCryptoDevices(session.myUserId) - .distinctUntilChanged() - .sample(5_000) - .onEach { - // If we have a new crypto device change, we might want to trigger refresh of device info - session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) - } - .launchIn(viewModelScope) - - // trigger a refresh of lastSeen / last Ip - session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) - } + private var isStarted = false override fun handle(action: Action) { when (action) { @@ -147,6 +92,75 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted } } } + Action.Start -> startObserving() + }.exhaustive + } + + private fun startObserving() { + if (isStarted) { + return + } + isStarted = true + + viewModelScope.launch { + val currentSessionTs = withContext(session.coroutineDispatchers.io) { + session.cryptoService().getCryptoDeviceInfo(session.myUserId) + .firstOrNull { it.deviceId == session.sessionParams.deviceId } + ?.firstTimeSeenLocalTs + ?: System.currentTimeMillis() + } + Timber.v("## Detector - Current Session first time seen $currentSessionTs") + + ignoredDeviceList.addAll( + vectorPreferences.getUnknownDeviceDismissedList().also { + Timber.v("## Detector - Remembered ignored list $it") + } + ) + + combine( + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningPrivateKeys() + ) { cryptoList, infoList, pInfo -> + // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") +// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") + infoList + .filter { info -> + // filter verified session, by checking the crypto device info + cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() + } + // filter out ignored devices + .filter { !ignoredDeviceList.contains(it.deviceId) } + .sortedByDescending { it.lastSeenTs } + .map { deviceInfo -> + val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 + DeviceDetectionInfo( + deviceInfo, + deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, + pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change + ) + } + } + .distinctUntilChanged() + .execute { async -> + // Timber.v("## Detector trigger passed distinct") + copy( + myMatrixItem = session.getUser(session.myUserId)?.toMatrixItem(), + unknownSessions = async + ) + } + + session.flow().liveUserCryptoDevices(session.myUserId) + .distinctUntilChanged() + .sample(5_000) + .onEach { + // If we have a new crypto device change, we might want to trigger refresh of device info + session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) + } + .launchIn(viewModelScope) + + // trigger a refresh of lastSeen / last Ip + session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) } }