diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 66ee0c58f5..24df40d7e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -30,8 +30,10 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityF import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields @@ -43,27 +45,112 @@ import javax.inject.Inject internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { - // Version 1L added Cross Signing info persistence companion object { - const val CRYPTO_STORE_SCHEMA_VERSION = 6L + // 0, 1, 2: legacy Riot-Android + // 3: migrate to RiotX schema + // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) + const val CRYPTO_STORE_SCHEMA_VERSION = 9L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion") - if (oldVersion <= 0) migrateTo1(realm) - if (oldVersion <= 1) migrateTo2(realm) - if (oldVersion <= 2) migrateTo3(realm) + if (oldVersion <= 0) migrateTo1Legacy(realm) + if (oldVersion <= 1) migrateTo2Legacy(realm) + if (oldVersion <= 2) migrateTo3RiotX(realm) if (oldVersion <= 3) migrateTo4(realm) if (oldVersion <= 4) migrateTo5(realm) if (oldVersion <= 5) migrateTo6(realm) + if (oldVersion <= 6) migrateTo7(realm) + if (oldVersion <= 7) migrateTo8(realm) + if (oldVersion <= 8) migrateTo9(realm) } - private fun migrateTo1(realm: DynamicRealm) { + private fun migrateTo1Legacy(realm: DynamicRealm) { Timber.d("Step 0 -> 1") + Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0") + + realm.schema.get("OlmSessionEntity") + ?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java) + ?.transform { + it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0) + } + } + + private fun migrateTo2Legacy(realm: DynamicRealm) { + Timber.d("Step 1 -> 2") + Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields") + + realm.schema.get("IncomingRoomKeyRequestEntity") + ?.addField("requestBodyAlgorithm", String::class.java) + ?.addField("requestBodyRoomId", String::class.java) + ?.addField("requestBodySenderKey", String::class.java) + ?.addField("requestBodySessionId", String::class.java) + ?.transform { dynamicObject -> + val requestBodyString = dynamicObject.getString("requestBodyString") + try { + // It was a map before + val map: Map? = deserializeFromRealm(requestBodyString) + + map?.let { + dynamicObject.setString("requestBodyAlgorithm", it["algorithm"]) + dynamicObject.setString("requestBodyRoomId", it["room_id"]) + dynamicObject.setString("requestBodySenderKey", it["sender_key"]) + dynamicObject.setString("requestBodySessionId", it["session_id"]) + } + } catch (e: Exception) { + Timber.e(e, "Error") + } + } + ?.removeField("requestBodyString") + + Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields") + + realm.schema.get("OutgoingRoomKeyRequestEntity") + ?.addField("requestBodyAlgorithm", String::class.java) + ?.addField("requestBodyRoomId", String::class.java) + ?.addField("requestBodySenderKey", String::class.java) + ?.addField("requestBodySessionId", String::class.java) + ?.transform { dynamicObject -> + val requestBodyString = dynamicObject.getString("requestBodyString") + try { + // It was a map before + val map: Map? = deserializeFromRealm(requestBodyString) + + map?.let { + dynamicObject.setString("requestBodyAlgorithm", it["algorithm"]) + dynamicObject.setString("requestBodyRoomId", it["room_id"]) + dynamicObject.setString("requestBodySenderKey", it["sender_key"]) + dynamicObject.setString("requestBodySessionId", it["session_id"]) + } + } catch (e: Exception) { + Timber.e(e, "Error") + } + } + ?.removeField("requestBodyString") + + Timber.d("Create KeysBackupDataEntity") + + realm.schema.create("KeysBackupDataEntity") + .addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java) + .addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY) + .setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true) + .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java) + .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java) + } + + private fun migrateTo3RiotX(realm: DynamicRealm) { + Timber.d("Step 2 -> 3") + // TODO Remove log of realm + Timber.d("Migrate to RiotX model $realm") + } + + // Version 4L added Cross Signing info persistence + private fun migrateTo4(realm: DynamicRealm) { + Timber.d("Step 3 -> 4") Timber.d("Create KeyInfoEntity") - val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity") + val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity") .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java) .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true) .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java) @@ -73,7 +160,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java) .addField(KeyInfoEntityFields.SIGNATURES, String::class.java) .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java) - .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) + .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema) Timber.d("Create CrossSigningInfoEntity") @@ -112,7 +199,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java) ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java) ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true) - ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) + ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema) ?.transform { obj -> try { @@ -158,8 +245,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi ?.removeField("deviceInfoData") } - private fun migrateTo2(realm: DynamicRealm) { - Timber.d("Step 1 -> 2") + private fun migrateTo5(realm: DynamicRealm) { + Timber.d("Step 4 -> 5") realm.schema.remove("OutgoingRoomKeyRequestEntity") realm.schema.remove("IncomingRoomKeyRequestEntity") @@ -199,16 +286,16 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) } - private fun migrateTo3(realm: DynamicRealm) { - Timber.d("Step 2 -> 3") + private fun migrateTo6(realm: DynamicRealm) { + Timber.d("Step 5 -> 6") Timber.d("Updating CryptoMetadataEntity table") realm.schema.get("CryptoMetadataEntity") ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java) ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java) } - private fun migrateTo4(realm: DynamicRealm) { - Timber.d("Step 3 -> 4") + private fun migrateTo7(realm: DynamicRealm) { + Timber.d("Step 6 -> 7") Timber.d("Updating KeyInfoEntity table") val keyInfoEntities = realm.where("KeyInfoEntity").findAll() try { @@ -239,8 +326,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi } } - private fun migrateTo5(realm: DynamicRealm) { - Timber.d("Step 4 -> 5") + private fun migrateTo8(realm: DynamicRealm) { + Timber.d("Step 7 -> 8") realm.schema.create("MyDeviceLastSeenInfoEntity") .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java) .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID) @@ -261,7 +348,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi } // Fixes duplicate devices in UserEntity#devices - private fun migrateTo6(realm: DynamicRealm) { + private fun migrateTo9(realm: DynamicRealm) { + Timber.d("Step 8 -> 9") val userEntities = realm.where("UserEntity").findAll() userEntities.forEach { try {