Skip to content

Commit 4c4ef0d

Browse files
committed
Batch insertion of user data after downloading keys.
1 parent f26178f commit 4c4ef0d

File tree

4 files changed

+155
-102
lines changed

4 files changed

+155
-102
lines changed

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
3030
import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper
3131
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
3232
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
33+
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
3334
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
3435
import org.matrix.android.sdk.internal.session.SessionScope
3536
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
@@ -371,6 +372,8 @@ internal class DeviceListManager @Inject constructor(
371372
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
372373
}
373374

375+
val userDataToStore = UserDataToStore()
376+
374377
for (userId in filteredUsers) {
375378
// al devices =
376379
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
@@ -404,7 +407,7 @@ internal class DeviceListManager @Inject constructor(
404407
}
405408
// Update the store
406409
// Note that devices which aren't in the response will be removed from the stores
407-
cryptoStore.storeUserDevices(userId, workingCopy)
410+
userDataToStore.userDevices[userId] = workingCopy
408411
}
409412

410413
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
@@ -416,14 +419,11 @@ internal class DeviceListManager @Inject constructor(
416419
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
417420
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
418421
}
419-
cryptoStore.storeUserCrossSigningKeys(
420-
userId,
421-
masterKey,
422-
selfSigningKey,
423-
userSigningKey
424-
)
422+
userDataToStore.userCrossSigningKeys[userId] = Triple(masterKey, selfSigningKey, userSigningKey)
425423
}
426424

