Merge pull request #7879 from vector-im/feature/bma/still_investigating

Reduce number of crypto database transactions when handling the sync response
This commit is contained in:
Benoit Marty 2023-01-06 15:16:16 +01:00 committed by GitHub
commit b7076a13dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 390 additions and 193 deletions

1
changelog.d/7879.bugfix Normal file
View File

@ -0,0 +1 @@
Reduce number of crypto database transactions when handling the sync response

View File

@ -3506,4 +3506,7 @@
<string name="message_reply_to_sender_sent_video">sent a video.</string> <string name="message_reply_to_sender_sent_video">sent a video.</string>
<string name="message_reply_to_sender_sent_sticker">sent a sticker.</string> <string name="message_reply_to_sender_sent_sticker">sent a sticker.</string>
<string name="message_reply_to_sender_created_poll">created a poll.</string> <string name="message_reply_to_sender_created_poll">created a poll.</string>
<string name="settings_access_token">Access Token</string>
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
</resources> </resources>

View File

@ -0,0 +1,26 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.crosssigning
/**
* Container for the three cross signing keys: master, self signing and user signing.
*/
data class UserIdentity(
val masterKey: CryptoCrossSigningKey?,
val selfSigningKey: CryptoCrossSigningKey?,
val userSigningKey: CryptoCrossSigningKey?,
)

View File

@ -89,6 +89,7 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
@ -192,21 +193,21 @@ internal class DefaultCryptoService @Inject constructor(
private val isStarting = AtomicBoolean(false) private val isStarting = AtomicBoolean(false)
private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
fun onStateEvent(roomId: String, event: Event) { fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) {
when (event.type) { when (event.type) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator)
} }
} }
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) {
// handle state events // handle state events
if (event.isStateEvent()) { if (event.isStateEvent()) {
when (event.type) { when (event.type) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator)
} }
} }
@ -430,8 +431,10 @@ internal class DefaultCryptoService @Inject constructor(
* A sync response has been received. * A sync response has been received.
* *
* @param syncResponse the syncResponse * @param syncResponse the syncResponse
* @param cryptoStoreAggregator data aggregated during the sync response treatment to store
*/ */
fun onSyncCompleted(syncResponse: SyncResponse) { fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
cryptoStore.storeData(cryptoStoreAggregator)
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching { runCatching {
if (syncResponse.deviceLists != null) { if (syncResponse.deviceLists != null) {
@ -998,15 +1001,26 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) {
if (!event.isStateEvent()) return if (!event.isStateEvent()) return
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>() val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
val historyVisibility = eventContent?.historyVisibility val historyVisibility = eventContent?.historyVisibility
if (historyVisibility == null) { if (historyVisibility == null) {
cryptoStore.setShouldShareHistory(roomId, false) if (cryptoStoreAggregator != null) {
cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false
} else {
// Store immediately
cryptoStore.setShouldShareHistory(roomId, false)
}
} else { } else {
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) if (cryptoStoreAggregator != null) {
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED
cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory()
} else {
// Store immediately
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
}
} }
} }

View File

@ -25,11 +25,13 @@ import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.extensions.measureMetric
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
@ -371,6 +373,8 @@ internal class DeviceListManager @Inject constructor(
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
} }
val userDataToStore = UserDataToStore()
for (userId in filteredUsers) { for (userId in filteredUsers) {
// al devices = // al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
@ -404,7 +408,7 @@ internal class DeviceListManager @Inject constructor(
} }
// Update the store // Update the store
// Note that devices which aren't in the response will be removed from the stores // Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, workingCopy) userDataToStore.userDevices[userId] = workingCopy
} }
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
@ -416,14 +420,15 @@ internal class DeviceListManager @Inject constructor(
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
} }
cryptoStore.storeUserCrossSigningKeys( userDataToStore.userIdentities[userId] = UserIdentity(
userId, masterKey = masterKey,
masterKey, selfSigningKey = selfSigningKey,
selfSigningKey, userSigningKey = userSigningKey
userSigningKey
) )
} }
cryptoStore.storeData(userDataToStore)
// Update devices trust for these users // Update devices trust for these users
// dispatchDeviceChange(downloadUsers) // dispatchDeviceChange(downloadUsers)

