Add salt and backup type to metadata
This commit is contained in:
parent
39cb0c6443
commit
793663acb5
15 changed files with 123 additions and 41 deletions
|
@ -13,6 +13,7 @@ typealias PackageMetadataMap = HashMap<String, PackageMetadata>
|
|||
data class BackupMetadata(
|
||||
internal val version: Byte = VERSION,
|
||||
internal val token: Long,
|
||||
internal val salt: String,
|
||||
internal var time: Long = 0L,
|
||||
internal val androidVersion: Int = Build.VERSION.SDK_INT,
|
||||
internal val androidIncremental: String = Build.VERSION.INCREMENTAL,
|
||||
|
@ -23,6 +24,7 @@ data class BackupMetadata(
|
|||
internal const val JSON_METADATA = "@meta@"
|
||||
internal const val JSON_METADATA_VERSION = "version"
|
||||
internal const val JSON_METADATA_TOKEN = "token"
|
||||
internal const val JSON_METADATA_SALT = "salt"
|
||||
internal const val JSON_METADATA_TIME = "time"
|
||||
internal const val JSON_METADATA_SDK_INT = "sdk_int"
|
||||
internal const val JSON_METADATA_INCREMENTAL = "incremental"
|
||||
|
@ -69,6 +71,7 @@ data class PackageMetadata(
|
|||
*/
|
||||
internal var time: Long = 0L,
|
||||
internal var state: PackageState = UNKNOWN_ERROR,
|
||||
internal var backupType: BackupType? = null,
|
||||
internal val system: Boolean = false,
|
||||
internal val version: Long? = null,
|
||||
internal val installer: String? = null,
|
||||
|
@ -87,7 +90,10 @@ data class ApkSplit(
|
|||
// There's also a revisionCode, but it doesn't seem to be used just yet
|
||||
)
|
||||
|
||||
enum class BackupType { KV, FULL }
|
||||
|
||||
internal const val JSON_PACKAGE_TIME = "time"
|
||||
internal const val JSON_PACKAGE_BACKUP_TYPE = "backupType"
|
||||
internal const val JSON_PACKAGE_STATE = "state"
|
||||
internal const val JSON_PACKAGE_SYSTEM = "system"
|
||||
internal const val JSON_PACKAGE_VERSION = "version"
|
||||
|
|
|
@ -10,6 +10,8 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import com.stevesoltys.seedvault.Clock
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.encodeBase64
|
||||
import com.stevesoltys.seedvault.header.VERSION
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||
|
@ -24,16 +26,18 @@ private val TAG = MetadataManager::class.java.simpleName
|
|||
|
||||
@VisibleForTesting
|
||||
internal const val METADATA_CACHE_FILE = "metadata.cache"
|
||||
internal const val METADATA_SALT_SIZE = 32
|
||||
|
||||
@WorkerThread
|
||||
class MetadataManager(
|
||||
internal class MetadataManager(
|
||||
private val context: Context,
|
||||
private val clock: Clock,
|
||||
private val crypto: Crypto,
|
||||
private val metadataWriter: MetadataWriter,
|
||||
private val metadataReader: MetadataReader
|
||||
) {
|
||||
|
||||
private val uninitializedMetadata = BackupMetadata(token = 0L)
|
||||
private val uninitializedMetadata = BackupMetadata(token = 0L, salt = "")
|
||||
private var metadata: BackupMetadata = uninitializedMetadata
|
||||
get() {
|
||||
if (field == uninitializedMetadata) {
|
||||
|
@ -57,8 +61,9 @@ class MetadataManager(
|
|||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun onDeviceInitialization(token: Long, metadataOutputStream: OutputStream) {
|
||||
val salt = crypto.getRandomBytes(METADATA_SALT_SIZE).encodeBase64()
|
||||
modifyMetadata(metadataOutputStream) {
|
||||
metadata = BackupMetadata(token = token)
|
||||
metadata = BackupMetadata(token = token, salt = salt)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +126,11 @@ class MetadataManager(
|
|||
*/
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun onPackageBackedUp(packageInfo: PackageInfo, metadataOutputStream: OutputStream) {
|
||||
fun onPackageBackedUp(
|
||||
packageInfo: PackageInfo,
|
||||
type: BackupType,
|
||||
metadataOutputStream: OutputStream
|
||||
) {
|
||||
val packageName = packageInfo.packageName
|
||||
modifyMetadata(metadataOutputStream) {
|
||||
val now = clock.time()
|
||||
|
@ -129,10 +138,12 @@ class MetadataManager(
|
|||
if (metadata.packageMetadataMap.containsKey(packageName)) {
|
||||
metadata.packageMetadataMap[packageName]!!.time = now
|
||||
metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA
|
||||
metadata.packageMetadataMap[packageName]!!.backupType = type
|
||||
} else {
|
||||
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
||||
time = now,
|
||||
state = APK_AND_DATA,
|
||||
backupType = type,
|
||||
system = packageInfo.isSystemApp()
|
||||
)
|
||||
}
|
||||
|
@ -150,7 +161,8 @@ class MetadataManager(
|
|||
internal fun onPackageBackupError(
|
||||
packageInfo: PackageInfo,
|
||||
packageState: PackageState,
|
||||
metadataOutputStream: OutputStream
|
||||
metadataOutputStream: OutputStream,
|
||||
backupType: BackupType? = null
|
||||
) {
|
||||
check(packageState != APK_AND_DATA) { "Backup Error with non-error package state." }
|
||||
val packageName = packageInfo.packageName
|
||||
|
@ -161,6 +173,7 @@ class MetadataManager(
|
|||
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
||||
time = 0L,
|
||||
state = packageState,
|
||||
backupType = backupType,
|
||||
system = packageInfo.isSystemApp()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.koin.android.ext.koin.androidContext
|
|||
import org.koin.dsl.module
|
||||
|
||||
val metadataModule = module {
|
||||
single { MetadataManager(androidContext(), get(), get(), get()) }
|
||||
single { MetadataManager(androidContext(), get(), get(), get(), get()) }
|
||||
single<MetadataWriter> { MetadataWriterImpl(get()) }
|
||||
single<MetadataReader> { MetadataReaderImpl(get()) }
|
||||
}
|
||||
|
|
|
@ -112,6 +112,13 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
|||
WAS_STOPPED.name -> WAS_STOPPED
|
||||
else -> UNKNOWN_ERROR
|
||||
}
|
||||
val pBackupType = when (p.optString(JSON_PACKAGE_BACKUP_TYPE)) {
|
||||
BackupType.KV.name -> BackupType.KV
|
||||
BackupType.FULL.name -> BackupType.FULL
|
||||
// we can't fail when format version is 0,
|
||||
// because when only backing up the APK for example, there's no type
|
||||
else -> null
|
||||
}
|
||||
val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false)
|
||||
val pVersion = p.optLong(JSON_PACKAGE_VERSION, 0L)
|
||||
val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
|
||||
|
@ -127,6 +134,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
|||
packageMetadataMap[packageName] = PackageMetadata(
|
||||
time = p.getLong(JSON_PACKAGE_TIME),
|
||||
state = pState,
|
||||
backupType = pBackupType,
|
||||
system = pSystem,
|
||||
version = if (pVersion == 0L) null else pVersion,
|
||||
installer = if (pInstaller == "") null else pInstaller,
|
||||
|
@ -138,6 +146,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
|||
return BackupMetadata(
|
||||
version = version,
|
||||
token = token,
|
||||
salt = if (version == 0.toByte()) "" else meta.getString(JSON_METADATA_SALT),
|
||||
time = meta.getLong(JSON_METADATA_TIME),
|
||||
androidVersion = meta.getInt(JSON_METADATA_SDK_INT),
|
||||
androidIncremental = meta.getString(JSON_METADATA_INCREMENTAL),
|
||||
|
|
|
@ -30,6 +30,7 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
|
|||
put(JSON_METADATA, JSONObject().apply {
|
||||
put(JSON_METADATA_VERSION, metadata.version.toInt())
|
||||
put(JSON_METADATA_TOKEN, metadata.token)
|
||||
put(JSON_METADATA_SALT, metadata.salt)
|
||||
put(JSON_METADATA_TIME, metadata.time)
|
||||
put(JSON_METADATA_SDK_INT, metadata.androidVersion)
|
||||
put(JSON_METADATA_INCREMENTAL, metadata.androidIncremental)
|
||||
|
@ -42,6 +43,11 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
|
|||
if (packageMetadata.state != APK_AND_DATA) {
|
||||
put(JSON_PACKAGE_STATE, packageMetadata.state.name)
|
||||
}
|
||||
// We can't require a backup type in metadata at this point,
|
||||
// only when version > 0 and we have actual restore data
|
||||
if (packageMetadata.backupType != null) {
|
||||
put(JSON_PACKAGE_BACKUP_TYPE, packageMetadata.backupType!!.name)
|
||||
}
|
||||
if (packageMetadata.system) {
|
||||
put(JSON_PACKAGE_SYSTEM, packageMetadata.system)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.security.MessageDigest
|
|||
private val TAG = ApkBackup::class.java.simpleName
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
class ApkBackup(
|
||||
internal class ApkBackup(
|
||||
private val pm: PackageManager,
|
||||
private val settingsManager: SettingsManager,
|
||||
private val metadataManager: MetadataManager
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.annotation.VisibleForTesting.PRIVATE
|
|||
import androidx.annotation.WorkerThread
|
||||
import com.stevesoltys.seedvault.Clock
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
import com.stevesoltys.seedvault.metadata.PackageState
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||
|
@ -334,7 +335,7 @@ internal class BackupCoordinator(
|
|||
TAG, "Cancel full backup of ${packageInfo.packageName}" +
|
||||
" because of ${state.cancelReason}"
|
||||
)
|
||||
onPackageBackupError(packageInfo)
|
||||
onPackageBackupError(packageInfo, BackupType.FULL)
|
||||
full.cancelFullBackup()
|
||||
}
|
||||
|
||||
|
@ -381,14 +382,16 @@ internal class BackupCoordinator(
|
|||
check(!full.hasState()) {
|
||||
"K/V backup has state, but full backup has dangling state as well"
|
||||
}
|
||||
onPackageBackedUp(kv.getCurrentPackage()!!) // not-null because we have state
|
||||
// getCurrentPackage() not-null because we have state
|
||||
onPackageBackedUp(kv.getCurrentPackage()!!, BackupType.KV)
|
||||
kv.finishBackup()
|
||||
}
|
||||
full.hasState() -> {
|
||||
check(!kv.hasState()) {
|
||||
"Full backup has state, but K/V backup has dangling state as well"
|
||||
}
|
||||
onPackageBackedUp(full.getCurrentPackage()!!) // not-null because we have state
|
||||
// getCurrentPackage() not-null because we have state
|
||||
onPackageBackedUp(full.getCurrentPackage()!!, BackupType.FULL)
|
||||
full.finishBackup()
|
||||
}
|
||||
state.expectFinish -> {
|
||||
|
@ -456,10 +459,10 @@ internal class BackupCoordinator(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun onPackageBackedUp(packageInfo: PackageInfo) {
|
||||
private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType) {
|
||||
try {
|
||||
plugin.getMetadataOutputStream().use {
|
||||
metadataManager.onPackageBackedUp(packageInfo, it)
|
||||
metadataManager.onPackageBackedUp(packageInfo, type, it)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error while writing metadata for ${packageInfo.packageName}", e)
|
||||
|
@ -468,13 +471,13 @@ internal class BackupCoordinator(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun onPackageBackupError(packageInfo: PackageInfo) {
|
||||
private suspend fun onPackageBackupError(packageInfo: PackageInfo, type: BackupType) {
|
||||
// don't bother with system apps that have no data
|
||||
if (state.cancelReason == NO_DATA && packageInfo.isSystemApp()) return
|
||||
val packageName = packageInfo.packageName
|
||||
try {
|
||||
plugin.getMetadataOutputStream().use {
|
||||
metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it)
|
||||
metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it, type)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error while writing metadata for $packageName", e)
|
||||
|
|
|
@ -9,6 +9,8 @@ import android.content.pm.PackageInfo
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.stevesoltys.seedvault.Clock
|
||||
import com.stevesoltys.seedvault.TestApp
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.encodeBase64
|
||||
import com.stevesoltys.seedvault.getRandomByteArray
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||
|
@ -46,10 +48,11 @@ class MetadataManagerTest {
|
|||
|
||||
private val context: Context = mockk()
|
||||
private val clock: Clock = mockk()
|
||||
private val crypto: Crypto = mockk()
|
||||
private val metadataWriter: MetadataWriter = mockk()
|
||||
private val metadataReader: MetadataReader = mockk()
|
||||
|
||||
private val manager = MetadataManager(context, clock, metadataWriter, metadataReader)
|
||||
private val manager = MetadataManager(context, clock, crypto, metadataWriter, metadataReader)
|
||||
|
||||
private val time = 42L
|
||||
private val token = Random.nextLong()
|
||||
|
@ -58,7 +61,9 @@ class MetadataManagerTest {
|
|||
packageName = this@MetadataManagerTest.packageName
|
||||
applicationInfo = ApplicationInfo().apply { flags = FLAG_ALLOW_BACKUP }
|
||||
}
|
||||
private val initialMetadata = BackupMetadata(token = token)
|
||||
private val saltBytes = Random.nextBytes(METADATA_SALT_SIZE)
|
||||
private val salt = saltBytes.encodeBase64()
|
||||
private val initialMetadata = BackupMetadata(token = token, salt = salt)
|
||||
private val storageOutputStream = ByteArrayOutputStream()
|
||||
private val cacheOutputStream: FileOutputStream = mockk()
|
||||
private val cacheInputStream: FileInputStream = mockk()
|
||||
|
@ -72,6 +77,7 @@ class MetadataManagerTest {
|
|||
@Test
|
||||
fun `test onDeviceInitialization()`() {
|
||||
every { clock.time() } returns time
|
||||
every { crypto.getRandomBytes(METADATA_SALT_SIZE) } returns saltBytes
|
||||
expectReadFromCache()
|
||||
expectModifyMetadata(initialMetadata)
|
||||
|
||||
|
@ -233,10 +239,10 @@ class MetadataManagerTest {
|
|||
every { clock.time() } returns time
|
||||
expectModifyMetadata(initialMetadata)
|
||||
|
||||
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||
manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
|
||||
|
||||
assertEquals(
|
||||
packageMetadata.copy(state = APK_AND_DATA, system = true),
|
||||
packageMetadata.copy(state = APK_AND_DATA, backupType = BackupType.FULL, system = true),
|
||||
manager.getPackageMetadata(packageName)
|
||||
)
|
||||
assertEquals(time, manager.getLastBackupTime())
|
||||
|
@ -254,14 +260,15 @@ class MetadataManagerTest {
|
|||
time = updateTime,
|
||||
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
|
||||
)
|
||||
updatedMetadata.packageMetadataMap[packageName] = PackageMetadata(updateTime, APK_AND_DATA)
|
||||
updatedMetadata.packageMetadataMap[packageName] =
|
||||
PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV)
|
||||
|
||||
expectReadFromCache()
|
||||
every { clock.time() } returns updateTime
|
||||
every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException()
|
||||
|
||||
try {
|
||||
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||
manager.onPackageBackedUp(packageInfo, BackupType.KV, storageOutputStream)
|
||||
fail()
|
||||
} catch (e: IOException) {
|
||||
// expected
|
||||
|
@ -294,7 +301,7 @@ class MetadataManagerTest {
|
|||
every { clock.time() } returns time
|
||||
expectModifyMetadata(updatedMetadata)
|
||||
|
||||
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||
manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
|
||||
|
||||
assertEquals(time, manager.getLastBackupTime())
|
||||
assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
|
|||
import com.stevesoltys.seedvault.crypto.CryptoImpl
|
||||
import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES
|
||||
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||
import com.stevesoltys.seedvault.getRandomBase64
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||
import com.stevesoltys.seedvault.header.VERSION
|
||||
|
@ -33,8 +34,8 @@ internal class MetadataReadWriteTest {
|
|||
private val reader = MetadataReaderImpl(cryptoImpl)
|
||||
|
||||
private val packages = HashMap<String, PackageMetadata>().apply {
|
||||
put(getRandomString(), PackageMetadata(Random.nextLong(), APK_AND_DATA))
|
||||
put(getRandomString(), PackageMetadata(Random.nextLong(), WAS_STOPPED))
|
||||
put(getRandomString(), PackageMetadata(Random.nextLong(), APK_AND_DATA, BackupType.FULL))
|
||||
put(getRandomString(), PackageMetadata(Random.nextLong(), WAS_STOPPED, BackupType.KV))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -55,6 +56,7 @@ internal class MetadataReadWriteTest {
|
|||
return BackupMetadata(
|
||||
version = VERSION,
|
||||
token = Random.nextLong(),
|
||||
salt = getRandomBase64(32),
|
||||
time = Random.nextLong(),
|
||||
androidVersion = Random.nextInt(),
|
||||
androidIncremental = getRandomString(),
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.stevesoltys.seedvault.metadata
|
|||
|
||||
import com.stevesoltys.seedvault.Utf8
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.getRandomBase64
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
|
||||
|
@ -90,6 +91,7 @@ class MetadataReaderTest {
|
|||
"org.example", PackageMetadata(
|
||||
time = Random.nextLong(),
|
||||
state = QUOTA_EXCEEDED,
|
||||
backupType = BackupType.FULL,
|
||||
version = Random.nextLong(),
|
||||
installer = getRandomString(),
|
||||
sha256 = getRandomString(),
|
||||
|
@ -123,6 +125,7 @@ class MetadataReaderTest {
|
|||
json.put("org.example", JSONObject().apply {
|
||||
put(JSON_PACKAGE_TIME, Random.nextLong())
|
||||
put(JSON_PACKAGE_STATE, getRandomString())
|
||||
put(JSON_PACKAGE_BACKUP_TYPE, BackupType.FULL.name)
|
||||
put(JSON_PACKAGE_VERSION, Random.nextLong())
|
||||
put(JSON_PACKAGE_INSTALLER, getRandomString())
|
||||
put(JSON_PACKAGE_SHA256, getRandomString())
|
||||
|
@ -130,7 +133,9 @@ class MetadataReaderTest {
|
|||
})
|
||||
val jsonBytes = json.toString().toByteArray(Utf8)
|
||||
val metadata = decoder.decode(jsonBytes, metadata.version, metadata.token)
|
||||
assertEquals(this.metadata.salt, metadata.salt)
|
||||
assertEquals(UNKNOWN_ERROR, metadata.packageMetadataMap["org.example"]!!.state)
|
||||
assertEquals(BackupType.FULL, metadata.packageMetadataMap["org.example"]!!.backupType)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -142,6 +147,7 @@ class MetadataReaderTest {
|
|||
val jsonBytes = json.toString().toByteArray(Utf8)
|
||||
val metadata = decoder.decode(jsonBytes, metadata.version, metadata.token)
|
||||
assertFalse(metadata.packageMetadataMap["org.example"]!!.system)
|
||||
assertNull(metadata.packageMetadataMap["org.example"]!!.backupType)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,12 +155,14 @@ class MetadataReaderTest {
|
|||
val json = JSONObject(metadataByteArray.toString(Utf8))
|
||||
json.put("org.example", JSONObject().apply {
|
||||
put(JSON_PACKAGE_TIME, Random.nextLong())
|
||||
put(JSON_PACKAGE_BACKUP_TYPE, BackupType.KV.name)
|
||||
})
|
||||
val jsonBytes = json.toString().toByteArray(Utf8)
|
||||
val result = decoder.decode(jsonBytes, metadata.version, metadata.token)
|
||||
|
||||
assertEquals(1, result.packageMetadataMap.size)
|
||||
val packageMetadata = result.packageMetadataMap.getOrElse("org.example") { fail() }
|
||||
assertEquals(BackupType.KV, packageMetadata.backupType)
|
||||
assertNull(packageMetadata.version)
|
||||
assertNull(packageMetadata.installer)
|
||||
assertNull(packageMetadata.signatures)
|
||||
|
@ -166,6 +174,7 @@ class MetadataReaderTest {
|
|||
return BackupMetadata(
|
||||
version = 1.toByte(),
|
||||
token = Random.nextLong(),
|
||||
salt = getRandomBase64(METADATA_SALT_SIZE),
|
||||
time = Random.nextLong(),
|
||||
androidVersion = Random.nextInt(),
|
||||
androidIncremental = getRandomString(),
|
||||
|
|
|
@ -54,6 +54,7 @@ internal class MetadataV0ReadTest {
|
|||
) = BackupMetadata(
|
||||
version = 0x00,
|
||||
token = 1337L,
|
||||
salt = "",
|
||||
time = 2342L,
|
||||
androidVersion = 30,
|
||||
androidIncremental = "sdfqefpojlfj",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.stevesoltys.seedvault.metadata
|
||||
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.getRandomBase64
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||
|
@ -35,8 +36,8 @@ internal class MetadataWriterDecoderTest {
|
|||
fun `encoded metadata matches decoded metadata (with package, no apk info)`() {
|
||||
val time = Random.nextLong()
|
||||
val packages = HashMap<String, PackageMetadata>().apply {
|
||||
put(getRandomString(), PackageMetadata(time, APK_AND_DATA))
|
||||
put(getRandomString(), PackageMetadata(time, WAS_STOPPED))
|
||||
put(getRandomString(), PackageMetadata(time, APK_AND_DATA, BackupType.FULL))
|
||||
put(getRandomString(), PackageMetadata(time, WAS_STOPPED, BackupType.KV))
|
||||
}
|
||||
val metadata = getMetadata(packages)
|
||||
assertEquals(
|
||||
|
@ -52,6 +53,7 @@ internal class MetadataWriterDecoderTest {
|
|||
getRandomString(), PackageMetadata(
|
||||
time = Random.nextLong(),
|
||||
state = APK_AND_DATA,
|
||||
backupType = BackupType.FULL,
|
||||
version = Random.nextLong(),
|
||||
installer = getRandomString(),
|
||||
splits = listOf(
|
||||
|
@ -78,6 +80,7 @@ internal class MetadataWriterDecoderTest {
|
|||
getRandomString(), PackageMetadata(
|
||||
time = Random.nextLong(),
|
||||
state = QUOTA_EXCEEDED,
|
||||
backupType = BackupType.FULL,
|
||||
system = Random.nextBoolean(),
|
||||
version = Random.nextLong(),
|
||||
installer = getRandomString(),
|
||||
|
@ -89,6 +92,7 @@ internal class MetadataWriterDecoderTest {
|
|||
getRandomString(), PackageMetadata(
|
||||
time = Random.nextLong(),
|
||||
state = NO_DATA,
|
||||
backupType = BackupType.KV,
|
||||
system = Random.nextBoolean(),
|
||||
version = Random.nextLong(),
|
||||
installer = getRandomString(),
|
||||
|
@ -121,6 +125,7 @@ internal class MetadataWriterDecoderTest {
|
|||
return BackupMetadata(
|
||||
version = Random.nextBytes(1)[0],
|
||||
token = Random.nextLong(),
|
||||
salt = getRandomBase64(32),
|
||||
time = Random.nextLong(),
|
||||
androidVersion = Random.nextInt(),
|
||||
androidIncremental = getRandomString(),
|
||||
|
|
|
@ -13,6 +13,7 @@ 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
|
||||
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
|
||||
|
@ -33,7 +34,6 @@ import com.stevesoltys.seedvault.transport.restore.KVRestore
|
|||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
||||
import com.stevesoltys.seedvault.transport.restore.OutputFactory
|
||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import io.mockk.CapturingSlot
|
||||
import io.mockk.Runs
|
||||
|
@ -93,7 +93,6 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
notificationManager
|
||||
)
|
||||
|
||||
private val restorePlugin = mockk<RestorePlugin>()
|
||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
||||
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
||||
|
@ -172,13 +171,11 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
|
||||
} returns metadataOutputStream
|
||||
every {
|
||||
metadataManager.onApkBackedUp(
|
||||
packageInfo,
|
||||
packageMetadata,
|
||||
metadataOutputStream
|
||||
)
|
||||
metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream)
|
||||
} just Runs
|
||||
every {
|
||||
metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
|
||||
} just Runs
|
||||
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||
|
||||
// start and finish K/V backup
|
||||
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
||||
|
@ -252,7 +249,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
coEvery {
|
||||
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
|
||||
} returns metadataOutputStream
|
||||
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||
every {
|
||||
metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
|
||||
} just Runs
|
||||
|
||||
// start and finish K/V backup
|
||||
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
||||
|
@ -314,7 +313,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
metadataOutputStream
|
||||
)
|
||||
} just Runs
|
||||
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||
every {
|
||||
metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream)
|
||||
} just Runs
|
||||
|
||||
// perform backup to output stream
|
||||
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
|
||||
|
|
|
@ -10,8 +10,10 @@ import android.util.Log
|
|||
import com.stevesoltys.seedvault.Clock
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
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.METADATA_SALT_SIZE
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import io.mockk.every
|
||||
|
@ -45,6 +47,7 @@ internal abstract class TransportTest {
|
|||
}
|
||||
protected val metadata = BackupMetadata(
|
||||
token = token,
|
||||
salt = getRandomBase64(METADATA_SALT_SIZE),
|
||||
androidVersion = Random.nextInt(),
|
||||
androidIncremental = getRandomString(),
|
||||
deviceName = getRandomString()
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.os.ParcelFileDescriptor
|
|||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.coAssertThrows
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
|
||||
|
@ -254,7 +255,9 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
every { kv.getCurrentPackage() } returns packageInfo
|
||||
every { settingsManager.getToken() } returns token
|
||||
coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream
|
||||
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||
every {
|
||||
metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
|
||||
} just Runs
|
||||
every { kv.finishBackup() } returns result
|
||||
every { metadataOutputStream.close() } just Runs
|
||||
|
||||
|
@ -272,7 +275,9 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
every { full.getCurrentPackage() } returns packageInfo
|
||||
every { settingsManager.getToken() } returns token
|
||||
coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream
|
||||
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||
every {
|
||||
metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream)
|
||||
} just Runs
|
||||
every { full.finishBackup() } returns result
|
||||
every { metadataOutputStream.close() } just Runs
|
||||
|
||||
|
@ -302,7 +307,8 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
metadataManager.onPackageBackupError(
|
||||
packageInfo,
|
||||
QUOTA_EXCEEDED,
|
||||
metadataOutputStream
|
||||
metadataOutputStream,
|
||||
BackupType.FULL
|
||||
)
|
||||
} just Runs
|
||||
coEvery { full.cancelFullBackup() } just Runs
|
||||
|
@ -325,7 +331,12 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
assertEquals(0L, backup.requestFullBackupTime())
|
||||
|
||||
verify(exactly = 1) {
|
||||
metadataManager.onPackageBackupError(packageInfo, QUOTA_EXCEEDED, metadataOutputStream)
|
||||
metadataManager.onPackageBackupError(
|
||||
packageInfo,
|
||||
QUOTA_EXCEEDED,
|
||||
metadataOutputStream,
|
||||
BackupType.FULL
|
||||
)
|
||||
}
|
||||
verify { metadataOutputStream.close() }
|
||||
}
|
||||
|
@ -341,7 +352,8 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
metadataManager.onPackageBackupError(
|
||||
packageInfo,
|
||||
NO_DATA,
|
||||
metadataOutputStream
|
||||
metadataOutputStream,
|
||||
BackupType.FULL
|
||||
)
|
||||
} just Runs
|
||||
coEvery { full.cancelFullBackup() } just Runs
|
||||
|
@ -361,7 +373,12 @@ internal class BackupCoordinatorTest : BackupTest() {
|
|||
assertEquals(0L, backup.requestFullBackupTime())
|
||||
|
||||
verify(exactly = 1) {
|
||||
metadataManager.onPackageBackupError(packageInfo, NO_DATA, metadataOutputStream)
|
||||
metadataManager.onPackageBackupError(
|
||||
packageInfo,
|
||||
NO_DATA,
|
||||
metadataOutputStream,
|
||||
BackupType.FULL
|
||||
)
|
||||
}
|
||||
verify { metadataOutputStream.close() }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue