Add salt and backup type to metadata
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
index 03f1568..3dacf69 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
@@ -13,6 +13,7 @@
 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 @@
 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 @@
      */
     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 @@
     // 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"
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
index a006a30..648816b 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -10,6 +10,8 @@
 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 @@
 
 @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 @@
     @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 @@
      */
     @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 @@
             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 @@
     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 @@
                 metadata.packageMetadataMap[packageName] = PackageMetadata(
                     time = 0L,
                     state = packageState,
+                    backupType = backupType,
                     system = packageInfo.isSystemApp()
                 )
             }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
index 1ba64b3..68c723a 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
@@ -4,7 +4,7 @@
 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()) }
 }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
index d1e30ac..99598fb 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
@@ -112,6 +112,13 @@
                     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 @@
                 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 @@
             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),
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
index 9ace924..1359c11 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
@@ -30,6 +30,7 @@
             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 @@
                 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)
                 }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
index adf2eb6..b38fbc7 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
@@ -25,7 +25,7 @@
 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
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
index 3b934af..e0b0a93 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
@@ -21,6 +21,7 @@
 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 @@
             TAG, "Cancel full backup of ${packageInfo.packageName}" +
                 " because of ${state.cancelReason}"
         )
-        onPackageBackupError(packageInfo)
+        onPackageBackupError(packageInfo, BackupType.FULL)
         full.cancelFullBackup()
     }
 
@@ -381,14 +382,16 @@
             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 @@
         }
     }
 
-    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 @@
         }
     }
 
-    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)
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
index 0aae411..0e120ff 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
@@ -9,6 +9,8 @@
 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 @@
 
     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 @@
         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 @@
     @Test
     fun `test onDeviceInitialization()`() {
         every { clock.time() } returns time
+        every { crypto.getRandomBytes(METADATA_SALT_SIZE) } returns saltBytes
         expectReadFromCache()
         expectModifyMetadata(initialMetadata)
 
@@ -233,10 +239,10 @@
         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 @@
             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 @@
         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))
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
index db9881b..f0afbb8 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt
@@ -4,6 +4,7 @@
 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 @@
     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 @@
         return BackupMetadata(
             version = VERSION,
             token = Random.nextLong(),
+            salt = getRandomBase64(32),
             time = Random.nextLong(),
             androidVersion = Random.nextInt(),
             androidIncremental = getRandomString(),
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt
index a49e9f0..8b9216a 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt
@@ -2,6 +2,7 @@
 
 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 @@
                 "org.example", PackageMetadata(
                     time = Random.nextLong(),
                     state = QUOTA_EXCEEDED,
+                    backupType = BackupType.FULL,
                     version = Random.nextLong(),
                     installer = getRandomString(),
                     sha256 = getRandomString(),
@@ -123,6 +125,7 @@
         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 @@
         })
         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 @@
         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 @@
         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 @@
         return BackupMetadata(
             version = 1.toByte(),
             token = Random.nextLong(),
+            salt = getRandomBase64(METADATA_SALT_SIZE),
             time = Random.nextLong(),
             androidVersion = Random.nextInt(),
             androidIncremental = getRandomString(),
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
index 0cf33d5..d5796ae 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt
@@ -54,6 +54,7 @@
     ) = BackupMetadata(
         version = 0x00,
         token = 1337L,
+        salt = "",
         time = 2342L,
         androidVersion = 30,
         androidIncremental = "sdfqefpojlfj",
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
index a85bbe6..aa1c23f 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
@@ -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 @@
     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 @@
                 getRandomString(), PackageMetadata(
                     time = Random.nextLong(),
                     state = APK_AND_DATA,
+                    backupType = BackupType.FULL,
                     version = Random.nextLong(),
                     installer = getRandomString(),
                     splits = listOf(
@@ -78,6 +80,7 @@
                 getRandomString(), PackageMetadata(
                     time = Random.nextLong(),
                     state = QUOTA_EXCEEDED,
+                    backupType = BackupType.FULL,
                     system = Random.nextBoolean(),
                     version = Random.nextLong(),
                     installer = getRandomString(),
@@ -89,6 +92,7 @@
                 getRandomString(), PackageMetadata(
                     time = Random.nextLong(),
                     state = NO_DATA,
+                    backupType = BackupType.KV,
                     system = Random.nextBoolean(),
                     version = Random.nextLong(),
                     installer = getRandomString(),
@@ -121,6 +125,7 @@
         return BackupMetadata(
             version = Random.nextBytes(1)[0],
             token = Random.nextLong(),
+            salt = getRandomBase64(32),
             time = Random.nextLong(),
             androidVersion = Random.nextInt(),
             androidIncremental = getRandomString(),
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
index 234e8d9..52f20e4 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
@@ -13,6 +13,7 @@
 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.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 @@
         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 @@
             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, 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))
@@ -252,7 +249,9 @@
         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 @@
                 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))
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt
index 6691804..74795d5 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt
@@ -10,8 +10,10 @@
 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 @@
     }
     protected val metadata = BackupMetadata(
         token = token,
+        salt = getRandomBase64(METADATA_SALT_SIZE),
         androidVersion = Random.nextInt(),
         androidIncremental = getRandomString(),
         deviceName = getRandomString()
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
index bac93f4..089c573 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
@@ -15,6 +15,7 @@
 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 @@
         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 @@
         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 @@
             metadataManager.onPackageBackupError(
                 packageInfo,
                 QUOTA_EXCEEDED,
-                metadataOutputStream
+                metadataOutputStream,
+                BackupType.FULL
             )
         } just Runs
         coEvery { full.cancelFullBackup() } just Runs
@@ -325,7 +331,12 @@
         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 @@
             metadataManager.onPackageBackupError(
                 packageInfo,
                 NO_DATA,
-                metadataOutputStream
+                metadataOutputStream,
+                BackupType.FULL
             )
         } just Runs
         coEvery { full.cancelFullBackup() } just Runs
@@ -361,7 +373,12 @@
         assertEquals(0L, backup.requestFullBackupTime())
 
         verify(exactly = 1) {
-            metadataManager.onPackageBackupError(packageInfo, NO_DATA, metadataOutputStream)
+            metadataManager.onPackageBackupError(
+                packageInfo,
+                NO_DATA,
+                metadataOutputStream,
+                BackupType.FULL
+            )
         }
         verify { metadataOutputStream.close() }
     }