View File

@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmOutboundGroupSession import org.matrix.olm.OlmOutboundGroupSession
@ -230,11 +231,12 @@ internal interface IMXCryptoStore {
*/ */
fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?)
fun storeUserCrossSigningKeys( /**
* Store the cross signing keys for the user userId.
*/
fun storeUserIdentity(
userId: String, userId: String,
masterKey: CryptoCrossSigningKey?, userIdentity: UserIdentity
selfSigningKey: CryptoCrossSigningKey?,
userSigningKey: CryptoCrossSigningKey?
) )
/** /**
@ -290,6 +292,13 @@ internal interface IMXCryptoStore {
fun shouldEncryptForInvitedMembers(roomId: String): Boolean fun shouldEncryptForInvitedMembers(roomId: String): Boolean
/**
* Sets a boolean flag that will determine whether or not this device should encrypt Events for
* invited members.
*
* @param roomId the room id
* @param shouldEncryptForInvitedMembers The boolean flag
*/
fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
fun shouldShareHistory(roomId: String): Boolean fun shouldShareHistory(roomId: String): Boolean
@ -580,4 +589,14 @@ internal interface IMXCryptoStore {
fun areDeviceKeysUploaded(): Boolean fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase() fun tidyUpDataBase()
fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest> fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest>
/**
* Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator].
*/
fun storeData(cryptoStoreAggregator: CryptoStoreAggregator)
/**
* Store a bunch of data related to the users. @See [UserDataToStore].
*/
fun storeData(userDataToStore: UserDataToStore)
} }

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.store
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
internal data class UserDataToStore(
/**
* Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]).
*/
val userDevices: MutableMap<String, Map<String, CryptoDeviceInfo>> = mutableMapOf(),
/**
* Map of userId -> [UserIdentity].
*/
val userIdentities: MutableMap<String, UserIdentity> = mutableMapOf(),
)

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.store.db
data class CryptoStoreAggregator(
val setShouldShareHistoryData: MutableMap<String, Boolean> = mutableMapOf(),
val setShouldEncryptForInvitedMembersData: MutableMap<String, Boolean> = mutableMapOf(),
) {
fun isEmpty(): Boolean {
return setShouldShareHistoryData.isEmpty() &&
setShouldEncryptForInvitedMembersData.isEmpty()
}
}

View File