425+
cryptoStore.storeUserDataToStore(userDataToStore)
426+
427427
// Update devices trust for these users
428428
// dispatchDeviceChange(downloadUsers)
429429

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,4 +583,6 @@ internal interface IMXCryptoStore {
583583
fun areDeviceKeysUploaded(): Boolean
584584
fun tidyUpDataBase()
585585
fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest>
586+
587+
fun storeUserDataToStore(userDataToStore: UserDataToStore)
586588
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.crypto.store
18+
19+
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
20+
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
21+
22+
internal data class UserDataToStore(
23+
val userDevices: MutableMap<String, Map<String, CryptoDeviceInfo>> = mutableMapOf(),
24+
val userCrossSigningKeys: MutableMap<String, Triple<CryptoCrossSigningKey?, CryptoCrossSigningKey?, CryptoCrossSigningKey?>> = mutableMapOf(),
25+
)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt

Lines changed: 121 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe
5555
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
5656
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
5757
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
58+
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
5859
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
5960
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
6061
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
@@ -289,37 +290,41 @@ internal class RealmCryptoStore @Inject constructor(
289290

290291
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
291292
doRealmTransaction("storeUserDevices", realmConfiguration) { realm ->
292-
if (devices == null) {
293-
Timber.d("Remove user $userId")
294-
// Remove the user
295-
UserEntity.delete(realm, userId)
296-
} else {
297-
val userEntity = UserEntity.getOrCreate(realm, userId)
298-
// First delete the removed devices
299-
val deviceIds = devices.keys
300-
userEntity.devices.toTypedArray().iterator().let {
301-
while (it.hasNext()) {
302-
val deviceInfoEntity = it.next()
303-
if (deviceInfoEntity.deviceId !in deviceIds) {
304-
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
305-
deviceInfoEntity.deleteOnCascade()
306-
}
293+
storeUserDevices(realm, userId, devices)
294+
}
295+
}
296+
297+
private fun storeUserDevices(realm: Realm, userId: String, devices: Map<String, CryptoDeviceInfo>?) {
298+
if (devices == null) {
299+
Timber.d("Remove user $userId")
300+
// Remove the user
301+
UserEntity.delete(realm, userId)
302+
} else {
303+
val userEntity = UserEntity.getOrCreate(realm, userId)
304+
// First delete the removed devices
305+
val deviceIds = devices.keys
306+
userEntity.devices.toTypedArray().iterator().let {
307+
while (it.hasNext()) {
308+
val deviceInfoEntity = it.next()
309+
if (deviceInfoEntity.deviceId !in deviceIds) {
310+
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
311+
deviceInfoEntity.deleteOnCascade()
307312
}
308313
}
309-
// Then update existing devices or add new one
310-
devices.values.forEach { cryptoDeviceInfo ->
311-
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
312-
if (existingDeviceInfoEntity == null) {
313-
// Add the device
314-
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
315-
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
316-
newEntity.firstTimeSeenLocalTs = clock.epochMillis()
317-
userEntity.devices.add(newEntity)
318-
} else {
319-
// Update the device
320-
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
321-
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
322-
}
314+
}
315+
// Then update existing devices or add new one
316+
devices.values.forEach { cryptoDeviceInfo ->
317+
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
318+
if (existingDeviceInfoEntity == null) {
319+
// Add the device
320+
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
321+
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
322+
newEntity.firstTimeSeenLocalTs = clock.epochMillis()
323+
userEntity.devices.add(newEntity)
324+
} else {
325+
// Update the device
326+
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
327+
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
323328
}
324329
}
325330
}
@@ -332,85 +337,95 @@ internal class RealmCryptoStore @Inject constructor(
332337
userSigningKey: CryptoCrossSigningKey?
333338
) {
334339
doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm ->
335-
UserEntity.getOrCreate(realm, userId)
336-
.let { userEntity ->
337-
if (masterKey == null || selfSigningKey == null) {
338-
// The user has disabled cross signing?
339-
userEntity.crossSigningInfoEntity?.deleteOnCascade()
340-
userEntity.crossSigningInfoEntity = null
341-
} else {
342-
var shouldResetMyDevicesLocalTrust = false
343-
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
344-
// What should we do if we detect a change of the keys?
345-
val existingMaster = signingInfo.getMasterKey()
346-
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) {
347-
crossSigningKeysMapper.update(existingMaster, masterKey)
348-
} else {
349-
Timber.d("## CrossSigning MSK change for $userId")
350-
val keyEntity = crossSigningKeysMapper.map(masterKey)
351-
signingInfo.setMasterKey(keyEntity)
352-
if (userId == this.userId) {
353-
shouldResetMyDevicesLocalTrust = true
354-
// my msk has changed! clear my private key
355-
// Could we have some race here? e.g I am the one that did change the keys
356-
// could i get this update to early and clear the private keys?
357-
// -> initializeCrossSigning is guarding for that by storing all at once
358-
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
359-
xSignMasterPrivateKey = null
360-
}
340+
storeUserCrossSigningKeys(realm, userId, masterKey, selfSigningKey, userSigningKey)
341+
}
342+
}
343+
344+
private fun storeUserCrossSigningKeys(
345+
realm: Realm,
346+
userId: String,
347+
masterKey: CryptoCrossSigningKey?,
348+
selfSigningKey: CryptoCrossSigningKey?,
349+
userSigningKey: CryptoCrossSigningKey?
350+
) {
351+
UserEntity.getOrCreate(realm, userId)
352+
.let { userEntity ->
353+
if (masterKey == null || selfSigningKey == null) {
354+
// The user has disabled cross signing?
355+
userEntity.crossSigningInfoEntity?.deleteOnCascade()
356+
userEntity.crossSigningInfoEntity = null
357+
} else {
358+
var shouldResetMyDevicesLocalTrust = false
359+
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
360+
// What should we do if we detect a change of the keys?
361+
val existingMaster = signingInfo.getMasterKey()
362+
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) {
363+
crossSigningKeysMapper.update(existingMaster, masterKey)
364+
} else {
365+
Timber.d("## CrossSigning MSK change for $userId")
366+
val keyEntity = crossSigningKeysMapper.map(masterKey)
367+
signingInfo.setMasterKey(keyEntity)
368+
if (userId == this.userId) {
369+
shouldResetMyDevicesLocalTrust = true
370+
// my msk has changed! clear my private key
371+
// Could we have some race here? e.g I am the one that did change the keys
372+
// could i get this update to early and clear the private keys?
373+
// -> initializeCrossSigning is guarding for that by storing all at once
374+
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
375+
xSignMasterPrivateKey = null
361376
}
362377
}
378+
}
363379

364-
val existingSelfSigned = signingInfo.getSelfSignedKey()
365-
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
366-
crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey)
380+
val existingSelfSigned = signingInfo.getSelfSignedKey()
381+
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
382+
crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey)
383+
} else {
384+
Timber.d("## CrossSigning SSK change for $userId")
385+
val keyEntity = crossSigningKeysMapper.map(selfSigningKey)
386+
signingInfo.setSelfSignedKey(keyEntity)
387+
if (userId == this.userId) {
388+
shouldResetMyDevicesLocalTrust = true
389+
// my ssk has changed! clear my private key
390+
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
391+
xSignSelfSignedPrivateKey = null
392+
}
393+
}
394+
}
395+
396+
// Only for me
397+
if (userSigningKey != null) {
398+
val existingUSK = signingInfo.getUserSigningKey()
399+
if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) {
400+
crossSigningKeysMapper.update(existingUSK, userSigningKey)
367401
} else {
368-
Timber.d("## CrossSigning SSK change for $userId")
369-
val keyEntity = crossSigningKeysMapper.map(selfSigningKey)
370-
signingInfo.setSelfSignedKey(keyEntity)
402+
Timber.d("## CrossSigning USK change for $userId")
403+
val keyEntity = crossSigningKeysMapper.map(userSigningKey)
404+
signingInfo.setUserSignedKey(keyEntity)
371405
if (userId == this.userId) {
372406
shouldResetMyDevicesLocalTrust = true
373-
// my ssk has changed! clear my private key
407+
// my usk has changed! clear my private key
374408
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
375-
xSignSelfSignedPrivateKey = null
409+
xSignUserPrivateKey = null
376410
}
377411
}
378412
}
413+
}
379414

380-
// Only for me
381-
if (userSigningKey != null) {
382-
val existingUSK = signingInfo.getUserSigningKey()
383-
if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) {
384-
crossSigningKeysMapper.update(existingUSK, userSigningKey)
385-
} else {
386-
Timber.d("## CrossSigning USK change for $userId")
387-
val keyEntity = crossSigningKeysMapper.map(userSigningKey)
388-
signingInfo.setUserSignedKey(keyEntity)
389-
if (userId == this.userId) {
390-
shouldResetMyDevicesLocalTrust = true
391-
// my usk has changed! clear my private key
392-
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
393-
xSignUserPrivateKey = null
394-
}
415+
// When my cross signing keys are reset, we consider clearing all existing device trust
416+
if (shouldResetMyDevicesLocalTrust) {
417+
realm.where<UserEntity>()
418+
.equalTo(UserEntityFields.USER_ID, this.userId)
419+
.findFirst()
420+
?.devices?.forEach {
421+
it?.trustLevelEntity?.crossSignedVerified = false
422+
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
395423
}
396-
}
397-
}
398-
399-
// When my cross signing keys are reset, we consider clearing all existing device trust
400-
if (shouldResetMyDevicesLocalTrust) {
401-
realm.where<UserEntity>()
402-
.equalTo(UserEntityFields.USER_ID, this.userId)
403-
.findFirst()
404-
?.devices?.forEach {
405-
it?.trustLevelEntity?.crossSignedVerified = false
406-
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
407-
}
408-
}
409-
userEntity.crossSigningInfoEntity = signingInfo
410424
}
425+
userEntity.crossSigningInfoEntity = signingInfo
411426
}
412427
}
413-
}
428+
}
414429
}
415430

416431
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
@@ -1831,13 +1846,24 @@ internal class RealmCryptoStore @Inject constructor(
18311846
}
18321847
doRealmTransaction("onSyncCompleted", realmConfiguration) { realm ->
18331848
// setShouldShareHistory
1834-
aggregator.setShouldShareHistoryData.map {
1849+
aggregator.setShouldShareHistoryData.forEach {
18351850
CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value
18361851
}
18371852
// setShouldEncryptForInvitedMembers
1838-
aggregator.setShouldEncryptForInvitedMembersData.map {
1853+
aggregator.setShouldEncryptForInvitedMembersData.forEach {
18391854
CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value
18401855
}
18411856
}
18421857
}
1858+
1859+
override fun storeUserDataToStore(userDataToStore: UserDataToStore) {
1860+
doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm ->
1861+
userDataToStore.userDevices.forEach {
1862+
storeUserDevices(realm, it.key, it.value)
1863+
}
1864+
userDataToStore.userCrossSigningKeys.forEach {
1865+
storeUserCrossSigningKeys(realm, it.key, it.value.first, it.value.second, it.value.third)
1866+
}
1867+
}
1868+
}
18431869
}

0 commit comments

Comments
 (0)