K/V restore using single file
This commit is contained in:
parent
0c915e5eb8
commit
a0f3c6b45f
10 changed files with 405 additions and 158 deletions
|
@ -272,6 +272,7 @@ internal class BackupCoordinator(
|
|||
val salt = metadataManager.salt
|
||||
val result = kv.performBackup(packageInfo, data, flags, token, salt)
|
||||
if (result == TRANSPORT_OK && packageName == MAGIC_PACKAGE_MANAGER) {
|
||||
// TODO move to finish backup of @pm@ so we can upload the DB before
|
||||
// hook in here to back up APKs of apps that are otherwise not allowed for backup
|
||||
backUpApksOfNotBackedUpPackages()
|
||||
}
|
||||
|
@ -392,7 +393,9 @@ internal class BackupCoordinator(
|
|||
}
|
||||
// getCurrentPackage() not-null because we have state
|
||||
onPackageBackedUp(kv.getCurrentPackage()!!, BackupType.KV)
|
||||
val isPmBackup = kv.getCurrentPackage()!!.packageName == MAGIC_PACKAGE_MANAGER
|
||||
kv.finishBackup()
|
||||
// TODO move @pm@ backup hook here
|
||||
}
|
||||
full.hasState() -> {
|
||||
check(!kv.hasState()) {
|
||||
|
|
|
@ -8,37 +8,59 @@ import android.database.sqlite.SQLiteOpenHelper
|
|||
import android.provider.BaseColumns
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
interface KvDbManager {
|
||||
fun getDb(packageName: String): KVDb
|
||||
fun getDb(packageName: String, isRestore: Boolean = false): KVDb
|
||||
|
||||
/**
|
||||
* Use only for backup.
|
||||
*/
|
||||
fun getDbInputStream(packageName: String): InputStream
|
||||
|
||||
/**
|
||||
* Use only for restore.
|
||||
*/
|
||||
fun getDbOutputStream(packageName: String): OutputStream
|
||||
|
||||
/**
|
||||
* Use only for backup.
|
||||
*/
|
||||
fun existsDb(packageName: String): Boolean
|
||||
fun deleteDb(packageName: String): Boolean
|
||||
fun deleteDb(packageName: String, isRestore: Boolean = false): Boolean
|
||||
}
|
||||
|
||||
class KvDbManagerImpl(private val context: Context) : KvDbManager {
|
||||
|
||||
override fun getDb(packageName: String): KVDb {
|
||||
return KVDbImpl(context, getFileName(packageName))
|
||||
override fun getDb(packageName: String, isRestore: Boolean): KVDb {
|
||||
return KVDbImpl(context, getFileName(packageName, isRestore))
|
||||
}
|
||||
|
||||
private fun getFileName(packageName: String) = "kv_$packageName.db"
|
||||
private fun getFileName(packageName: String, isRestore: Boolean): String {
|
||||
val prefix = if (isRestore) "restore_" else ""
|
||||
return "${prefix}kv_$packageName.db"
|
||||
}
|
||||
|
||||
private fun getDbFile(packageName: String): File {
|
||||
return context.getDatabasePath(getFileName(packageName))
|
||||
private fun getDbFile(packageName: String, isRestore: Boolean = false): File {
|
||||
return context.getDatabasePath(getFileName(packageName, isRestore))
|
||||
}
|
||||
|
||||
override fun getDbInputStream(packageName: String): InputStream {
|
||||
return FileInputStream(getDbFile(packageName))
|
||||
}
|
||||
|
||||
override fun getDbOutputStream(packageName: String): OutputStream {
|
||||
return FileOutputStream(getDbFile(packageName, true))
|
||||
}
|
||||
|
||||
override fun existsDb(packageName: String): Boolean {
|
||||
return getDbFile(packageName).isFile
|
||||
}
|
||||
|
||||
override fun deleteDb(packageName: String): Boolean {
|
||||
return getDbFile(packageName).delete()
|
||||
override fun deleteDb(packageName: String, isRestore: Boolean): Boolean {
|
||||
return getDbFile(packageName, isRestore).delete()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,19 +15,25 @@ import com.stevesoltys.seedvault.header.HeaderReader
|
|||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||
import com.stevesoltys.seedvault.header.VERSION
|
||||
import com.stevesoltys.seedvault.header.getADForKV
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
||||
import com.stevesoltys.seedvault.transport.backup.KVDb
|
||||
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
||||
import libcore.io.IoUtils.closeQuietly
|
||||
import java.io.IOException
|
||||
import java.security.GeneralSecurityException
|
||||
import java.util.ArrayList
|
||||
import java.util.zip.GZIPInputStream
|
||||
import javax.crypto.AEADBadTagException
|
||||
|
||||
private class KVRestoreState(
|
||||
val version: Byte,
|
||||
val token: Long,
|
||||
val name: String,
|
||||
val packageInfo: PackageInfo,
|
||||
/**
|
||||
* Optional [PackageInfo] for single package restore, optimizes restore of @pm@
|
||||
*/
|
||||
@Deprecated("TODO remove?")
|
||||
val pmPackageInfo: PackageInfo?
|
||||
)
|
||||
|
||||
|
@ -35,20 +41,25 @@ private val TAG = KVRestore::class.java.simpleName
|
|||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
internal class KVRestore(
|
||||
private val plugin: KVRestorePlugin,
|
||||
private val plugin: BackupPlugin,
|
||||
private val legacyPlugin: KVRestorePlugin,
|
||||
private val outputFactory: OutputFactory,
|
||||
private val headerReader: HeaderReader,
|
||||
private val crypto: Crypto
|
||||
private val crypto: Crypto,
|
||||
private val dbManager: KvDbManager
|
||||
) {
|
||||
|
||||
private var state: KVRestoreState? = null
|
||||
|
||||
/**
|
||||
* Return true if there are records stored for the given package.
|
||||
*
|
||||
* Deprecated. Use only for v0 backups.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
@Deprecated("Use BackupPlugin#hasData() instead")
|
||||
suspend fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean {
|
||||
return plugin.hasDataForPackage(token, packageInfo)
|
||||
return legacyPlugin.hasDataForPackage(token, packageInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,10 +73,11 @@ internal class KVRestore(
|
|||
fun initializeState(
|
||||
version: Byte,
|
||||
token: Long,
|
||||
name: String,
|
||||
packageInfo: PackageInfo,
|
||||
pmPackageInfo: PackageInfo? = null
|
||||
) {
|
||||
state = KVRestoreState(version, token, packageInfo, pmPackageInfo)
|
||||
state = KVRestoreState(version, token, name, packageInfo, pmPackageInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,12 +90,66 @@ internal class KVRestore(
|
|||
suspend fun getRestoreData(data: ParcelFileDescriptor): Int {
|
||||
val state = this.state ?: throw IllegalStateException("no state")
|
||||
|
||||
// take legacy path for version 0
|
||||
if (state.version == 0x00.toByte()) return getRestoreDataV0(state, data)
|
||||
|
||||
return try {
|
||||
val db = getRestoreDb(state)
|
||||
val out = outputFactory.getBackupDataOutput(data)
|
||||
db.getAll().sortedBy { it.first }.forEach { (key, value) ->
|
||||
val size = value.size
|
||||
Log.v(TAG, " ... key=$key size=$size")
|
||||
out.writeEntityHeader(key, size)
|
||||
out.writeEntityData(value, size)
|
||||
}
|
||||
TRANSPORT_OK
|
||||
} catch (e: UnsupportedVersionException) {
|
||||
Log.e(TAG, "Unsupported version in backup: ${e.version}", e)
|
||||
TRANSPORT_ERROR
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Unable to process K/V backup database", e)
|
||||
TRANSPORT_ERROR
|
||||
} catch (e: GeneralSecurityException) {
|
||||
Log.e(TAG, "General security exception while reading backup database", e)
|
||||
TRANSPORT_ERROR
|
||||
} catch (e: AEADBadTagException) {
|
||||
Log.e(TAG, "Decryption failed", e)
|
||||
TRANSPORT_ERROR
|
||||
} finally {
|
||||
dbManager.deleteDb(state.packageInfo.packageName, true)
|
||||
this.state = null
|
||||
closeQuietly(data)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, GeneralSecurityException::class, UnsupportedVersionException::class)
|
||||
private suspend fun getRestoreDb(state: KVRestoreState): KVDb {
|
||||
val packageName = state.packageInfo.packageName
|
||||
plugin.getInputStream(state.token, state.name).use { inputStream ->
|
||||
headerReader.readVersion(inputStream, state.version)
|
||||
val ad = getADForKV(VERSION, packageName)
|
||||
crypto.newDecryptingStream(inputStream, ad).use { decryptedStream ->
|
||||
GZIPInputStream(decryptedStream).use { gzipStream ->
|
||||
dbManager.getDbOutputStream(packageName).use { outputStream ->
|
||||
gzipStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbManager.getDb(packageName, true)
|
||||
}
|
||||
|
||||
//
|
||||
// v0 restore legacy code below
|
||||
//
|
||||
|
||||
private suspend fun getRestoreDataV0(state: KVRestoreState, data: ParcelFileDescriptor): Int {
|
||||
// The restore set is the concatenation of the individual record blobs,
|
||||
// each of which is a file in the package's directory.
|
||||
// We return the data in lexical order sorted by key,
|
||||
// so that apps which use synthetic keys like BLOB_1, BLOB_2, etc
|
||||
// will see the date in the most obvious order.
|
||||
val sortedKeys = getSortedKeys(state.token, state.packageInfo)
|
||||
val sortedKeys = getSortedKeysV0(state.token, state.packageInfo)
|
||||
if (sortedKeys == null) {
|
||||
// nextRestorePackage() ensures the dir exists, so this is an error
|
||||
Log.e(TAG, "No keys for package: ${state.packageInfo.packageName}")
|
||||
|
@ -96,7 +162,7 @@ internal class KVRestore(
|
|||
return try {
|
||||
val dataOutput = outputFactory.getBackupDataOutput(data)
|
||||
for (keyEntry in sortedKeys) {
|
||||
readAndWriteValue(state, keyEntry, dataOutput)
|
||||
readAndWriteValueV0(state, keyEntry, dataOutput)
|
||||
}
|
||||
TRANSPORT_OK
|
||||
} catch (e: IOException) {
|
||||
|
@ -105,9 +171,6 @@ internal class KVRestore(
|
|||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Security exception while reading backup records", e)
|
||||
TRANSPORT_ERROR
|
||||
} catch (e: GeneralSecurityException) {
|
||||
Log.e(TAG, "General security exception while reading backup records", e)
|
||||
TRANSPORT_ERROR
|
||||
} catch (e: UnsupportedVersionException) {
|
||||
Log.e(TAG, "Unsupported version in backup: ${e.version}", e)
|
||||
TRANSPORT_ERROR
|
||||
|
@ -124,9 +187,9 @@ internal class KVRestore(
|
|||
* Return a list of the records (represented by key files) in the given directory,
|
||||
* sorted lexically by the Base64-decoded key file name, not by the on-disk filename.
|
||||
*/
|
||||
private suspend fun getSortedKeys(token: Long, packageInfo: PackageInfo): List<DecodedKey>? {
|
||||
private suspend fun getSortedKeysV0(token: Long, packageInfo: PackageInfo): List<DecodedKey>? {
|
||||
val records: List<String> = try {
|
||||
plugin.listRecords(token, packageInfo)
|
||||
legacyPlugin.listRecords(token, packageInfo)
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
|
@ -150,24 +213,18 @@ internal class KVRestore(
|
|||
/**
|
||||
* Read the encrypted value for the given key and write it to the given [BackupDataOutput].
|
||||
*/
|
||||
@Suppress("Deprecation")
|
||||
@Throws(IOException::class, UnsupportedVersionException::class, GeneralSecurityException::class)
|
||||
private suspend fun readAndWriteValue(
|
||||
private suspend fun readAndWriteValueV0(
|
||||
state: KVRestoreState,
|
||||
dKey: DecodedKey,
|
||||
out: BackupDataOutput
|
||||
) = plugin.getInputStreamForRecord(state.token, state.packageInfo, dKey.base64Key)
|
||||
) = legacyPlugin.getInputStreamForRecord(state.token, state.packageInfo, dKey.base64Key)
|
||||
.use { inputStream ->
|
||||
val version = headerReader.readVersion(inputStream, state.version)
|
||||
val packageName = state.packageInfo.packageName
|
||||
val value = if (version == 0.toByte()) {
|
||||
crypto.decryptHeader(inputStream, version, packageName, dKey.key)
|
||||
crypto.decryptMultipleSegments(inputStream)
|
||||
} else {
|
||||
val ad = getADForKV(VERSION, packageName)
|
||||
crypto.newDecryptingStream(inputStream, ad).use { decryptedStream ->
|
||||
decryptedStream.readBytes()
|
||||
}
|
||||
}
|
||||
crypto.decryptHeader(inputStream, version, packageName, dKey.key)
|
||||
val value = crypto.decryptMultipleSegments(inputStream)
|
||||
val size = value.size
|
||||
Log.v(TAG, " ... key=${dKey.key} size=$size")
|
||||
|
||||
|
|
|
@ -207,7 +207,13 @@ internal class RestoreCoordinator(
|
|||
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
|
||||
if (plugin.hasData(state.token, name)) {
|
||||
Log.i(TAG, "Found K/V data for $packageName.")
|
||||
kv.initializeState(version, state.token, packageInfo, state.pmPackageInfo)
|
||||
kv.initializeState(
|
||||
version = version,
|
||||
token = state.token,
|
||||
name = name,
|
||||
packageInfo = packageInfo,
|
||||
pmPackageInfo = state.pmPackageInfo
|
||||
)
|
||||
state.currentPackage = packageName
|
||||
TYPE_KEY_VALUE
|
||||
} else throw IOException("No data found for $packageName. Skipping.")
|
||||
|
@ -243,7 +249,7 @@ internal class RestoreCoordinator(
|
|||
// check key/value data first and if available, don't even check for full data
|
||||
kv.hasDataForPackage(state.token, packageInfo) -> {
|
||||
Log.i(TAG, "Found K/V data for $packageName.")
|
||||
kv.initializeState(0x00, state.token, packageInfo, state.pmPackageInfo)
|
||||
kv.initializeState(0x00, state.token, "", packageInfo, state.pmPackageInfo)
|
||||
state.currentPackage = packageName
|
||||
TYPE_KEY_VALUE
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.koin.dsl.module
|
|||
|
||||
val restoreModule = module {
|
||||
single { OutputFactory() }
|
||||
single { KVRestore(get<RestorePlugin>().kvRestorePlugin, get(), get(), get()) }
|
||||
single { KVRestore(get(), get<RestorePlugin>().kvRestorePlugin, get(), get(), get(), get()) }
|
||||
single { FullRestore(get(), get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
|
||||
single {
|
||||
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||
|
|
|
@ -10,7 +10,6 @@ import android.os.ParcelFileDescriptor
|
|||
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
||||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||
import com.stevesoltys.seedvault.encodeBase64
|
||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||
import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
|
@ -39,6 +38,7 @@ import io.mockk.coEvery
|
|||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -81,7 +81,14 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
)
|
||||
|
||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
||||
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
private val kvRestore = KVRestore(
|
||||
backupPlugin,
|
||||
kvRestorePlugin,
|
||||
outputFactory,
|
||||
headerReader,
|
||||
cryptoImpl,
|
||||
dbManager
|
||||
)
|
||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
||||
private val fullRestore =
|
||||
FullRestore(backupPlugin, fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
|
@ -104,9 +111,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
private val metadataOutputStream = ByteArrayOutputStream()
|
||||
private val packageMetadata = PackageMetadata(time = 0L)
|
||||
private val key = "RestoreKey"
|
||||
private val key64 = key.encodeBase64()
|
||||
private val key2 = "RestoreKey2"
|
||||
private val key264 = key2.encodeBase64()
|
||||
|
||||
// as we use real crypto, we need a real name for packageInfo
|
||||
private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName)
|
||||
|
@ -116,7 +121,6 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
val value = CapturingSlot<ByteArray>()
|
||||
val value2 = CapturingSlot<ByteArray>()
|
||||
val bOutputStream = ByteArrayOutputStream()
|
||||
val bOutputStream2 = ByteArrayOutputStream()
|
||||
|
||||
every { settingsManager.getToken() } returns token
|
||||
every { metadataManager.salt } returns salt
|
||||
|
@ -170,29 +174,21 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
// restore finds the backed up key and writes the decrypted value
|
||||
val backupDataOutput = mockk<BackupDataOutput>()
|
||||
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
|
||||
val rInputStream2 = ByteArrayInputStream(bOutputStream2.toByteArray())
|
||||
coEvery { kvRestorePlugin.listRecords(token, packageInfo) } returns listOf(key64, key264)
|
||||
coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream
|
||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
|
||||
coEvery {
|
||||
kvRestorePlugin.getInputStreamForRecord(
|
||||
token,
|
||||
packageInfo,
|
||||
key64
|
||||
)
|
||||
} returns rInputStream
|
||||
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
|
||||
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
|
||||
coEvery {
|
||||
kvRestorePlugin.getInputStreamForRecord(
|
||||
token,
|
||||
packageInfo,
|
||||
key264
|
||||
)
|
||||
} returns rInputStream2
|
||||
every { backupDataOutput.writeEntityHeader(key2, appData2.size) } returns 1137
|
||||
every { backupDataOutput.writeEntityData(appData2, appData2.size) } returns appData2.size
|
||||
|
||||
assertEquals(TRANSPORT_OK, restore.getRestoreData(fileDescriptor))
|
||||
|
||||
verify {
|
||||
backupDataOutput.writeEntityHeader(key, appData.size)
|
||||
backupDataOutput.writeEntityData(appData, appData.size)
|
||||
backupDataOutput.writeEntityHeader(key2, appData2.size)
|
||||
backupDataOutput.writeEntityData(appData2, appData2.size)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -246,19 +242,17 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
// restore finds the backed up key and writes the decrypted value
|
||||
val backupDataOutput = mockk<BackupDataOutput>()
|
||||
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
|
||||
coEvery { kvRestorePlugin.listRecords(token, packageInfo) } returns listOf(key64)
|
||||
coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream
|
||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
|
||||
coEvery {
|
||||
kvRestorePlugin.getInputStreamForRecord(
|
||||
token,
|
||||
packageInfo,
|
||||
key64
|
||||
)
|
||||
} returns rInputStream
|
||||
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
|
||||
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
|
||||
|
||||
assertEquals(TRANSPORT_OK, restore.getRestoreData(fileDescriptor))
|
||||
|
||||
verify {
|
||||
backupDataOutput.writeEntityHeader(key, appData.size)
|
||||
backupDataOutput.writeEntityData(appData, appData.size)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -3,22 +3,29 @@ package com.stevesoltys.seedvault.transport.backup
|
|||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.toByteArrayFromHex
|
||||
import com.stevesoltys.seedvault.toHexString
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertNull
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.json.JSONObject
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
class TestKvDbManager : KvDbManager {
|
||||
|
||||
private var db: TestKVDb? = null
|
||||
private val outputStream = ByteArrayOutputStream()
|
||||
|
||||
override fun getDb(packageName: String): KVDb {
|
||||
override fun getDb(packageName: String, isRestore: Boolean): KVDb {
|
||||
if (isRestore) {
|
||||
readDbFromStream(ByteArrayInputStream(outputStream.toByteArray()))
|
||||
return this.db!!
|
||||
}
|
||||
return TestKVDb().apply { db = this }
|
||||
}
|
||||
|
||||
|
@ -26,11 +33,16 @@ class TestKvDbManager : KvDbManager {
|
|||
return ByteArrayInputStream(db!!.serialize().toByteArray())
|
||||
}
|
||||
|
||||
override fun getDbOutputStream(packageName: String): OutputStream {
|
||||
outputStream.reset()
|
||||
return outputStream
|
||||
}
|
||||
|
||||
override fun existsDb(packageName: String): Boolean {
|
||||
return db != null
|
||||
}
|
||||
|
||||
override fun deleteDb(packageName: String): Boolean {
|
||||
override fun deleteDb(packageName: String, isRestore: Boolean): Boolean {
|
||||
clearDb()
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -10,48 +10,57 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
|||
import com.stevesoltys.seedvault.header.VERSION
|
||||
import com.stevesoltys.seedvault.header.VersionHeader
|
||||
import com.stevesoltys.seedvault.header.getADForKV
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
||||
import com.stevesoltys.seedvault.transport.backup.KVDb
|
||||
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.GeneralSecurityException
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
internal class KVRestoreTest : RestoreTest() {
|
||||
|
||||
private val plugin = mockk<KVRestorePlugin>()
|
||||
private val plugin = mockk<BackupPlugin>()
|
||||
private val legacyPlugin = mockk<KVRestorePlugin>()
|
||||
private val dbManager = mockk<KvDbManager>()
|
||||
private val output = mockk<BackupDataOutput>()
|
||||
private val restore = KVRestore(plugin, outputFactory, headerReader, crypto)
|
||||
private val restore =
|
||||
KVRestore(plugin, legacyPlugin, outputFactory, headerReader, crypto, dbManager)
|
||||
|
||||
private val db = mockk<KVDb>()
|
||||
private val ad = getADForKV(VERSION, packageInfo.packageName)
|
||||
|
||||
private val key = "Restore Key"
|
||||
private val key64 = key.encodeBase64()
|
||||
private val key2 = "Restore Key2"
|
||||
private val key264 = key2.encodeBase64()
|
||||
private val data2 = getRandomByteArray()
|
||||
|
||||
private val outputStream = ByteArrayOutputStream().apply {
|
||||
GZIPOutputStream(this).close()
|
||||
}
|
||||
private val decryptInputStream = ByteArrayInputStream(outputStream.toByteArray())
|
||||
|
||||
init {
|
||||
// for InputStream#readBytes()
|
||||
mockkStatic("kotlin.io.ByteStreamsKt")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hasDataForPackage() delegates to plugin`() = runBlocking {
|
||||
val result = Random.nextBoolean()
|
||||
|
||||
coEvery { plugin.hasDataForPackage(token, packageInfo) } returns result
|
||||
|
||||
assertEquals(result, restore.hasDataForPackage(token, packageInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getRestoreData() throws without initializing state`() {
|
||||
coAssertThrows(IllegalStateException::class.java) {
|
||||
|
@ -60,22 +69,133 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `listing records throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
fun `unexpected version aborts with error`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, name, packageInfo)
|
||||
|
||||
coEvery { plugin.listRecords(token, packageInfo) } throws IOException()
|
||||
coEvery { plugin.getInputStream(token, name) } returns inputStream
|
||||
every {
|
||||
headerReader.readVersion(inputStream, VERSION)
|
||||
} throws UnsupportedVersionException(Byte.MAX_VALUE)
|
||||
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
verifyStreamWasClosed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `newDecryptingStream throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, name, packageInfo)
|
||||
|
||||
coEvery { plugin.getInputStream(token, name) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException()
|
||||
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
verifyStreamWasClosed()
|
||||
|
||||
verifyAll {
|
||||
dbManager.deleteDb(packageInfo.packageName, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `writeEntityHeader throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, name, packageInfo)
|
||||
|
||||
coEvery { plugin.getInputStream(token, name) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
|
||||
every {
|
||||
dbManager.getDbOutputStream(packageInfo.packageName)
|
||||
} returns ByteArrayOutputStream()
|
||||
every { dbManager.getDb(packageInfo.packageName, true) } returns db
|
||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns output
|
||||
every { db.getAll() } returns listOf(Pair(key, data))
|
||||
every { output.writeEntityHeader(key, data.size) } throws IOException()
|
||||
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
verifyStreamWasClosed()
|
||||
|
||||
verify {
|
||||
dbManager.deleteDb(packageInfo.packageName, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two records get restored`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, name, packageInfo)
|
||||
|
||||
coEvery { plugin.getInputStream(token, name) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
|
||||
every {
|
||||
dbManager.getDbOutputStream(packageInfo.packageName)
|
||||
} returns ByteArrayOutputStream()
|
||||
every { dbManager.getDb(packageInfo.packageName, true) } returns db
|
||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns output
|
||||
every { db.getAll() } returns listOf(
|
||||
Pair(key, data),
|
||||
Pair(key2, data2)
|
||||
)
|
||||
every { output.writeEntityHeader(key, data.size) } returns 42
|
||||
every { output.writeEntityData(data, data.size) } returns data.size
|
||||
every { output.writeEntityHeader(key2, data2.size) } returns 42
|
||||
every { output.writeEntityData(data2, data2.size) } returns data2.size
|
||||
|
||||
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_OK, restore.getRestoreData(fileDescriptor))
|
||||
verifyStreamWasClosed()
|
||||
|
||||
verify {
|
||||
output.writeEntityHeader(key, data.size)
|
||||
output.writeEntityData(data, data.size)
|
||||
output.writeEntityHeader(key2, data2.size)
|
||||
output.writeEntityData(data2, data2.size)
|
||||
dbManager.deleteDb(packageInfo.packageName, true)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// v0 legacy tests below
|
||||
//
|
||||
|
||||
@Test
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 hasDataForPackage() delegates to plugin`() = runBlocking {
|
||||
val result = Random.nextBoolean()
|
||||
|
||||
coEvery { legacyPlugin.hasDataForPackage(token, packageInfo) } returns result
|
||||
|
||||
assertEquals(result, restore.hasDataForPackage(token, packageInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 listing records throws`() = runBlocking {
|
||||
restore.initializeState(0x00, token, name, packageInfo)
|
||||
|
||||
coEvery { legacyPlugin.listRecords(token, packageInfo) } throws IOException()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reading VersionHeader with unsupported version throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
fun `v0 reading VersionHeader with unsupported version throws`() = runBlocking {
|
||||
restore.initializeState(0x00, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every {
|
||||
headerReader.readVersion(inputStream, VERSION)
|
||||
headerReader.readVersion(inputStream, 0x00)
|
||||
} throws UnsupportedVersionException(unsupportedVersion)
|
||||
streamsGetClosed()
|
||||
|
||||
|
@ -84,12 +204,14 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `error reading VersionHeader throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
fun `v0 error reading VersionHeader throws`() = runBlocking {
|
||||
restore.initializeState(0x00, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } throws IOException()
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0x00) } throws IOException()
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
|
@ -97,13 +219,18 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `decrypting stream throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
@Suppress("deprecation")
|
||||
fun `v0 decrypting stream throws`() = runBlocking {
|
||||
restore.initializeState(0x00, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } throws IOException()
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0x00) } returns 0x00
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key)
|
||||
} throws IOException()
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
|
@ -111,13 +238,19 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `decrypting stream throws security exception`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
@Suppress("deprecation")
|
||||
fun `v0 decrypting stream throws security exception`() = runBlocking {
|
||||
restore.initializeState(0x00, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } throws SecurityException()
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0x00) } returns 0x00
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key)
|
||||
} returns VersionHeader(0x00, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } throws IOException()
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
|
@ -125,14 +258,19 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `writing header throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 writing header throws`() = runBlocking {
|
||||
restore.initializeState(0, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
||||
every { decryptedInputStream.readBytes() } returns data
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key)
|
||||
} returns VersionHeader(0x00, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } returns data
|
||||
every { output.writeEntityHeader(key, data.size) } throws IOException()
|
||||
streamsGetClosed()
|
||||
|
||||
|
@ -141,14 +279,19 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `writing value throws`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
@Suppress("deprecation")
|
||||
fun `v0 writing value throws`() = runBlocking {
|
||||
restore.initializeState(0, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
||||
every { decryptedInputStream.readBytes() } returns data
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key)
|
||||
} returns VersionHeader(0, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } returns data
|
||||
every { output.writeEntityHeader(key, data.size) } returns 42
|
||||
every { output.writeEntityData(data, data.size) } throws IOException()
|
||||
streamsGetClosed()
|
||||
|
@ -158,14 +301,19 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `writing value succeeds`() = runBlocking {
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
@Suppress("deprecation")
|
||||
fun `v0 writing value succeeds`() = runBlocking {
|
||||
restore.initializeState(0, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
||||
every { decryptedInputStream.readBytes() } returns data
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key)
|
||||
} returns VersionHeader(0, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } returns data
|
||||
every { output.writeEntityHeader(key, data.size) } returns 42
|
||||
every { output.writeEntityData(data, data.size) } returns data.size
|
||||
streamsGetClosed()
|
||||
|
@ -175,14 +323,17 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `writing value uses old v0 code`() = runBlocking {
|
||||
restore.initializeState(0.toByte(), token, packageInfo)
|
||||
@Suppress("deprecation")
|
||||
fun `v0 writing value uses old v0 code`() = runBlocking {
|
||||
restore.initializeState(0, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0.toByte()) } returns 0.toByte()
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0.toByte(), packageInfo.packageName, key)
|
||||
crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key)
|
||||
} returns VersionHeader(VERSION, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } returns data
|
||||
every { output.writeEntityHeader(key, data.size) } returns 42
|
||||
|
@ -194,43 +345,35 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `unexpected version aborts with error`() = runBlocking {
|
||||
restore.initializeState(Byte.MAX_VALUE, token, packageInfo)
|
||||
|
||||
getRecordsAndOutput()
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every {
|
||||
headerReader.readVersion(inputStream, Byte.MAX_VALUE)
|
||||
} throws GeneralSecurityException()
|
||||
streamsGetClosed()
|
||||
|
||||
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
|
||||
verifyStreamWasClosed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `writing two values succeeds`() = runBlocking {
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 writing two values succeeds`() = runBlocking {
|
||||
val data2 = getRandomByteArray()
|
||||
val inputStream2 = mockk<InputStream>()
|
||||
val decryptedInputStream2 = mockk<InputStream>()
|
||||
restore.initializeState(VERSION, token, packageInfo)
|
||||
restore.initializeState(0, token, name, packageInfo)
|
||||
|
||||
getRecordsAndOutput(listOf(key64, key264))
|
||||
// first key/value
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
|
||||
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
|
||||
every { decryptedInputStream.readBytes() } returns data
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key64)
|
||||
} returns inputStream
|
||||
every { headerReader.readVersion(inputStream, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key)
|
||||
} returns VersionHeader(0, packageInfo.packageName, key)
|
||||
every { crypto.decryptMultipleSegments(inputStream) } returns data
|
||||
every { output.writeEntityHeader(key, data.size) } returns 42
|
||||
every { output.writeEntityData(data, data.size) } returns data.size
|
||||
// second key/value
|
||||
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key264) } returns inputStream2
|
||||
every { headerReader.readVersion(inputStream2, VERSION) } returns VERSION
|
||||
every { crypto.newDecryptingStream(inputStream2, ad) } returns decryptedInputStream2
|
||||
every { decryptedInputStream2.readBytes() } returns data2
|
||||
coEvery {
|
||||
legacyPlugin.getInputStreamForRecord(token, packageInfo, key264)
|
||||
} returns inputStream2
|
||||
every { headerReader.readVersion(inputStream2, 0) } returns 0
|
||||
every {
|
||||
crypto.decryptHeader(inputStream2, 0, packageInfo.packageName, key2)
|
||||
} returns VersionHeader(0, packageInfo.packageName, key2)
|
||||
every { crypto.decryptMultipleSegments(inputStream2) } returns data2
|
||||
every { output.writeEntityHeader(key2, data2.size) } returns 42
|
||||
every { output.writeEntityData(data2, data2.size) } returns data2.size
|
||||
every { decryptedInputStream2.close() } just Runs
|
||||
every { inputStream2.close() } just Runs
|
||||
streamsGetClosed()
|
||||
|
||||
|
@ -238,12 +381,11 @@ internal class KVRestoreTest : RestoreTest() {
|
|||
}
|
||||
|
||||
private fun getRecordsAndOutput(recordKeys: List<String> = listOf(key64)) {
|
||||
coEvery { plugin.listRecords(token, packageInfo) } returns recordKeys
|
||||
coEvery { legacyPlugin.listRecords(token, packageInfo) } returns recordKeys
|
||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns output
|
||||
}
|
||||
|
||||
private fun streamsGetClosed() {
|
||||
every { decryptedInputStream.close() } just Runs
|
||||
every { inputStream.close() } just Runs
|
||||
every { fileDescriptor.close() } just Runs
|
||||
}
|
||||
|
|
|
@ -223,19 +223,20 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns true
|
||||
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
|
||||
every { kv.initializeState(VERSION, token, name, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { kv.initializeState(0x00, token, packageInfo) } just Runs
|
||||
every { kv.initializeState(0x00, token, "", packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
@ -292,7 +293,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns true
|
||||
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
|
||||
every { kv.initializeState(VERSION, token, name, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
@ -315,7 +316,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { kv.initializeState(0.toByte(), token, packageInfo) } just Runs
|
||||
every { kv.initializeState(0.toByte(), token, "", packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
@ -331,6 +332,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Suppress("Deprecation")
|
||||
fun `v0 when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
|||
import com.stevesoltys.seedvault.toByteArrayFromHex
|
||||
import com.stevesoltys.seedvault.transport.TransportTest
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
||||
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
|
@ -44,12 +45,20 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
|||
private val cipherFactory = CipherFactoryImpl(keyManager)
|
||||
private val headerReader = HeaderReaderImpl()
|
||||
private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader)
|
||||
private val dbManager = mockk<KvDbManager>()
|
||||
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
||||
private val notificationManager = mockk<BackupNotificationManager>()
|
||||
|
||||
private val backupPlugin = mockk<BackupPlugin>()
|
||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
||||
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
private val kvRestore = KVRestore(
|
||||
backupPlugin,
|
||||
kvRestorePlugin,
|
||||
outputFactory,
|
||||
headerReader,
|
||||
cryptoImpl,
|
||||
dbManager
|
||||
)
|
||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
||||
private val fullRestore =
|
||||
FullRestore(backupPlugin, fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
|
|
Loading…
Reference in a new issue