Make RestoreCoordinator use the new storage API with salt and backup type

This breaks restores until all the other required changed have been implemented.
This commit is contained in:
Torsten Grote 2021-09-16 18:14:55 +02:00 committed by Chirayu Desai
parent 75cf014e5d
commit 4bdaaa0ce9
6 changed files with 159 additions and 25 deletions

View file

@ -13,8 +13,10 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.header.UnsupportedVersionException
import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.MetadataReader
@ -40,6 +42,7 @@ private val TAG = RestoreCoordinator::class.java.simpleName
@Suppress("BlockingMethodInNonBlockingContext")
internal class RestoreCoordinator(
private val context: Context,
private val crypto: Crypto,
private val settingsManager: SettingsManager,
private val metadataManager: MetadataManager,
private val notificationManager: BackupNotificationManager,
@ -193,22 +196,59 @@ internal class RestoreCoordinator(
val state = this.state ?: throw IllegalStateException("no state")
if (!state.packages.hasNext()) return NO_MORE_PACKAGES
val version = state.backupMetadata.version
val packageInfo = state.packages.next()
val packageName = packageInfo.packageName
val version = state.backupMetadata.version
if (version == 0.toByte()) return nextRestorePackageV0(state, packageInfo)
val packageName = packageInfo.packageName
val type = try {
when (state.backupMetadata.packageMetadataMap[packageName]?.backupType) {
BackupType.KV -> {
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)
state.currentPackage = packageName
TYPE_KEY_VALUE
} else throw IOException("No data found for $packageName. Skipping.")
}
BackupType.FULL -> {
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
if (plugin.hasData(state.token, name)) {
Log.i(TAG, "Found full backup data for $packageName.")
full.initializeState(version, state.token, packageInfo)
state.currentPackage = packageName
TYPE_FULL_STREAM
} else throw IOException("No data found for $packageName. Skipping.")
}
null -> throw IOException("No backup type found for $packageName. Skipping.")
}
} catch (e: IOException) {
Log.e(TAG, "Error finding restore data for $packageName.", e)
failedPackages.add(packageName)
// don't return null and cause abort here, but try next package
return nextRestorePackage()
}
return RestoreDescription(packageName, type)
}
private suspend fun nextRestorePackageV0(
state: RestoreCoordinatorState,
packageInfo: PackageInfo
): RestoreDescription? {
val packageName = packageInfo.packageName
val type = try {
when {
// 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(version, state.token, packageInfo, state.pmPackageInfo)
kv.initializeState(0x00, state.token, packageInfo, state.pmPackageInfo)
state.currentPackage = packageName
TYPE_KEY_VALUE
}
full.hasDataForPackage(state.token, packageInfo) -> {
Log.i(TAG, "Found full backup data for $packageName.")
full.initializeState(version, state.token, packageInfo)
full.initializeState(0x00, state.token, packageInfo)
state.currentPackage = packageName
TYPE_FULL_STREAM
}

View file

@ -7,5 +7,7 @@ val restoreModule = module {
single { OutputFactory() }
single { KVRestore(get<RestorePlugin>().kvRestorePlugin, get(), get(), get()) }
single { FullRestore(get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
single { RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
single {
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
}
}

View file

@ -100,6 +100,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
private val restore = RestoreCoordinator(
context,
crypto,
settingsManager,
metadataManager,
notificationManager,
@ -186,7 +187,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
// find data for K/V backup
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
coEvery { backupPlugin.hasData(token, name) } returns true
val restoreDescription = restore.nextRestorePackage() ?: fail()
assertEquals(packageInfo.packageName, restoreDescription.packageName)
@ -262,7 +264,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
// find data for K/V backup
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
coEvery { backupPlugin.hasData(token, name) } returns true
val restoreDescription = restore.nextRestorePackage() ?: fail()
assertEquals(packageInfo.packageName, restoreDescription.packageName)
@ -288,6 +291,11 @@ internal class CoordinatorIntegrationTest : TransportTest() {
@Test
fun `test full backup and restore with two chunks`() = runBlocking {
// package is of type FULL
val packageMetadata = metadata.packageMetadataMap[packageInfo.packageName]!!
metadata.packageMetadataMap[packageInfo.packageName] =
packageMetadata.copy(backupType = BackupType.FULL)
// return streams from plugin and app data
val bOutputStream = ByteArrayOutputStream()
val bInputStream = ByteArrayInputStream(appData)
@ -327,9 +335,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
restore.beforeStartRestore(metadata)
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
// find data only for full backup
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns false
coEvery { fullRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
// finds data for full backup
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
coEvery { backupPlugin.hasData(token, name) } returns true
val restoreDescription = restore.nextRestorePackage() ?: fail()
assertEquals(packageInfo.packageName, restoreDescription.packageName)

View file

@ -13,8 +13,11 @@ import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.getRandomBase64
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.settings.SettingsManager
import io.mockk.every
import io.mockk.mockk
@ -50,8 +53,13 @@ internal abstract class TransportTest {
salt = getRandomBase64(METADATA_SALT_SIZE),
androidVersion = Random.nextInt(),
androidIncremental = getRandomString(),
deviceName = getRandomString()
deviceName = getRandomString(),
packageMetadataMap = PackageMetadataMap().apply {
put(packageInfo.packageName, PackageMetadata(backupType = BackupType.KV))
}
)
protected val name = getRandomString(12)
protected val name2 = getRandomString(23)
init {
mockkStatic(Log::class)

View file

@ -11,6 +11,7 @@ import android.os.ParcelFileDescriptor
import com.stevesoltys.seedvault.coAssertThrows
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataReader
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.settings.Storage
@ -45,6 +46,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val restore = RestoreCoordinator(
context,
crypto,
settingsManager,
metadataManager,
notificationManager,
@ -66,6 +68,11 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val packageName = packageInfo.packageName
private val storageName = getRandomString()
init {
metadata.packageMetadataMap[packageInfo2.packageName] =
PackageMetadata(backupType = BackupType.FULL)
}
@Test
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
val encryptedMetadata = EncryptedMetadata(token) { inputStream }
@ -210,37 +217,69 @@ internal class RestoreCoordinatorTest : TransportTest() {
}
@Test
fun `nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
fun `nextRestorePackage() returns KV description`() = runBlocking {
restore.beforeStartRestore(metadata)
restore.startRestore(token, packageInfoArray)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
coEvery { plugin.hasData(token, name) } returns true
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
val expected = RestoreDescription(packageName, TYPE_KEY_VALUE)
assertEquals(expected, restore.nextRestorePackage())
}
@Test
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
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
assertEquals(expected, restore.nextRestorePackage())
}
@Test
fun `nextRestorePackage() returns full description if no KV data found`() = runBlocking {
restore.beforeStartRestore(metadata)
fun `v0 nextRestorePackage() returns full description if no KV data found`() = runBlocking {
restore.beforeStartRestore(metadata.copy(version = 0x00))
restore.startRestore(token, packageInfoArray)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
coEvery { full.hasDataForPackage(token, packageInfo) } returns true
every { full.initializeState(VERSION, token, packageInfo) } just Runs
every { full.initializeState(0x00, token, packageInfo) } just Runs
val expected = RestoreDescription(packageInfo.packageName, TYPE_FULL_STREAM)
assertEquals(expected, restore.nextRestorePackage())
}
@Test
fun `nextRestorePackage() returns NO_MORE_PACKAGES if data found`() = runBlocking {
fun `nextRestorePackage() returns NO_MORE_PACKAGES if data not found`() = runBlocking {
restore.beforeStartRestore(metadata)
restore.startRestore(token, packageInfoArray)
restore.startRestore(token, packageInfoArray2)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
coEvery { full.hasDataForPackage(token, packageInfo) } returns false
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
coEvery { plugin.hasData(token, name) } returns false
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
coEvery { plugin.hasData(token, name2) } returns false
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
}
@Test
fun `nextRestorePackage() tries next package if one has no backup type()`() = runBlocking {
metadata.packageMetadataMap[packageName] =
metadata.packageMetadataMap[packageName]!!.copy(backupType = null)
restore.beforeStartRestore(metadata)
restore.startRestore(token, packageInfoArray2)
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
coEvery { plugin.hasData(token, name2) } returns true
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
val expected = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
assertEquals(expected, restore.nextRestorePackage())
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
}
@ -250,15 +289,38 @@ internal class RestoreCoordinatorTest : TransportTest() {
restore.beforeStartRestore(metadata)
restore.startRestore(token, packageInfoArray2)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
coEvery { plugin.hasData(token, name) } returns true
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
assertEquals(expected, restore.nextRestorePackage())
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
coEvery { plugin.hasData(token, name2) } returns true
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
val expected2 =
RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
assertEquals(expected2, restore.nextRestorePackage())
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
}
@Test
fun `v0 nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
restore.beforeStartRestore(metadata.copy(version = 0x00))
restore.startRestore(token, packageInfoArray2)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
every { kv.initializeState(0.toByte(), token, packageInfo) } just Runs
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
assertEquals(expected, restore.nextRestorePackage())
coEvery { kv.hasDataForPackage(token, packageInfo2) } returns false
coEvery { full.hasDataForPackage(token, packageInfo2) } returns true
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
every { full.initializeState(0.toByte(), token, packageInfo2) } just Runs
val expected2 = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
assertEquals(expected2, restore.nextRestorePackage())
@ -267,8 +329,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
}
@Test
fun `when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
restore.beforeStartRestore(metadata)
fun `v0 when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
restore.beforeStartRestore(metadata.copy(version = 0x00))
restore.startRestore(token, packageInfoArray)
coEvery { kv.hasDataForPackage(token, packageInfo) } throws IOException()
@ -277,8 +339,21 @@ internal class RestoreCoordinatorTest : TransportTest() {
}
@Test
fun `when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
fun `when plugin#hasData() throws, it tries next package`() = runBlocking {
restore.beforeStartRestore(metadata)
restore.startRestore(token, packageInfoArray2)
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
coEvery { plugin.hasData(token, name) } returns false
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
coEvery { plugin.hasData(token, name2) } throws IOException()
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
}
@Test
fun `v0 when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
restore.beforeStartRestore(metadata.copy(version = 0x00))
restore.startRestore(token, packageInfoArray)
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false

View file

@ -55,6 +55,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
private val restore = RestoreCoordinator(
context,
crypto,
settingsManager,
metadataManager,
notificationManager,