@ -20,10 +20,12 @@ import android.util.Base64
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.RealmObject import io.realm.RealmObject
import timber.log.Timber
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import kotlin.system.measureTimeMillis
/** /**
* Get realm, invoke the action, close realm, and return the result of the action. * Get realm, invoke the action, close realm, and return the result of the action.
@ -55,10 +57,12 @@ internal fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: Realm
/** /**
* Get realm instance, invoke the action in a transaction and close realm. * Get realm instance, invoke the action in a transaction and close realm.
*/ */
internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
Realm.getInstance(realmConfiguration).use { realm -> measureTimeMillis {
realm.executeTransaction { action.invoke(it) } Realm.getInstance(realmConfiguration).use { realm ->
} realm.executeTransaction { action.invoke(it) }
}
}.also { Timber.w("doRealmTransaction for $tag took $it millis") }
} }
internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {

View File

@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
@ -147,7 +148,7 @@ internal class RealmCryptoStore @Inject constructor(
init { init {
// Ensure CryptoMetadataEntity is inserted in DB // Ensure CryptoMetadataEntity is inserted in DB
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("init", realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst() var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
var deleteAll = false var deleteAll = false
@ -189,7 +190,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun deleteStore() { override fun deleteStore() {
doRealmTransaction(realmConfiguration) { doRealmTransaction("deleteStore", realmConfiguration) {
it.deleteAll() it.deleteAll()
} }
} }
@ -218,7 +219,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun storeDeviceId(deviceId: String) { override fun storeDeviceId(deviceId: String) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("storeDeviceId", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.deviceId = deviceId it.where<CryptoMetadataEntity>().findFirst()?.deviceId = deviceId
} }
} }
@ -230,7 +231,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun saveOlmAccount() { override fun saveOlmAccount() {
doRealmTransaction(realmConfiguration) { doRealmTransaction("saveOlmAccount", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.putOlmAccount(olmAccount) it.where<CryptoMetadataEntity>().findFirst()?.putOlmAccount(olmAccount)
} }
} }
@ -248,7 +249,7 @@ internal class RealmCryptoStore @Inject constructor(
@Synchronized @Synchronized
override fun getOrCreateOlmAccount(): OlmAccount { override fun getOrCreateOlmAccount(): OlmAccount {
doRealmTransaction(realmConfiguration) { doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) {
val metaData = it.where<CryptoMetadataEntity>().findFirst() val metaData = it.where<CryptoMetadataEntity>().findFirst()
val existing = metaData!!.getOlmAccount() val existing = metaData!!.getOlmAccount()
if (existing == null) { if (existing == null) {
@ -288,129 +289,139 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) { override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeUserDevices", realmConfiguration) { realm ->
if (devices == null) { storeUserDevices(realm, userId, devices)
Timber.d("Remove user $userId") }
// Remove the user }
UserEntity.delete(realm, userId)
} else { private fun storeUserDevices(realm: Realm, userId: String, devices: Map<String, CryptoDeviceInfo>?) {
val userEntity = UserEntity.getOrCreate(realm, userId) if (devices == null) {
// First delete the removed devices Timber.d("Remove user $userId")
val deviceIds = devices.keys // Remove the user
userEntity.devices.toTypedArray().iterator().let { UserEntity.delete(realm, userId)
while (it.hasNext()) { } else {
val deviceInfoEntity = it.next() val userEntity = UserEntity.getOrCreate(realm, userId)
if (deviceInfoEntity.deviceId !in deviceIds) { // First delete the removed devices
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") val deviceIds = devices.keys
deviceInfoEntity.deleteOnCascade() userEntity.devices.toTypedArray().iterator().let {
} while (it.hasNext()) {
val deviceInfoEntity = it.next()
if (deviceInfoEntity.deviceId !in deviceIds) {
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
deviceInfoEntity.deleteOnCascade()
} }
} }
// Then update existing devices or add new one }
devices.values.forEach { cryptoDeviceInfo -> // Then update existing devices or add new one
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } devices.values.forEach { cryptoDeviceInfo ->
if (existingDeviceInfoEntity == null) { val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
// Add the device if (existingDeviceInfoEntity == null) {
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") // Add the device
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
newEntity.firstTimeSeenLocalTs = clock.epochMillis() val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
userEntity.devices.add(newEntity) newEntity.firstTimeSeenLocalTs = clock.epochMillis()
} else { userEntity.devices.add(newEntity)
// Update the device } else {
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") // Update the device
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
} CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
} }
} }
} }
} }
override fun storeUserCrossSigningKeys( override fun storeUserIdentity(
userId: String, userId: String,
masterKey: CryptoCrossSigningKey?, userIdentity: UserIdentity,
selfSigningKey: CryptoCrossSigningKey?,
userSigningKey: CryptoCrossSigningKey?
) { ) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeUserIdentity", realmConfiguration) { realm ->
UserEntity.getOrCreate(realm, userId) storeUserIdentity(realm, userId, userIdentity)
.let { userEntity -> }
if (masterKey == null || selfSigningKey == null) { }
// The user has disabled cross signing?
userEntity.crossSigningInfoEntity?.deleteOnCascade() private fun storeUserIdentity(
userEntity.crossSigningInfoEntity = null realm: Realm,
} else { userId: String,
var shouldResetMyDevicesLocalTrust = false userIdentity: UserIdentity,
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> ) {
// What should we do if we detect a change of the keys? UserEntity.getOrCreate(realm, userId)
val existingMaster = signingInfo.getMasterKey() .let { userEntity ->
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) {
crossSigningKeysMapper.update(existingMaster, masterKey) // The user has disabled cross signing?
} else { userEntity.crossSigningInfoEntity?.deleteOnCascade()
Timber.d("## CrossSigning MSK change for $userId") userEntity.crossSigningInfoEntity = null
val keyEntity = crossSigningKeysMapper.map(masterKey) } else {
signingInfo.setMasterKey(keyEntity) var shouldResetMyDevicesLocalTrust = false
if (userId == this.userId) { CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
shouldResetMyDevicesLocalTrust = true // What should we do if we detect a change of the keys?
// my msk has changed! clear my private key val existingMaster = signingInfo.getMasterKey()
// Could we have some race here? e.g I am the one that did change the keys if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) {
// could i get this update to early and clear the private keys? crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey)
// -> initializeCrossSigning is guarding for that by storing all at once } else {
realm.where<CryptoMetadataEntity>().findFirst()?.apply { Timber.d("## CrossSigning MSK change for $userId")
xSignMasterPrivateKey = null val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey)
} signingInfo.setMasterKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my msk has changed! clear my private key
// Could we have some race here? e.g I am the one that did change the keys
// could i get this update to early and clear the private keys?
// -> initializeCrossSigning is guarding for that by storing all at once
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = null
} }
} }
val existingSelfSigned = signingInfo.getSelfSignedKey()
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey)
} else {
Timber.d("## CrossSigning SSK change for $userId")
val keyEntity = crossSigningKeysMapper.map(selfSigningKey)
signingInfo.setSelfSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my ssk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignSelfSignedPrivateKey = null
}
}
}
// Only for me
if (userSigningKey != null) {
val existingUSK = signingInfo.getUserSigningKey()
if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingUSK, userSigningKey)
} else {
Timber.d("## CrossSigning USK change for $userId")
val keyEntity = crossSigningKeysMapper.map(userSigningKey)
signingInfo.setUserSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my usk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignUserPrivateKey = null
}
}
}
}
// When my cross signing keys are reset, we consider clearing all existing device trust
if (shouldResetMyDevicesLocalTrust) {
realm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, this.userId)
.findFirst()
?.devices?.forEach {
it?.trustLevelEntity?.crossSignedVerified = false
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
}
}
userEntity.crossSigningInfoEntity = signingInfo
} }
val existingSelfSigned = signingInfo.getSelfSignedKey()
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey)
} else {
Timber.d("## CrossSigning SSK change for $userId")
val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey)
signingInfo.setSelfSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my ssk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignSelfSignedPrivateKey = null
}
}
}
// Only for me
if (userIdentity.userSigningKey != null) {
val existingUSK = signingInfo.getUserSigningKey()
if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey)
} else {
Timber.d("## CrossSigning USK change for $userId")
val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey)
signingInfo.setUserSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my usk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignUserPrivateKey = null
}
}
}
}
// When my cross signing keys are reset, we consider clearing all existing device trust
if (shouldResetMyDevicesLocalTrust) {
realm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, this.userId)
.findFirst()
?.devices?.forEach {
it?.trustLevelEntity?.crossSignedVerified = false
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
}
}
userEntity.crossSigningInfoEntity = signingInfo
} }
} }
} }
} }
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
@ -480,7 +491,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}")
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply { realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = msk xSignMasterPrivateKey = msk
xSignUserPrivateKey = usk xSignUserPrivateKey = usk
@ -490,7 +501,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply { realm.where<CryptoMetadataEntity>().findFirst()?.apply {
keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKey = recoveryKey
keyBackupRecoveryKeyVersion = version keyBackupRecoveryKeyVersion = version
@ -516,7 +527,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun storeMSKPrivateKey(msk: String?) { override fun storeMSKPrivateKey(msk: String?) {
Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ")
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply { realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = msk xSignMasterPrivateKey = msk
} }
@ -525,7 +536,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun storeSSKPrivateKey(ssk: String?) { override fun storeSSKPrivateKey(ssk: String?) {
Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ")
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply { realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignSelfSignedPrivateKey = ssk xSignSelfSignedPrivateKey = ssk
} }
@ -534,7 +545,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun storeUSKPrivateKey(usk: String?) { override fun storeUSKPrivateKey(usk: String?) {
Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ")
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply { realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignUserPrivateKey = usk xSignUserPrivateKey = usk
} }
@ -667,7 +678,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun storeRoomAlgorithm(roomId: String, algorithm: String?) { override fun storeRoomAlgorithm(roomId: String, algorithm: String?) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("storeRoomAlgorithm", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> CryptoRoomEntity.getOrCreate(it, roomId).let { entity ->
entity.algorithm = algorithm entity.algorithm = algorithm
// store anyway the new algorithm, but mark the room // store anyway the new algorithm, but mark the room
@ -708,7 +719,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers
} }
} }
@ -716,7 +727,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.v("setShouldShareHistory for room $roomId is $shouldShareHistory") .v("setShouldShareHistory for room $roomId is $shouldShareHistory")
doRealmTransaction(realmConfiguration) { doRealmTransaction("setShouldShareHistory", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory
} }
} }
@ -733,7 +744,7 @@ internal class RealmCryptoStore @Inject constructor(
if (sessionIdentifier != null) { if (sessionIdentifier != null) {
val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey) val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey)
doRealmTransaction(realmConfiguration) { doRealmTransaction("storeSession", realmConfiguration) {
val realmOlmSession = OlmSessionEntity().apply { val realmOlmSession = OlmSessionEntity().apply {
primaryKey = key primaryKey = key
sessionId = sessionIdentifier sessionId = sessionIdentifier
@ -790,7 +801,7 @@ internal class RealmCryptoStore @Inject constructor(
return return
} }
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm ->
sessions.forEach { wrapper -> sessions.forEach { wrapper ->
val sessionIdentifier = try { val sessionIdentifier = try {
@ -914,7 +925,7 @@ internal class RealmCryptoStore @Inject constructor(
override fun removeInboundGroupSession(sessionId: String, senderKey: String) { override fun removeInboundGroupSession(sessionId: String, senderKey: String) {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
doRealmTransaction(realmConfiguration) { doRealmTransaction("removeInboundGroupSession", realmConfiguration) {
it.where<OlmInboundGroupSessionEntity>() it.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findAll() .findAll()
@ -933,7 +944,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setKeyBackupVersion(keyBackupVersion: String?) { override fun setKeyBackupVersion(keyBackupVersion: String?) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("setKeyBackupVersion", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.backupVersion = keyBackupVersion it.where<CryptoMetadataEntity>().findFirst()?.backupVersion = keyBackupVersion
} }
} }
@ -945,7 +956,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("setKeysBackupData", realmConfiguration) {
if (keysBackupData == null) { if (keysBackupData == null) {
// Clear the table // Clear the table
it.where<KeysBackupDataEntity>() it.where<KeysBackupDataEntity>()
@ -959,7 +970,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun resetBackupMarkers() { override fun resetBackupMarkers() {
doRealmTransaction(realmConfiguration) { doRealmTransaction("resetBackupMarkers", realmConfiguration) {
it.where<OlmInboundGroupSessionEntity>() it.where<OlmInboundGroupSessionEntity>()
.findAll() .findAll()
.map { inboundGroupSession -> .map { inboundGroupSession ->
@ -973,7 +984,7 @@ internal class RealmCryptoStore @Inject constructor(
return return
} }
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm ->
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
try { try {
val sessionIdentifier = val sessionIdentifier =
@ -1032,13 +1043,13 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices = block it.where<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices = block
} }
} }
override fun enableKeyGossiping(enable: Boolean) { override fun enableKeyGossiping(enable: Boolean) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("enableKeyGossiping", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.globalEnableKeyGossiping = enable it.where<CryptoMetadataEntity>().findFirst()?.globalEnableKeyGossiping = enable
} }
} }
@ -1062,13 +1073,13 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun enableShareKeyOnInvite(enable: Boolean) { override fun enableShareKeyOnInvite(enable: Boolean) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite = enable it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite = enable
} }
} }
override fun setDeviceKeysUploaded(uploaded: Boolean) { override fun setDeviceKeysUploaded(uploaded: Boolean) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded
} }
} }
@ -1115,7 +1126,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm ->
CryptoRoomEntity.getById(realm, roomId) CryptoRoomEntity.getById(realm, roomId)
?.blacklistUnverifiedDevices = block ?.blacklistUnverifiedDevices = block
} }
@ -1135,7 +1146,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) { override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) {
doRealmTransaction(realmConfiguration) { doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) {
deviceTrackingStatuses deviceTrackingStatuses
.map { entry -> .map { entry ->
UserEntity.getOrCreate(it, entry.key) UserEntity.getOrCreate(it, entry.key)
@ -1268,7 +1279,7 @@ internal class RealmCryptoStore @Inject constructor(
): OutgoingKeyRequest { ): OutgoingKeyRequest {
// Insert the request and return the one passed in parameter // Insert the request and return the one passed in parameter
lateinit var request: OutgoingKeyRequest lateinit var request: OutgoingKeyRequest
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm ->
val existing = realm.where<OutgoingKeyRequestEntity>() val existing = realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId)
@ -1306,7 +1317,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) { override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm ->
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
.findFirst()?.apply { .findFirst()?.apply {
@ -1320,7 +1331,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) { override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm ->
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
.findFirst()?.apply { .findFirst()?.apply {
@ -1337,7 +1348,7 @@ internal class RealmCryptoStore @Inject constructor(
fromDevice: String?, fromDevice: String?,
event: Event event: Event
) { ) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm ->
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId)
.equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId)
@ -1353,7 +1364,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun deleteOutgoingRoomKeyRequest(requestId: String) { override fun deleteOutgoingRoomKeyRequest(requestId: String) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm ->
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
.findFirst()?.deleteOnCascade() .findFirst()?.deleteOnCascade()
@ -1361,7 +1372,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) { override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm ->
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name) .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name)
.findAll() .findAll()
@ -1497,7 +1508,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { userId -> realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { userId ->
addOrUpdateCrossSigningInfo(realm, userId, info) addOrUpdateCrossSigningInfo(realm, userId, info)
} }
@ -1505,7 +1516,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm ->
val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
.findFirst() .findFirst()
@ -1525,7 +1536,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("setDeviceTrust", realmConfiguration) { realm ->
realm.where(DeviceInfoEntity::class.java) realm.where(DeviceInfoEntity::class.java)
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
.findFirst()?.let { deviceInfoEntity -> .findFirst()?.let { deviceInfoEntity ->
@ -1545,7 +1556,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun clearOtherUserTrust() { override fun clearOtherUserTrust() {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm ->
val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java)
.findAll() .findAll()
xInfoEntities?.forEach { info -> xInfoEntities?.forEach { info ->
@ -1560,7 +1571,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun updateUsersTrust(check: (String) -> Boolean) { override fun updateUsersTrust(check: (String) -> Boolean) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("updateUsersTrust", realmConfiguration) { realm ->
val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java)
.findAll() .findAll()
xInfoEntities?.forEach { xInfoEntity -> xInfoEntities?.forEach { xInfoEntity ->
@ -1668,13 +1679,13 @@ internal class RealmCryptoStore @Inject constructor(
} }
override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm ->
addOrUpdateCrossSigningInfo(realm, userId, info) addOrUpdateCrossSigningInfo(realm, userId, info)
} }
} }
override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { myUserId -> realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { myUserId ->
CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity ->
val level = xInfoEntity.trustLevelEntity val level = xInfoEntity.trustLevelEntity
@ -1713,7 +1724,7 @@ internal class RealmCryptoStore @Inject constructor(
val roomId = withHeldContent.roomId ?: return val roomId = withHeldContent.roomId ?: return
val sessionId = withHeldContent.sessionId ?: return val sessionId = withHeldContent.sessionId ?: return
if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm ->
WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let {
it.code = withHeldContent.code it.code = withHeldContent.code
it.senderKey = withHeldContent.senderKey it.senderKey = withHeldContent.senderKey
@ -1745,7 +1756,7 @@ internal class RealmCryptoStore @Inject constructor(
deviceIdentityKey: String, deviceIdentityKey: String,
chainIndex: Int chainIndex: Int
) { ) {
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm ->
SharedSessionEntity.create( SharedSessionEntity.create(
realm = realm, realm = realm,
roomId = roomId, roomId = roomId,
@ -1794,7 +1805,7 @@ internal class RealmCryptoStore @Inject constructor(
*/ */
override fun tidyUpDataBase() { override fun tidyUpDataBase() {
val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
doRealmTransaction(realmConfiguration) { realm -> doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm ->
// Clean the old ones? // Clean the old ones?
realm.where<OutgoingKeyRequestEntity>() realm.where<OutgoingKeyRequestEntity>()
@ -1815,4 +1826,31 @@ internal class RealmCryptoStore @Inject constructor(
// Can we do something for WithHeldSessionEntity? // Can we do something for WithHeldSessionEntity?
} }
} }
override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) {
if (cryptoStoreAggregator.isEmpty()) {
return
}
doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm ->
// setShouldShareHistory
cryptoStoreAggregator.setShouldShareHistoryData.forEach {
CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value
}
// setShouldEncryptForInvitedMembers
cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach {
CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value
}
}
}
override fun storeData(userDataToStore: UserDataToStore) {
doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm ->
userDataToStore.userDevices.forEach {
storeUserDevices(realm, it.key, it.value)
}
userDataToStore.userIdentities.forEach {
storeUserIdentity(realm, it.key, it.value)
}
}
}
} }

View File

@ -42,14 +42,12 @@ internal class StreamEventsManager @Inject constructor() {
listeners.remove(listener) listeners.remove(listener)
} }
fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { fun dispatchLiveEventReceived(event: Event, roomId: String) {
Timber.v("## dispatchLiveEventReceived ${event.eventId}") Timber.v("## dispatchLiveEventReceived ${event.eventId}")
coroutineScope.launch { coroutineScope.launch {
if (!initialSync) { listeners.forEach {
listeners.forEach { tryOrNull {
tryOrNull { it.onLiveEvent(roomId, event)
it.onLiveEvent(roomId, event)
}
} }
} }
} }

View File

@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor(
} }
// Give info to crypto module // Give info to crypto module
cryptoService.onStateEvent(roomId, event) cryptoService.onStateEvent(roomId, event, null)
} }
roomMemberContentsByUser.getOrPut(event.senderId) { roomMemberContentsByUser.getOrPut(event.senderId) {

View File

@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.session.SessionListeners import org.matrix.android.sdk.internal.session.SessionListeners
@ -92,7 +93,7 @@ internal class SyncResponseHandler @Inject constructor(
postTreatmentSyncResponse(syncResponse, isInitialSync) postTreatmentSyncResponse(syncResponse, isInitialSync)
markCryptoSyncCompleted(syncResponse) markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator)
handlePostSync() handlePostSync()
@ -218,10 +219,10 @@ internal class SyncResponseHandler @Inject constructor(
} }
} }
private fun markCryptoSyncCompleted(syncResponse: SyncResponse) { private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") {
measureTimeMillis { measureTimeMillis {
cryptoSyncHandler.onSyncCompleted(syncResponse) cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator)
}.also { }.also {
Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms")
} }

View File

@ -16,6 +16,8 @@
package org.matrix.android.sdk.internal.session.sync package org.matrix.android.sdk.internal.session.sync
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
internal class SyncResponsePostTreatmentAggregator { internal class SyncResponsePostTreatmentAggregator {
// List of RoomId // List of RoomId
val ephemeralFilesToDelete = mutableListOf<String>() val ephemeralFilesToDelete = mutableListOf<String>()
@ -28,4 +30,7 @@ internal class SyncResponsePostTreatmentAggregator {
// Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync
val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf<String>() val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf<String>()
// For the crypto store
val cryptoStoreAggregator = CryptoStoreAggregator()
} }

View File

@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
import org.matrix.android.sdk.internal.session.sync.ProgressReporter import org.matrix.android.sdk.internal.session.sync.ProgressReporter
@ -85,8 +86,8 @@ internal class CryptoSyncHandler @Inject constructor(
} }
} }
fun onSyncCompleted(syncResponse: SyncResponse) { fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
cryptoService.onSyncCompleted(syncResponse) cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator)
} }
/** /**

View File

@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor(
root = eventEntity root = eventEntity
} }
// Give info to crypto module // Give info to crypto module
cryptoService.onStateEvent(roomId, event) cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator)
roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator) roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator)
} }
} }
@ -376,8 +376,15 @@ internal class RoomSyncHandler @Inject constructor(
roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) }
roomTypingUsersHandler.handle(realm, roomId, null) roomTypingUsersHandler.handle(realm, roomId, null)
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE)
roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSummaryUpdater.update(
roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) realm,
roomId,
membership,
roomSync.summary,
roomSync.unreadNotifications,
roomSync.unreadThreadNotifications,
aggregator = aggregator,
)
return roomEntity return roomEntity
} }
@ -423,7 +430,9 @@ internal class RoomSyncHandler @Inject constructor(
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
eventIds.add(event.eventId) eventIds.add(event.eventId)
liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) if (!isInitialSync) {
liveEventService.get().dispatchLiveEventReceived(event, roomId)
}
if (event.isEncrypted() && !isInitialSync) { if (event.isEncrypted() && !isInitialSync) {
try { try {
@ -486,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor(
} }
} }
// Give info to crypto module // Give info to crypto module
cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator)
// Try to remove local echo // Try to remove local echo
event.unsignedData?.transactionId?.also { txId -> event.unsignedData?.transactionId?.also { txId ->

View File

@ -25,6 +25,7 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.NightlyProxy import im.vector.app.features.home.NightlyProxy
import im.vector.app.features.rageshake.RageShake import im.vector.app.features.rageshake.RageShake
@ -64,6 +65,14 @@ class VectorSettingsAdvancedSettingsFragment :
override fun bindPref() { override fun bindPref() {
setupRageShakeSection() setupRageShakeSection()
setupNightlySection() setupNightlySection()
setupDevToolsSection()
}
private fun setupDevToolsSection() {
findPreference<VectorPreference>("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener {
copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken)
true
}
} }
private fun setupRageShakeSection() { private fun setupRageShakeSection() {

View File

@ -93,6 +93,12 @@
android:title="@string/settings_key_requests" android:title="@string/settings_key_requests"
app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" /> app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" />
<im.vector.app.core.preference.VectorPreference
android:key="SETTINGS_ACCESS_TOKEN"
android:persistent="false"
android:summary="@string/settings_access_token_summary"
android:title="@string/settings_access_token" />
</im.vector.app.core.preference.VectorPreferenceCategory> </im.vector.app.core.preference.VectorPreferenceCategory>
<im.vector.app.core.preference.VectorPreferenceCategory <im.vector.app.core.preference.VectorPreferenceCategory