Add tests for extracting and migrate data

This commit is contained in:
ganfra 2022-05-10 15:17:12 +02:00
parent f9f885418a
commit 88733784cd
9 changed files with 239 additions and 19 deletions

View File

@ -83,6 +83,11 @@ android {
test { test {
java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/sharedTest/java"
} }
main {
assets {
srcDirs 'src/main/assets', 'src/androidTest/assets'
}
}
} }
} }

View File

@ -0,0 +1,66 @@
/*
* Copyright 2020 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.migration
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.Realm
import org.amshove.kluent.internal.assertFails
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.common.TemporaryRealmConfigurationFactory
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.store.migration.fixtures.rustCryptoStoreMigrationConfiguration
import org.matrix.olm.OlmManager
@RunWith(AndroidJUnit4::class)
class ExtractMigrationDataUseCaseTest : InstrumentedTest {
@Rule
@JvmField
val realmConfigurationFactory = TemporaryRealmConfigurationFactory()
private val extractMigrationData = ExtractMigrationDataUseCase()
@Before
fun setup() {
// Ensure Olm is initialized
OlmManager()
}
@Test
fun given_a_valid_crypto_store_realm_file_then_extraction_should_be_successful() {
val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = true)
val migrationData = Realm.getInstance(realmConfiguration).use {
extractMigrationData(it)
}
assertNotNull(migrationData)
}
@Test
fun given_an_empty_crypto_store_realm_file_then_extraction_should_throw() {
val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = false)
assertFails {
Realm.getInstance(realmConfiguration).use {
extractMigrationData(it)
}
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2020 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.migration
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.common.TemporaryRealmConfigurationFactory
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.store.migration.fixtures.rustCryptoStoreMigrationConfiguration
import org.matrix.olm.OlmManager
import uniffi.olm.OlmMachine
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
class RustCryptoStoreMigrateUseCaseTest : InstrumentedTest {
@Rule
@JvmField
val realmConfigurationFactory = TemporaryRealmConfigurationFactory()
private val extractMigrationData = ExtractMigrationDataUseCase()
@Before
fun setup() {
// Ensure Olm is initialized
OlmManager()
}
@Test
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() = runBlocking {
val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = true)
val cryptoStoreMigrate = RustCryptoStoreMigrateUseCase(realmConfiguration, realmConfigurationFactory.root, extractMigrationData)
val latch = CountDownLatch(1)
val progressListener = ProgressListener(latch)
val result = cryptoStoreMigrate(progressListener)
latch.await()
assert(result.isSuccess)
val machine = OlmMachine("@ganfra146:matrix.org", "UTDQCHKKNS",realmConfigurationFactory.root.path, null)
assertEquals("mW7LWO4zmhH8Ttuvmzn27vm/USXSKBPgmg7FKQITLiU", machine.identityKeys()["ed25519"])
assertNotNull(machine.getBackupKeys())
val crossSigningStatus = machine.crossSigningStatus()
assertTrue(crossSigningStatus.hasMaster)
assertTrue(crossSigningStatus.hasSelfSigning)
assertTrue(crossSigningStatus.hasUserSigning)
}
@Test
fun given_an_empty_crypto_store_realm_file_then_migration_should_fail() = runBlocking {
val realmConfiguration = realmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore = false)
val cryptoStoreMigrate = RustCryptoStoreMigrateUseCase(realmConfiguration, realmConfigurationFactory.root, extractMigrationData)
val progressListener = ProgressListener()
val result = cryptoStoreMigrate(progressListener)
assert(result.isFailure)
}
private class ProgressListener(val latch: CountDownLatch? = null) : uniffi.olm.ProgressListener {
override fun onProgress(progress: Int, total: Int) {
if (progress == total) {
latch?.countDown()
}
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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.migration.fixtures
import io.realm.RealmConfiguration
import org.matrix.android.sdk.common.TemporaryRealmConfigurationFactory
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
fun TemporaryRealmConfigurationFactory.rustCryptoStoreMigrationConfiguration(populateCryptoStore: Boolean): RealmConfiguration {
return create(
realmFilename = "crypto_store_rust_migration.realm",
assetFilename = "crypto_store_rust_migration.realm".takeIf { populateCryptoStore },
schemaVersion = 15L,
module = RealmCryptoStoreModule()
)
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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.migration
class CleanUpCryptoStoreUseCase {
}

View File

@ -35,7 +35,15 @@ private val charset = Charset.forName("UTF-8")
internal class ExtractMigrationDataUseCase @Inject constructor() { internal class ExtractMigrationDataUseCase @Inject constructor() {
operator fun invoke(realm: Realm): MigrationData? { operator fun invoke(realm: Realm): MigrationData {
return try {
extract(realm) ?: throw ExtractMigrationDataFailure
} catch (failure: Throwable) {
throw ExtractMigrationDataFailure
}
}
private fun extract(realm: Realm): MigrationData? {
val metadataEntity = realm.where<CryptoMetadataEntity>().findFirst() ?: return null val metadataEntity = realm.where<CryptoMetadataEntity>().findFirst() ?: return null
val pickleKey = OlmUtility.getRandomKey() val pickleKey = OlmUtility.getRandomKey()
@ -61,7 +69,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() {
val isOlmAccountShared = metadataEntity.deviceKeysSentToServer val isOlmAccountShared = metadataEntity.deviceKeysSentToServer
val olmAccount = metadataEntity.getOlmAccount()!! val olmAccount = metadataEntity.getOlmAccount()!!
val pickledOlmAccount = olmAccount.pickle(pickleKey) val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
val pickledAccount = PickledAccount( val pickledAccount = PickledAccount(
userId = userId, userId = userId,
deviceId = deviceId, deviceId = deviceId,
@ -88,7 +96,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() {
private fun OlmInboundGroupSessionEntity.toPickledInboundGroupSession(pickleKey: ByteArray): PickledInboundGroupSession { private fun OlmInboundGroupSessionEntity.toPickledInboundGroupSession(pickleKey: ByteArray): PickledInboundGroupSession {
val senderKey = this.senderKey ?: "" val senderKey = this.senderKey ?: ""
val olmInboundGroupSession = getInboundGroupSession()!! val olmInboundGroupSession = getInboundGroupSession()!!
val pickledInboundGroupSession = olmInboundGroupSession.olmInboundGroupSession!!.pickle(pickleKey) val pickledInboundGroupSession = olmInboundGroupSession.olmInboundGroupSession!!.pickle(pickleKey, StringBuffer()).asString()
return PickledInboundGroupSession( return PickledInboundGroupSession(
pickle = pickledInboundGroupSession, pickle = pickledInboundGroupSession,
senderKey = senderKey, senderKey = senderKey,
@ -104,7 +112,7 @@ internal class ExtractMigrationDataUseCase @Inject constructor() {
val deviceKey = this.deviceKey ?: "" val deviceKey = this.deviceKey ?: ""
val lastReceivedMessageTs = this.lastReceivedMessageTs val lastReceivedMessageTs = this.lastReceivedMessageTs
val olmSession = getOlmSession()!! val olmSession = getOlmSession()!!
val pickledOlmSession = olmSession.pickle(pickleKey) val pickledOlmSession = olmSession.pickle(pickleKey, StringBuffer()).asString()
return PickledSession( return PickledSession(
pickle = pickledOlmSession, pickle = pickledOlmSession,
senderKey = deviceKey, senderKey = deviceKey,
@ -114,14 +122,5 @@ internal class ExtractMigrationDataUseCase @Inject constructor() {
) )
} }
private fun Any.pickle(pickleKey: ByteArray): String { private fun ByteArray.asString() = String(this, charset)
return try {
val pickleMethod = this.javaClass.getDeclaredMethod("serialize", ByteArray::class.java, StringBuffer::class.java)
pickleMethod.isAccessible = true
val pickled = pickleMethod.invoke(this, pickleKey, StringBuffer())!!
String(pickled as ByteArray, charset)
} catch (throwable: Throwable) {
""
}
}
} }

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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.migration
object ExtractMigrationDataFailure : java.lang.RuntimeException("Can't proceed with migration, crypto store is empty or some necessary data is missing.")

View File

@ -21,7 +21,6 @@ import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import timber.log.Timber
import uniffi.olm.ProgressListener import uniffi.olm.ProgressListener
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -38,10 +37,6 @@ internal class RustCryptoStoreMigrateUseCase @Inject constructor(
private suspend fun migrate(progressListener: ProgressListener) { private suspend fun migrate(progressListener: ProgressListener) {
awaitTransaction(realmConfiguration) { realm: Realm -> awaitTransaction(realmConfiguration) { realm: Realm ->
val migrationData = extractMigrationData(realm) val migrationData = extractMigrationData(realm)
if (migrationData == null) {
Timber.v("No migration to do, return")
return@awaitTransaction
}
uniffi.olm.migrate(migrationData, dataDir.path, null, progressListener) uniffi.olm.migrate(migrationData, dataDir.path, null, progressListener)
} }
} }