From 2ce625ac87302d8ea9132da7573a252ec3f6b532 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 9 Jul 2019 19:22:24 +0200 Subject: [PATCH] Huge refactoring of backup transport * to get rid of global state * to have a testable architecture * to allow for authenticated encryption * to have a backup format version * to potentially allow for other storage plugins --- .travis.yml | 5 +- app/build.gradle | 34 +- app/libs/commons-io-2.6.jar | Bin 214788 -> 0 bytes .../java/com/stevesoltys/backup/Backup.kt | 5 + .../backup/NotificationBackupObserver.kt | 6 +- .../RestoreBackupActivityController.java | 5 +- .../backup/crypto/CipherFactory.kt | 31 ++ .../com/stevesoltys/backup/crypto/Crypto.kt | 138 +++++++ .../backup/{security => crypto}/KeyManager.kt | 40 +- .../com/stevesoltys/backup/header/Header.kt | 43 ++ .../stevesoltys/backup/header/HeaderReader.kt | 72 ++++ .../stevesoltys/backup/header/HeaderWriter.kt | 50 +++ .../backup/security/CipherUtil.java | 79 ---- .../backup/security/KeyGenerator.java | 44 --- .../backup/service/PackageService.java | 48 --- .../backup/service/PackageService.kt | 57 +++ .../backup/service/backup/BackupObserver.java | 5 - .../service/restore/RestoreService.java | 5 - .../backup/settings/BackupLocationFragment.kt | 2 + .../backup/settings/RecoveryCodeViewModel.kt | 4 +- .../backup/settings/SettingsFragment.kt | 3 +- .../backup/settings/SettingsViewModel.kt | 13 +- .../ConfigurableBackupTransport.java | 199 ---------- .../transport/ConfigurableBackupTransport.kt | 160 ++++++++ .../ConfigurableBackupTransportService.java | 43 -- .../ConfigurableBackupTransportService.kt | 37 ++ .../backup/transport/PluginManager.kt | 56 +++ .../transport/backup/BackupCoordinator.kt | 144 +++++++ .../backup/transport/backup/BackupPlugin.kt | 27 ++ .../backup/transport/backup/FullBackup.kt | 191 +++++++++ .../transport/backup/FullBackupPlugin.kt | 17 + .../backup/transport/backup/InputFactory.kt | 21 + .../backup/transport/backup/KVBackup.kt | 200 ++++++++++ .../backup/transport/backup/KVBackupPlugin.kt | 48 +++ .../plugins/DocumentsProviderBackupPlugin.kt | 46 +++ .../plugins/DocumentsProviderFullBackup.kt | 33 ++ .../plugins/DocumentsProviderKVBackup.kt | 54 +++ .../backup/plugins/DocumentsStorage.kt | 123 ++++++ .../transport/component/BackupComponent.java | 38 -- .../transport/component/RestoreComponent.java | 31 -- .../ContentProviderBackupComponent.java | 367 ------------------ .../ContentProviderBackupConstants.java | 16 - .../provider/ContentProviderBackupState.java | 109 ------ .../ContentProviderRestoreComponent.java | 360 ----------------- .../provider/ContentProviderRestoreState.java | 106 ----- .../backup/transport/restore/FullRestore.kt | 172 ++++++++ .../transport/restore/FullRestorePlugin.kt | 18 + .../backup/transport/restore/KVRestore.kt | 140 +++++++ .../transport/restore/KVRestorePlugin.kt | 30 ++ .../backup/transport/restore/OutputFactory.kt | 21 + .../transport/restore/RestoreCoordinator.kt | 155 ++++++++ .../backup/transport/restore/RestorePlugin.kt | 28 ++ .../DocumentsProviderFullRestorePlugin.kt | 25 ++ .../DocumentsProviderKVRestorePlugin.kt | 42 ++ .../plugins/DocumentsProviderRestorePlugin.kt | 29 ++ app/src/main/res/values/strings.xml | 1 + .../java/com/stevesoltys/backup/TestUtils.kt | 38 ++ .../backup/crypto/CryptoImplTest.kt | 53 +++ .../backup/crypto/CryptoIntegrationTest.kt | 44 +++ .../stevesoltys/backup/crypto/CryptoTest.kt | 192 +++++++++ .../backup/crypto/KeyManagerTestImpl.kt | 26 ++ .../backup/header/HeaderReaderTest.kt | 274 +++++++++++++ .../backup/header/HeaderWriterReaderTest.kt | 102 +++++ .../transport/CoordinatorIntegrationTest.kt | 162 ++++++++ .../backup/transport/TransportTest.kt | 30 ++ .../transport/backup/BackupCoordinatorTest.kt | 110 ++++++ .../backup/transport/backup/BackupTest.kt | 20 + .../backup/transport/backup/FullBackupTest.kt | 276 +++++++++++++ .../backup/transport/backup/KVBackupTest.kt | 216 +++++++++++ .../transport/restore/FullRestoreTest.kt | 173 +++++++++ .../backup/transport/restore/KVRestoreTest.kt | 221 +++++++++++ .../restore/RestoreCoordinatorTest.kt | 189 +++++++++ .../backup/transport/restore/RestoreTest.kt | 24 ++ build.gradle | 1 + 74 files changed, 4447 insertions(+), 1480 deletions(-) delete mode 100644 app/libs/commons-io-2.6.jar create mode 100644 app/src/main/java/com/stevesoltys/backup/crypto/CipherFactory.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/crypto/Crypto.kt rename app/src/main/java/com/stevesoltys/backup/{security => crypto}/KeyManager.kt (52%) create mode 100644 app/src/main/java/com/stevesoltys/backup/header/Header.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/header/HeaderReader.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/header/HeaderWriter.kt delete mode 100644 app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/security/KeyGenerator.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/service/PackageService.java create mode 100644 app/src/main/java/com/stevesoltys/backup/service/PackageService.kt delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.kt delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/PluginManager.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/BackupCoordinator.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/BackupPlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackup.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackupPlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/InputFactory.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackup.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackupPlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderBackupPlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderFullBackup.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderKVBackup.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsStorage.kt delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java delete mode 100644 app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestore.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestorePlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestore.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestorePlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/OutputFactory.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/RestoreCoordinator.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/RestorePlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt create mode 100644 app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderRestorePlugin.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/TestUtils.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/crypto/CryptoImplTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/crypto/CryptoIntegrationTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/crypto/CryptoTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/crypto/KeyManagerTestImpl.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/header/HeaderReaderTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/header/HeaderWriterReaderTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/CoordinatorIntegrationTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/TransportTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/backup/BackupCoordinatorTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/backup/BackupTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/backup/FullBackupTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/backup/KVBackupTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/restore/FullRestoreTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/restore/KVRestoreTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreCoordinatorTest.kt create mode 100644 app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreTest.kt diff --git a/.travis.yml b/.travis.yml index c4e4e394..dd23f68d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ +dist: trusty language: android android: components: - - build-tools-28.0.3 - - android-28 + - build-tools-28.0.3 + - android-28 licenses: - android-sdk-license-.+ diff --git a/app/build.gradle b/app/build.gradle index 0545a964..cd662b64 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,14 @@ android { targetCompatibility 1.8 sourceCompatibility 1.8 } + testOptions { + unitTests.all { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } + } + } // optional signingConfigs def keystorePropertiesFile = rootProject.file("keystore.properties") @@ -43,6 +51,7 @@ android { } } buildTypes.release.signingConfig = signingConfigs.release + buildTypes.debug.signingConfig = signingConfigs.release } } @@ -70,15 +79,17 @@ preBuild.doLast { } } +// To produce these binaries, in latest AOSP source tree, run +// $ make +def aospDeps = fileTree(include: [ + // out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar + 'android.jar', + // out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar + 'libcore.jar' +], dir: 'libs') + dependencies { - // To produce these binaries, in latest AOSP source tree, run - // $ make - compileOnly fileTree(include: [ - // out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar - 'android.jar', - // out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar - 'libcore.jar' - ], dir: 'libs') + compileOnly aospDeps implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -90,4 +101,11 @@ dependencies { implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha' + + testImplementation aospDeps + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.0' + testImplementation 'io.mockk:mockk:1.9.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.0' } diff --git a/app/libs/commons-io-2.6.jar b/app/libs/commons-io-2.6.jar deleted file mode 100644 index 00556b119d45dd85a3c3073b1826916c3c60b9c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214788 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%0AvVBjm&;RJ>Kn<2&;(OV+wD=TU;oOZ`se%a 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Ξ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 J5ikwX{{hg_{IUQ5 diff --git a/app/src/main/java/com/stevesoltys/backup/Backup.kt b/app/src/main/java/com/stevesoltys/backup/Backup.kt index 707bd642..8d03e025 100644 --- a/app/src/main/java/com/stevesoltys/backup/Backup.kt +++ b/app/src/main/java/com/stevesoltys/backup/Backup.kt @@ -4,6 +4,8 @@ import android.app.Application import android.app.backup.IBackupManager import android.content.Context.BACKUP_SERVICE import android.os.ServiceManager.getService +import com.stevesoltys.backup.crypto.KeyManager +import com.stevesoltys.backup.crypto.KeyManagerImpl const val JOB_ID_BACKGROUND_BACKUP = 1 @@ -17,6 +19,9 @@ class Backup : Application() { val backupManager: IBackupManager by lazy { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) } + val keyManager: KeyManager by lazy { + KeyManagerImpl() + } } } diff --git a/app/src/main/java/com/stevesoltys/backup/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/backup/NotificationBackupObserver.kt index d414cb5d..b4d01bb8 100644 --- a/app/src/main/java/com/stevesoltys/backup/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/backup/NotificationBackupObserver.kt @@ -3,7 +3,6 @@ package com.stevesoltys.backup import android.app.NotificationChannel import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_MIN -import android.app.backup.BackupManager import android.app.backup.BackupProgress import android.app.backup.IBackupObserver import android.content.Context @@ -13,12 +12,11 @@ import android.util.Log.isLoggable import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationCompat.PRIORITY_LOW -import com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport private const val CHANNEL_ID = "NotificationBackupObserver" private const val NOTIFICATION_ID = 1 -private val TAG = NotificationBackupObserver::class.java.name +private val TAG = NotificationBackupObserver::class.java.simpleName class NotificationBackupObserver( private val context: Context, @@ -94,11 +92,11 @@ class NotificationBackupObserver( if (isLoggable(TAG, INFO)) { Log.i(TAG, "Backup finished. Status: $status") } - if (status == BackupManager.SUCCESS) getBackupTransport(context).backupFinished() nm.cancel(NOTIFICATION_ID) } private fun getAppName(packageId: String): CharSequence { + if (packageId == "@pm@") return packageId val appInfo = pm.getApplicationInfo(packageId, 0) return pm.getApplicationLabel(appInfo) } diff --git a/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java b/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java index 9daa8799..43374240 100644 --- a/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java +++ b/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java @@ -29,7 +29,8 @@ import java.util.zip.ZipInputStream; import libcore.io.IoUtils; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY; +import static com.stevesoltys.backup.transport.backup.plugins.DocumentsStorageKt.DIRECTORY_FULL_BACKUP; + /** * @author Steve Soltys @@ -84,7 +85,7 @@ class RestoreBackupActivityController { while ((zipEntry = inputStream.getNextEntry()) != null) { String zipEntryPath = zipEntry.getName(); - if (zipEntryPath.startsWith(DEFAULT_FULL_BACKUP_DIRECTORY)) { + if (zipEntryPath.startsWith(DIRECTORY_FULL_BACKUP)) { String fileName = new File(zipEntryPath).getName(); results.add(fileName); } diff --git a/app/src/main/java/com/stevesoltys/backup/crypto/CipherFactory.kt b/app/src/main/java/com/stevesoltys/backup/crypto/CipherFactory.kt new file mode 100644 index 00000000..22bf719d --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/crypto/CipherFactory.kt @@ -0,0 +1,31 @@ +package com.stevesoltys.backup.crypto + +import javax.crypto.Cipher +import javax.crypto.Cipher.DECRYPT_MODE +import javax.crypto.Cipher.ENCRYPT_MODE +import javax.crypto.spec.GCMParameterSpec + +private const val CIPHER_TRANSFORMATION = "AES/GCM/NoPadding" +private const val GCM_AUTHENTICATION_TAG_LENGTH = 128 + +interface CipherFactory { + fun createEncryptionCipher(): Cipher + fun createDecryptionCipher(iv: ByteArray): Cipher +} + +class CipherFactoryImpl(private val keyManager: KeyManager) : CipherFactory { + + override fun createEncryptionCipher(): Cipher { + return Cipher.getInstance(CIPHER_TRANSFORMATION).apply { + init(ENCRYPT_MODE, keyManager.getBackupKey()) + } + } + + override fun createDecryptionCipher(iv: ByteArray): Cipher { + return Cipher.getInstance(CIPHER_TRANSFORMATION).apply { + val spec = GCMParameterSpec(GCM_AUTHENTICATION_TAG_LENGTH, iv) + init(DECRYPT_MODE, keyManager.getBackupKey(), spec) + } + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/backup/crypto/Crypto.kt new file mode 100644 index 00000000..7d615b2a --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/crypto/Crypto.kt @@ -0,0 +1,138 @@ +package com.stevesoltys.backup.crypto + +import com.stevesoltys.backup.header.* +import java.io.EOFException +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** + * A backup stream starts with a version byte followed by an encrypted [VersionHeader]. + * + * The header will be encrypted with AES/GCM to provide authentication. + * It can be written using [encryptHeader] and read using [decryptHeader]. + * The latter throws a [SecurityException], + * if the expected version and package name do not match the encrypted header. + * + * After the header, follows one or more data segments. + * Each segment begins with a clear-text [SegmentHeader] + * that contains the length of the segment + * and a nonce acting as the initialization vector for the encryption. + * The segment can be written using [encryptSegment] and read using [decryptSegment]. + * The latter throws a [SecurityException], + * if the length of the segment is specified larger than allowed. + */ +interface Crypto { + + /** + * Encrypts a backup stream header ([VersionHeader]) and writes it to the given [OutputStream]. + * + * The header using a small segment containing only + * the version number, the package name and (optionally) the key of a key/value stream. + */ + @Throws(IOException::class) + fun encryptHeader(outputStream: OutputStream, versionHeader: VersionHeader) + + /** + * Encrypts a new backup segment from the given cleartext payload + * and writes it to the given [OutputStream]. + * + * A segment starts with a [SegmentHeader] which includes the length of the segment + * and a nonce which is used as initialization vector for the encryption. + * + * After the header follows the encrypted payload. + * Larger backup streams such as from a full backup are encrypted in multiple segments + * to avoid having to load the entire stream into memory when doing authenticated encryption. + * + * The given cleartext can later be decrypted + * by calling [decryptSegment] on the same byte stream. + */ + @Throws(IOException::class) + fun encryptSegment(outputStream: OutputStream, cleartext: ByteArray) + + /** + * Reads and decrypts a [VersionHeader] from the given [InputStream] + * and ensures that the expected version, package name and key match + * what is found in the header. + * If a mismatch is found, a [SecurityException] is thrown. + * + * @return The read [VersionHeader] present in the beginning of the given [InputStream]. + */ + @Throws(IOException::class, SecurityException::class) + fun decryptHeader(inputStream: InputStream, expectedVersion: Byte, expectedPackageName: String, + expectedKey: String? = null): VersionHeader + + /** + * Reads and decrypts a segment from the given [InputStream]. + * + * @return The decrypted segment payload as passed into [encryptSegment] + */ + @Throws(EOFException::class, IOException::class, SecurityException::class) + fun decryptSegment(inputStream: InputStream): ByteArray +} + +class CryptoImpl( + private val cipherFactory: CipherFactory, + private val headerWriter: HeaderWriter, + private val headerReader: HeaderReader) : Crypto { + + @Throws(IOException::class) + override fun encryptHeader(outputStream: OutputStream, versionHeader: VersionHeader) { + val bytes = headerWriter.getEncodedVersionHeader(versionHeader) + + encryptSegment(outputStream, bytes) + } + + @Throws(IOException::class) + override fun encryptSegment(outputStream: OutputStream, cleartext: ByteArray) { + val cipher = cipherFactory.createEncryptionCipher() + + check(cipher.getOutputSize(cleartext.size) <= MAX_SEGMENT_LENGTH) + + val encrypted = cipher.doFinal(cleartext) + val segmentHeader = SegmentHeader(encrypted.size.toShort(), cipher.iv) + headerWriter.writeSegmentHeader(outputStream, segmentHeader) + outputStream.write(encrypted) + } + + @Throws(IOException::class, SecurityException::class) + override fun decryptHeader(inputStream: InputStream, expectedVersion: Byte, + expectedPackageName: String, expectedKey: String?): VersionHeader { + val decrypted = decryptSegment(inputStream, MAX_VERSION_HEADER_SIZE) + val header = headerReader.getVersionHeader(decrypted) + + if (header.version != expectedVersion) { + throw SecurityException("Invalid version '${header.version.toInt()}' in header, expected '${expectedVersion.toInt()}'.") + } + if (header.packageName != expectedPackageName) { + throw SecurityException("Invalid package name '${header.packageName}' in header, expected '$expectedPackageName'.") + } + if (header.key != expectedKey) { + throw SecurityException("Invalid key '${header.key}' in header, expected '$expectedKey'.") + } + + return header + } + + @Throws(EOFException::class, IOException::class, SecurityException::class) + override fun decryptSegment(inputStream: InputStream): ByteArray { + return decryptSegment(inputStream, MAX_SEGMENT_LENGTH) + } + + @Throws(EOFException::class, IOException::class, SecurityException::class) + fun decryptSegment(inputStream: InputStream, maxSegmentLength: Int): ByteArray { + val segmentHeader = headerReader.readSegmentHeader(inputStream) + if (segmentHeader.segmentLength > maxSegmentLength) { + throw SecurityException("Segment length too long: ${segmentHeader.segmentLength} > $maxSegmentLength") + } + + val buffer = ByteArray(segmentHeader.segmentLength.toInt()) + val bytesRead = inputStream.read(buffer) + if (bytesRead == -1) throw EOFException() + if (bytesRead != buffer.size) throw IOException() + val cipher = cipherFactory.createDecryptionCipher(segmentHeader.nonce) + + return cipher.doFinal(buffer) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/security/KeyManager.kt b/app/src/main/java/com/stevesoltys/backup/crypto/KeyManager.kt similarity index 52% rename from app/src/main/java/com/stevesoltys/backup/security/KeyManager.kt rename to app/src/main/java/com/stevesoltys/backup/crypto/KeyManager.kt index 319e219c..793e34d2 100644 --- a/app/src/main/java/com/stevesoltys/backup/security/KeyManager.kt +++ b/app/src/main/java/com/stevesoltys/backup/crypto/KeyManager.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.backup.security +package com.stevesoltys.backup.crypto import android.os.Build.VERSION.SDK_INT import android.security.keystore.KeyProperties.* @@ -8,11 +8,34 @@ import java.security.KeyStore.SecretKeyEntry import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec -private const val KEY_SIZE = 256 +internal const val KEY_SIZE = 256 +private const val KEY_SIZE_BYTES = KEY_SIZE / 8 private const val KEY_ALIAS = "com.stevesoltys.backup" private const val ANDROID_KEY_STORE = "AndroidKeyStore" -object KeyManager { +interface KeyManager { + /** + * Store a new backup key derived from the given [seed]. + * + * The seed needs to be larger or equal to [KEY_SIZE_BYTES]. + */ + fun storeBackupKey(seed: ByteArray) + + /** + * @return true if a backup key already exists in the [KeyStore]. + */ + fun hasBackupKey(): Boolean + + /** + * Returns the backup key, so it can be used for encryption or decryption. + * + * Note that any attempt to export the key will return null or an empty [ByteArray], + * because the key can not leave the [KeyStore]'s hardware security module. + */ + fun getBackupKey(): SecretKey +} + +class KeyManagerImpl : KeyManager { private val keyStore by lazy { KeyStore.getInstance(ANDROID_KEY_STORE).apply { @@ -20,18 +43,18 @@ object KeyManager { } } - fun storeBackupKey(seed: ByteArray) { - if (seed.size < KEY_SIZE / 8) throw IllegalArgumentException() + override fun storeBackupKey(seed: ByteArray) { + if (seed.size < KEY_SIZE_BYTES) throw IllegalArgumentException() // TODO check if using first 256 of 512 bytes produced by PBKDF2WithHmacSHA512 is safe! - val secretKeySpec = SecretKeySpec(seed, 0, KEY_SIZE / 8, "AES") + val secretKeySpec = SecretKeySpec(seed, 0, KEY_SIZE_BYTES, "AES") val ksEntry = SecretKeyEntry(secretKeySpec) keyStore.setEntry(KEY_ALIAS, ksEntry, getKeyProtection()) } - fun hasBackupKey() = keyStore.containsAlias(KEY_ALIAS) && + override fun hasBackupKey() = keyStore.containsAlias(KEY_ALIAS) && keyStore.entryInstanceOf(KEY_ALIAS, SecretKeyEntry::class.java) - fun getBackupKey(): SecretKey { + override fun getBackupKey(): SecretKey { val ksEntry = keyStore.getEntry(KEY_ALIAS, null) as SecretKeyEntry return ksEntry.secretKey } @@ -41,6 +64,7 @@ object KeyManager { .setBlockModes(BLOCK_MODE_GCM) .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) .setRandomizedEncryptionRequired(true) + // unlocking is required only for decryption, so when restoring from backup if (SDK_INT >= 28) builder.setUnlockedDeviceRequired(true) return builder.build() } diff --git a/app/src/main/java/com/stevesoltys/backup/header/Header.kt b/app/src/main/java/com/stevesoltys/backup/header/Header.kt new file mode 100644 index 00000000..806b6afa --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/header/Header.kt @@ -0,0 +1,43 @@ +package com.stevesoltys.backup.header + +import java.nio.charset.Charset + +internal const val VERSION: Byte = 0 +internal const val MAX_PACKAGE_LENGTH_SIZE = 255 +internal const val MAX_KEY_LENGTH_SIZE = MAX_PACKAGE_LENGTH_SIZE +internal const val MAX_VERSION_HEADER_SIZE = 1 + Short.SIZE_BYTES * 2 + MAX_PACKAGE_LENGTH_SIZE + MAX_KEY_LENGTH_SIZE + +/** + * After the first version byte of each backup stream + * must follow followed this header encrypted with authentication. + */ +data class VersionHeader( + internal val version: Byte = VERSION, // 1 byte + internal val packageName: String, // ?? bytes (max 255) + internal val key: String? = null // ?? bytes +) { + init { + check(packageName.length <= MAX_PACKAGE_LENGTH_SIZE) + key?.let { check(key.length <= MAX_KEY_LENGTH_SIZE) } + } +} + + +internal const val SEGMENT_LENGTH_SIZE: Int = Short.SIZE_BYTES +internal const val MAX_SEGMENT_LENGTH: Int = Short.MAX_VALUE.toInt() +internal const val IV_SIZE: Int = 12 +internal const val SEGMENT_HEADER_SIZE = SEGMENT_LENGTH_SIZE + IV_SIZE + +/** + * Each data segment must start with this header + */ +class SegmentHeader( + internal val segmentLength: Short, // 2 bytes + internal val nonce: ByteArray // 12 bytes +) { + init { + check(nonce.size == IV_SIZE) + } +} + +val Utf8: Charset = Charset.forName("UTF-8") diff --git a/app/src/main/java/com/stevesoltys/backup/header/HeaderReader.kt b/app/src/main/java/com/stevesoltys/backup/header/HeaderReader.kt new file mode 100644 index 00000000..2cf0b4a6 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/header/HeaderReader.kt @@ -0,0 +1,72 @@ +package com.stevesoltys.backup.header + +import java.io.EOFException +import java.io.IOException +import java.io.InputStream +import java.nio.ByteBuffer + +interface HeaderReader { + @Throws(IOException::class, UnsupportedVersionException::class) + fun readVersion(inputStream: InputStream): Byte + + @Throws(SecurityException::class) + fun getVersionHeader(byteArray: ByteArray): VersionHeader + + @Throws(EOFException::class, IOException::class) + fun readSegmentHeader(inputStream: InputStream): SegmentHeader +} + +internal class HeaderReaderImpl : HeaderReader { + + @Throws(IOException::class, UnsupportedVersionException::class) + override fun readVersion(inputStream: InputStream): Byte { + val version = inputStream.read().toByte() + if (version < 0) throw IOException() + if (version > VERSION) throw UnsupportedVersionException(version) + return version + } + + override fun getVersionHeader(byteArray: ByteArray): VersionHeader { + val buffer = ByteBuffer.wrap(byteArray) + val version = buffer.get() + + val packageLength = buffer.short.toInt() + if (packageLength <= 0) throw SecurityException("Invalid package length: $packageLength") + if (packageLength > MAX_PACKAGE_LENGTH_SIZE) throw SecurityException("Too large package length: $packageLength") + if (packageLength > buffer.remaining()) throw SecurityException("Not enough bytes for package name") + val packageName = ByteArray(packageLength) + .apply { buffer.get(this) } + .toString(Utf8) + + val keyLength = buffer.short.toInt() + if (keyLength < 0) throw SecurityException("Invalid key length: $keyLength") + if (keyLength > MAX_KEY_LENGTH_SIZE) throw SecurityException("Too large key length: $keyLength") + if (keyLength > buffer.remaining()) throw SecurityException("Not enough bytes for key") + val key = if (keyLength == 0) null else ByteArray(keyLength) + .apply { buffer.get(this) } + .toString(Utf8) + + if (buffer.remaining() != 0) throw SecurityException("Found extra bytes in header") + + return VersionHeader(version, packageName, key) + } + + @Throws(EOFException::class, IOException::class) + override fun readSegmentHeader(inputStream: InputStream): SegmentHeader { + val buffer = ByteArray(SEGMENT_HEADER_SIZE) + val bytesRead = inputStream.read(buffer) + if (bytesRead == -1) throw EOFException() + if (bytesRead != SEGMENT_HEADER_SIZE) { + throw IOException("Read $bytesRead bytes, but expected $SEGMENT_HEADER_SIZE") + } + + val segmentLength = ByteBuffer.wrap(buffer, 0, SEGMENT_LENGTH_SIZE).short + if (segmentLength <= 0) throw IOException() + val nonce = buffer.copyOfRange(SEGMENT_LENGTH_SIZE, buffer.size) + + return SegmentHeader(segmentLength, nonce) + } + +} + +class UnsupportedVersionException(val version: Byte) : IOException() diff --git a/app/src/main/java/com/stevesoltys/backup/header/HeaderWriter.kt b/app/src/main/java/com/stevesoltys/backup/header/HeaderWriter.kt new file mode 100644 index 00000000..36f93b53 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/header/HeaderWriter.kt @@ -0,0 +1,50 @@ +package com.stevesoltys.backup.header + +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer + +interface HeaderWriter { + @Throws(IOException::class) + fun writeVersion(outputStream: OutputStream, header: VersionHeader) + + fun getEncodedVersionHeader(header: VersionHeader): ByteArray + + @Throws(IOException::class) + fun writeSegmentHeader(outputStream: OutputStream, header: SegmentHeader) +} + +internal class HeaderWriterImpl : HeaderWriter { + + @Throws(IOException::class) + override fun writeVersion(outputStream: OutputStream, header: VersionHeader) { + val headerBytes = ByteArray(1) + headerBytes[0] = header.version + outputStream.write(headerBytes) + } + + override fun getEncodedVersionHeader(header: VersionHeader): ByteArray { + val packageBytes = header.packageName.toByteArray(Utf8) + val keyBytes = header.key?.toByteArray(Utf8) + val size = 1 + 2 + packageBytes.size + 2 + (keyBytes?.size ?: 0) + return ByteBuffer.allocate(size).apply { + put(header.version) + putShort(packageBytes.size.toShort()) + put(packageBytes) + if (keyBytes == null) { + putShort(0.toShort()) + } else { + putShort(keyBytes.size.toShort()) + put(keyBytes) + } + }.array() + } + + override fun writeSegmentHeader(outputStream: OutputStream, header: SegmentHeader) { + val buffer = ByteBuffer.allocate(SEGMENT_HEADER_SIZE) + .putShort(header.segmentLength) + .put(header.nonce) + outputStream.write(buffer.array()) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java b/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java deleted file mode 100644 index 868930f2..00000000 --- a/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.stevesoltys.backup.security; - -import javax.crypto.*; -import javax.crypto.spec.IvParameterSpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -/** - * A utility class for encrypting and decrypting data using a {@link Cipher}. - * - * @author Steve Soltys - */ -public class CipherUtil { - - /** - * The cipher algorithm. - */ - public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - - /** - * . - * Encrypts the given payload using the provided secret key. - * - * @param payload The payload. - * @param secretKey The secret key. - * @param iv The initialization vector. - */ - public static byte[] encrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, - NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, - InvalidAlgorithmParameterException, InvalidKeyException { - - return startEncrypt(secretKey, iv).doFinal(payload); - } - - /** - * Initializes a cipher in {@link Cipher#ENCRYPT_MODE}. - * - * @param secretKey The secret key. - * @param iv The initialization vector. - * @return The initialized cipher. - */ - public static Cipher startEncrypt(SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { - - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - return cipher; - } - - /** - * Decrypts the given payload using the provided secret key. - * - * @param payload The payload. - * @param secretKey The secret key. - * @param iv The initialization vector. - */ - public static byte[] decrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, - NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, - InvalidAlgorithmParameterException, InvalidKeyException { - - return startDecrypt(secretKey, iv).doFinal(payload); - } - - /** - * Initializes a cipher in {@link Cipher#DECRYPT_MODE}. - * - * @param secretKey The secret key. - * @param iv The initialization vector. - * @return The initialized cipher. - */ - public static Cipher startDecrypt(SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { - - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); - return cipher; - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/security/KeyGenerator.java b/app/src/main/java/com/stevesoltys/backup/security/KeyGenerator.java deleted file mode 100644 index 9323e7b3..00000000 --- a/app/src/main/java/com/stevesoltys/backup/security/KeyGenerator.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.stevesoltys.backup.security; - -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; - -/** - * A utility class which can be used for generating an AES secret key using PBKDF2. - * - * @author Steve Soltys - */ -public class KeyGenerator { - - /** - * The number of iterations for key generation. - */ - private static final int ITERATIONS = 32767; - - /** - * The generated key length. - */ - private static final int KEY_LENGTH = 256; - - /** - * Generates an AES secret key using PBKDF2. - * - * @param password The password. - * @param salt The salt. - * @return The generated key. - */ - public static SecretKey generate(String password, byte[] salt) - throws NoSuchAlgorithmException, InvalidKeySpecException { - - SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH); - - SecretKey secretKey = secretKeyFactory.generateSecret(keySpec); - return new SecretKeySpec(secretKey.getEncoded(), "AES"); - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/service/PackageService.java b/app/src/main/java/com/stevesoltys/backup/service/PackageService.java deleted file mode 100644 index 0b7560a6..00000000 --- a/app/src/main/java/com/stevesoltys/backup/service/PackageService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.stevesoltys.backup.service; - -import android.app.backup.IBackupManager; -import android.content.pm.IPackageManager; -import android.content.pm.PackageInfo; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; - -import java.util.List; -import java.util.Set; - -import static com.google.android.collect.Sets.newArraySet; - -/** - * @author Steve Soltys - */ -public class PackageService { - - private final IBackupManager backupManager; - - private final IPackageManager packageManager; - - private static final Set<String> IGNORED_PACKAGES = newArraySet( - "com.android.externalstorage", - "com.android.providers.downloads.ui", - "com.android.providers.downloads", - "com.android.providers.media", - "com.android.providers.calendar", - "com.android.providers.contacts", - "com.stevesoltys.backup" - ); - - public PackageService() { - backupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup")); - packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - } - - public String[] getEligiblePackages() throws RemoteException { - List<PackageInfo> packages = packageManager.getInstalledPackages(0, UserHandle.USER_SYSTEM).getList(); - String[] packageArray = packages.stream() - .map(packageInfo -> packageInfo.packageName) - .filter(packageName -> !IGNORED_PACKAGES.contains(packageName)) - .toArray(String[]::new); - - return backupManager.filterAppsEligibleForBackup(packageArray); - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/service/PackageService.kt b/app/src/main/java/com/stevesoltys/backup/service/PackageService.kt new file mode 100644 index 00000000..91dea0cf --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/service/PackageService.kt @@ -0,0 +1,57 @@ +package com.stevesoltys.backup.service + +import android.content.pm.IPackageManager +import android.content.pm.PackageInfo +import android.os.RemoteException +import android.os.ServiceManager.getService +import android.os.UserHandle +import android.util.Log +import com.google.android.collect.Sets.newArraySet +import com.stevesoltys.backup.Backup +import java.util.* + +private val TAG = PackageService::class.java.simpleName + +private val IGNORED_PACKAGES = newArraySet( + "com.android.externalstorage", + "com.android.providers.downloads.ui", + "com.android.providers.downloads", + "com.android.providers.media", + "com.android.providers.calendar", + "com.android.providers.contacts", + "com.stevesoltys.backup" +) + +/** + * @author Steve Soltys + * @author Torsten Grote + */ +class PackageService { + + private val backupManager = Backup.backupManager + private val packageManager: IPackageManager = IPackageManager.Stub.asInterface(getService("package")) + + val eligiblePackages: Array<String> + @Throws(RemoteException::class) + get() { + val packages: List<PackageInfo> = packageManager.getInstalledPackages(0, UserHandle.USER_SYSTEM).list as List<PackageInfo> + val packageList = packages + .map { packageInfo -> packageInfo.packageName } + .filter { packageName -> !IGNORED_PACKAGES.contains(packageName) } + .sorted() + + Log.d(TAG, "Got ${packageList.size} packages: $packageList") + + // TODO why is this filtering out so much? + val eligibleApps = backupManager.filterAppsEligibleForBackup(packageList.toTypedArray()) + + Log.d(TAG, "Filtering left ${eligibleApps.size} eligible packages: ${Arrays.toString(eligibleApps)}") + + // add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data + val packageArray = eligibleApps.toMutableList() + packageArray.add("@pm@") + + return packageArray.toTypedArray() + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java index a319b377..8bcbfefa 100644 --- a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java +++ b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java @@ -12,8 +12,6 @@ import com.stevesoltys.backup.session.backup.BackupResult; import com.stevesoltys.backup.session.backup.BackupSession; import com.stevesoltys.backup.session.backup.BackupSessionObserver; -import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport; - /** * @author Steve Soltys */ @@ -61,9 +59,6 @@ class BackupObserver implements BackupSessionObserver { @Override public void backupSessionCompleted(BackupSession backupSession, BackupResult backupResult) { - - if (backupResult == BackupResult.SUCCESS) getBackupTransport(context).backupFinished(); - context.runOnUiThread(() -> { if (backupResult == BackupResult.SUCCESS) { Toast.makeText(context, R.string.backup_success, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java index 6441397a..8e966b8f 100644 --- a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java +++ b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java @@ -12,12 +12,9 @@ import com.stevesoltys.backup.activity.PopupWindowUtil; import com.stevesoltys.backup.activity.restore.RestorePopupWindowListener; import com.stevesoltys.backup.service.TransportService; import com.stevesoltys.backup.session.restore.RestoreSession; -import com.stevesoltys.backup.transport.ConfigurableBackupTransport; import java.util.Set; -import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport; - /** * @author Steve Soltys */ @@ -28,8 +25,6 @@ public class RestoreService { private final TransportService transportService = new TransportService(); public void restorePackages(Set<String> selectedPackages, Uri contentUri, Activity parent, String password) { - ConfigurableBackupTransport backupTransport = getBackupTransport(parent.getApplication()); - backupTransport.prepareRestore(password, contentUri); try { PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent); RestoreObserver restoreObserver = new RestoreObserver(parent, popupWindow, selectedPackages.size()); diff --git a/app/src/main/java/com/stevesoltys/backup/settings/BackupLocationFragment.kt b/app/src/main/java/com/stevesoltys/backup/settings/BackupLocationFragment.kt index 140ea439..eab22e01 100644 --- a/app/src/main/java/com/stevesoltys/backup/settings/BackupLocationFragment.kt +++ b/app/src/main/java/com/stevesoltys/backup/settings/BackupLocationFragment.kt @@ -5,6 +5,7 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.content.Intent.* import android.os.Bundle +import android.provider.DocumentsContract.EXTRA_PROMPT import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.lifecycle.ViewModelProviders @@ -38,6 +39,7 @@ class BackupLocationFragment : PreferenceFragmentCompat() { private fun showChooseFolderActivity() { val openTreeIntent = Intent(ACTION_OPEN_DOCUMENT_TREE) + openTreeIntent.putExtra(EXTRA_PROMPT, getString(R.string.settings_backup_location_picker)) openTreeIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION or FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION) try { diff --git a/app/src/main/java/com/stevesoltys/backup/settings/RecoveryCodeViewModel.kt b/app/src/main/java/com/stevesoltys/backup/settings/RecoveryCodeViewModel.kt index a64f4ec8..59643546 100644 --- a/app/src/main/java/com/stevesoltys/backup/settings/RecoveryCodeViewModel.kt +++ b/app/src/main/java/com/stevesoltys/backup/settings/RecoveryCodeViewModel.kt @@ -3,9 +3,9 @@ package com.stevesoltys.backup.settings import android.app.Application import android.util.ByteStringUtils.toHexString import androidx.lifecycle.AndroidViewModel +import com.stevesoltys.backup.Backup import com.stevesoltys.backup.LiveEvent import com.stevesoltys.backup.MutableLiveEvent -import com.stevesoltys.backup.security.KeyManager import io.github.novacrypto.bip39.* import io.github.novacrypto.bip39.Validation.InvalidChecksumException import io.github.novacrypto.bip39.Validation.InvalidWordCountException @@ -48,7 +48,7 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica } val mnemonic = input.joinToString(" ") val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "") - KeyManager.storeBackupKey(seed) + Backup.keyManager.storeBackupKey(seed) // TODO remove once encryption/decryption uses key from KeyStore setBackupPassword(getApplication(), toHexString(seed)) diff --git a/app/src/main/java/com/stevesoltys/backup/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/backup/settings/SettingsFragment.kt index ccc0bb35..e36627d7 100644 --- a/app/src/main/java/com/stevesoltys/backup/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/backup/settings/SettingsFragment.kt @@ -10,6 +10,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.widget.Toast +import android.widget.Toast.LENGTH_SHORT import androidx.lifecycle.ViewModelProviders import androidx.preference.Preference.OnPreferenceChangeListener import androidx.preference.PreferenceFragmentCompat @@ -99,7 +100,7 @@ class SettingsFragment : PreferenceFragmentCompat() { true } item.itemId == R.id.action_restore -> { - Toast.makeText(requireContext(), "Not yet implemented", Toast.LENGTH_SHORT).show() + Toast.makeText(requireContext(), "Not yet implemented", LENGTH_SHORT).show() true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/com/stevesoltys/backup/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/backup/settings/SettingsViewModel.kt index dd20a14a..4c243eba 100644 --- a/app/src/main/java/com/stevesoltys/backup/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/backup/settings/SettingsViewModel.kt @@ -4,13 +4,15 @@ import android.app.Application import android.content.Intent import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION +import android.util.Log import androidx.lifecycle.AndroidViewModel +import com.stevesoltys.backup.Backup import com.stevesoltys.backup.LiveEvent import com.stevesoltys.backup.MutableLiveEvent -import com.stevesoltys.backup.security.KeyManager import com.stevesoltys.backup.service.backup.requestFullBackup +import com.stevesoltys.backup.transport.ConfigurableBackupTransportService -private val TAG = SettingsViewModel::class.java.name +private val TAG = SettingsViewModel::class.java.simpleName class SettingsViewModel(application: Application) : AndroidViewModel(application) { @@ -27,7 +29,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application internal val chooseBackupLocation: LiveEvent<Boolean> = mChooseBackupLocation internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true) - fun recoveryCodeIsSet() = KeyManager.hasBackupKey() + fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey() fun locationIsSet() = getBackupFolderUri(getApplication()) != null fun handleChooseFolderResult(result: Intent?) { @@ -45,6 +47,11 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application // notify the UI that the location has been set locationWasSet.setEvent(wasEmptyBefore) + + // stop backup service to be sure the old location will get updated + app.stopService(Intent(app, ConfigurableBackupTransportService::class.java)) + + Log.d(TAG, "New storage location chosen: $folderUri") } fun backupNow() = Thread { requestFullBackup(app) }.start() diff --git a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java deleted file mode 100644 index 522a6a14..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.stevesoltys.backup.transport; - -import android.app.backup.BackupTransport; -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import com.stevesoltys.backup.settings.SettingsActivity; -import com.stevesoltys.backup.transport.component.BackupComponent; -import com.stevesoltys.backup.transport.component.RestoreComponent; -import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupComponent; -import com.stevesoltys.backup.transport.component.provider.ContentProviderRestoreComponent; - -import static android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; -import static android.os.Build.VERSION.SDK_INT; - -/** - * @author Steve Soltys - */ -public class ConfigurableBackupTransport extends BackupTransport { - - private static final String TRANSPORT_DIRECTORY_NAME = - "com.stevesoltys.backup.transport.ConfigurableBackupTransport"; - - private static final String TAG = TRANSPORT_DIRECTORY_NAME; - - private final Context context; - - private final BackupComponent backupComponent; - - private final RestoreComponent restoreComponent; - - ConfigurableBackupTransport(Context context) { - this.context = context; - backupComponent = new ContentProviderBackupComponent(context); - restoreComponent = new ContentProviderRestoreComponent(context); - } - - public void prepareRestore(String password, Uri fileUri) { - restoreComponent.prepareRestore(password, fileUri); - } - - @Override - public String transportDirName() { - return TRANSPORT_DIRECTORY_NAME; - } - - @Override - public String name() { - // TODO: Make this class non-static in ConfigurableBackupTransportService and use Context and a ComponentName. - return this.getClass().getName(); - } - - @Override - public int getTransportFlags() { - if (SDK_INT >= 28) return FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; - return 0; - } - - @Override - public Intent dataManagementIntent() { - return new Intent(context, SettingsActivity.class); - } - - @Override - public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) { - return true; - } - - @Override - public long requestBackupTime() { - return backupComponent.requestBackupTime(); - } - - @Override - public String dataManagementLabel() { - return backupComponent.dataManagementLabel(); - } - - @Override - public int initializeDevice() { - return backupComponent.initializeDevice(); - } - - @Override - public String currentDestinationString() { - return backupComponent.currentDestinationString(); - } - - /* Methods related to Backup */ - - @Override - public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) { - return backupComponent.performIncrementalBackup(packageInfo, inFd, flags); - } - - @Override - public int performBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) { - Log.w(TAG, "Warning: Legacy performBackup() method called."); - return performBackup(targetPackage, fileDescriptor, 0); - } - - @Override - public int checkFullBackupSize(long size) { - return backupComponent.checkFullBackupSize(size); - } - - @Override - public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags) { - // TODO handle flags - return performFullBackup(targetPackage, socket); - } - - @Override - public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) { - return backupComponent.performFullBackup(targetPackage, fileDescriptor); - } - - @Override - public int sendBackupData(int numBytes) { - return backupComponent.sendBackupData(numBytes); - } - - @Override - public void cancelFullBackup() { - backupComponent.cancelFullBackup(); - } - - @Override - public int finishBackup() { - return backupComponent.finishBackup(); - } - - @Override - public long requestFullBackupTime() { - return backupComponent.requestFullBackupTime(); - } - - @Override - public long getBackupQuota(String packageName, boolean isFullBackup) { - return backupComponent.getBackupQuota(packageName, isFullBackup); - } - - @Override - public int clearBackupData(PackageInfo packageInfo) { - return backupComponent.clearBackupData(packageInfo); - } - - public void backupFinished() { - backupComponent.backupFinished(); - } - - /* Methods related to Restore */ - - @Override - public long getCurrentRestoreSet() { - return restoreComponent.getCurrentRestoreSet(); - } - - @Override - public int startRestore(long token, PackageInfo[] packages) { - return restoreComponent.startRestore(token, packages); - } - - @Override - public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { - return restoreComponent.getNextFullRestoreDataChunk(socket); - } - - @Override - public RestoreSet[] getAvailableRestoreSets() { - return restoreComponent.getAvailableRestoreSets(); - } - - @Override - public RestoreDescription nextRestorePackage() { - return restoreComponent.nextRestorePackage(); - } - - @Override - public int getRestoreData(ParcelFileDescriptor outputFileDescriptor) { - return restoreComponent.getRestoreData(outputFileDescriptor); - } - - @Override - public int abortFullRestore() { - return restoreComponent.abortFullRestore(); - } - - @Override - public void finishRestore() { - restoreComponent.finishRestore(); - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.kt b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.kt new file mode 100644 index 00000000..53e47cf0 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.kt @@ -0,0 +1,160 @@ +package com.stevesoltys.backup.transport + +import android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED +import android.app.backup.BackupTransport +import android.app.backup.RestoreDescription +import android.app.backup.RestoreSet +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.os.Build.VERSION.SDK_INT +import android.os.ParcelFileDescriptor +import android.util.Log +import com.stevesoltys.backup.settings.SettingsActivity + +const val DEFAULT_RESTORE_SET_TOKEN: Long = 1 + +private const val TRANSPORT_DIRECTORY_NAME = "com.stevesoltys.backup.transport.ConfigurableBackupTransport" +private val TAG = ConfigurableBackupTransport::class.java.simpleName + +/** + * @author Steve Soltys + * @author Torsten Grote + */ +class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport() { + + private val pluginManager = PluginManager(context) + private val backupCoordinator = pluginManager.backupCoordinator + private val restoreCoordinator = pluginManager.restoreCoordinator + + override fun transportDirName(): String { + return TRANSPORT_DIRECTORY_NAME + } + + override fun name(): String { + // TODO: Make this class non-static in ConfigurableBackupTransportService and use Context and a ComponentName. + return this.javaClass.name + } + + override fun getTransportFlags(): Int { + return if (SDK_INT >= 28) FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED else 0 + } + + override fun dataManagementIntent(): Intent { + return Intent(context, SettingsActivity::class.java) + } + + override fun dataManagementLabel(): String { + return "Please file a bug if you see this! 1" + } + + override fun currentDestinationString(): String { + return "Please file a bug if you see this! 2" + } + + // ------------------------------------------------------------------------------------ + // General backup methods + // + + override fun initializeDevice(): Int { + return backupCoordinator.initializeDevice() + } + + override fun isAppEligibleForBackup(targetPackage: PackageInfo, isFullBackup: Boolean): Boolean { + return backupCoordinator.isAppEligibleForBackup(targetPackage, isFullBackup) + } + + override fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { + return backupCoordinator.getBackupQuota(packageName, isFullBackup) + } + + override fun clearBackupData(packageInfo: PackageInfo): Int { + return backupCoordinator.clearBackupData(packageInfo) + } + + override fun finishBackup(): Int { + return backupCoordinator.finishBackup() + } + + // ------------------------------------------------------------------------------------ + // Key/value incremental backup support + // + + override fun requestBackupTime(): Long { + return backupCoordinator.requestBackupTime() + } + + override fun performBackup(packageInfo: PackageInfo, inFd: ParcelFileDescriptor, flags: Int): Int { + return backupCoordinator.performIncrementalBackup(packageInfo, inFd, flags) + } + + override fun performBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor): Int { + Log.w(TAG, "Warning: Legacy performBackup() method called.") + return performBackup(targetPackage, fileDescriptor, 0) + } + + // ------------------------------------------------------------------------------------ + // Full backup + // + + override fun requestFullBackupTime(): Long { + return backupCoordinator.requestFullBackupTime() + } + + override fun checkFullBackupSize(size: Long): Int { + return backupCoordinator.checkFullBackupSize(size) + } + + override fun performFullBackup(targetPackage: PackageInfo, socket: ParcelFileDescriptor, flags: Int): Int { + return backupCoordinator.performFullBackup(targetPackage, socket, flags) + } + + override fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor): Int { + return backupCoordinator.performFullBackup(targetPackage, fileDescriptor, 0) + } + + override fun sendBackupData(numBytes: Int): Int { + return backupCoordinator.sendBackupData(numBytes) + } + + override fun cancelFullBackup() { + backupCoordinator.cancelFullBackup() + } + + // ------------------------------------------------------------------------------------ + // Restore + // + + override fun getAvailableRestoreSets(): Array<RestoreSet>? { + return restoreCoordinator.getAvailableRestoreSets() + } + + override fun getCurrentRestoreSet(): Long { + return restoreCoordinator.getCurrentRestoreSet() + } + + override fun startRestore(token: Long, packages: Array<PackageInfo>): Int { + return restoreCoordinator.startRestore(token, packages) + } + + override fun getNextFullRestoreDataChunk(socket: ParcelFileDescriptor): Int { + return restoreCoordinator.getNextFullRestoreDataChunk(socket) + } + + override fun nextRestorePackage(): RestoreDescription? { + return restoreCoordinator.nextRestorePackage() + } + + override fun getRestoreData(outputFileDescriptor: ParcelFileDescriptor): Int { + return restoreCoordinator.getRestoreData(outputFileDescriptor) + } + + override fun abortFullRestore(): Int { + return restoreCoordinator.abortFullRestore() + } + + override fun finishRestore() { + restoreCoordinator.finishRestore() + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java deleted file mode 100644 index da896889..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.stevesoltys.backup.transport; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; - -/** - * @author Steve Soltys - */ -public class ConfigurableBackupTransportService extends Service { - - private static final String TAG = ConfigurableBackupTransportService.class.getName(); - - private static ConfigurableBackupTransport backupTransport = null; - - public static ConfigurableBackupTransport getBackupTransport(Context context) { - - if (backupTransport == null) { - backupTransport = new ConfigurableBackupTransport(context); - } - - return backupTransport; - } - - @Override - public void onCreate() { - super.onCreate(); - Log.d(TAG, "Service created."); - } - - @Override - public IBinder onBind(Intent intent) { - return getBackupTransport(getApplicationContext()).getBinder(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(TAG, "Service destroyed."); - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.kt b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.kt new file mode 100644 index 00000000..f178245d --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.kt @@ -0,0 +1,37 @@ +package com.stevesoltys.backup.transport + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.util.Log + +private val TAG = ConfigurableBackupTransportService::class.java.simpleName + +/** + * @author Steve Soltys + * @author Torsten Grote + */ +class ConfigurableBackupTransportService : Service() { + + private var transport: ConfigurableBackupTransport? = null + + override fun onCreate() { + super.onCreate() + transport = ConfigurableBackupTransport(applicationContext) + Log.d(TAG, "Service created.") + } + + override fun onBind(intent: Intent): IBinder { + val transport = this.transport ?: throw IllegalStateException() + return transport.binder.apply { + Log.d(TAG, "Transport bound.") + } + } + + override fun onDestroy() { + super.onDestroy() + transport = null + Log.d(TAG, "Service destroyed.") + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/PluginManager.kt b/app/src/main/java/com/stevesoltys/backup/transport/PluginManager.kt new file mode 100644 index 00000000..0c0c6471 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/PluginManager.kt @@ -0,0 +1,56 @@ +package com.stevesoltys.backup.transport + +import android.content.Context +import android.os.Build +import com.stevesoltys.backup.Backup +import com.stevesoltys.backup.crypto.CipherFactoryImpl +import com.stevesoltys.backup.crypto.CryptoImpl +import com.stevesoltys.backup.header.HeaderReaderImpl +import com.stevesoltys.backup.header.HeaderWriterImpl +import com.stevesoltys.backup.settings.getBackupFolderUri +import com.stevesoltys.backup.transport.backup.BackupCoordinator +import com.stevesoltys.backup.transport.backup.FullBackup +import com.stevesoltys.backup.transport.backup.InputFactory +import com.stevesoltys.backup.transport.backup.KVBackup +import com.stevesoltys.backup.transport.backup.plugins.DocumentsProviderBackupPlugin +import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage +import com.stevesoltys.backup.transport.restore.FullRestore +import com.stevesoltys.backup.transport.restore.KVRestore +import com.stevesoltys.backup.transport.restore.OutputFactory +import com.stevesoltys.backup.transport.restore.RestoreCoordinator +import com.stevesoltys.backup.transport.restore.plugins.DocumentsProviderRestorePlugin + +class PluginManager(context: Context) { + + // We can think about using an injection framework such as Dagger to simplify this. + + private val storage = DocumentsStorage(context, getBackupFolderUri(context), getDeviceName()) + + private val headerWriter = HeaderWriterImpl() + private val headerReader = HeaderReaderImpl() + private val cipherFactory = CipherFactoryImpl(Backup.keyManager) + private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader) + + + private val backupPlugin = DocumentsProviderBackupPlugin(storage, context.packageManager) + private val inputFactory = InputFactory() + private val kvBackup = KVBackup(backupPlugin.kvBackupPlugin, inputFactory, headerWriter, crypto) + private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto) + + internal val backupCoordinator = BackupCoordinator(backupPlugin, kvBackup, fullBackup) + + + private val restorePlugin = DocumentsProviderRestorePlugin(storage) + private val outputFactory = OutputFactory() + private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, outputFactory, headerReader, crypto) + private val fullRestore = FullRestore(restorePlugin.fullRestorePlugin, outputFactory, headerReader, crypto) + + internal val restoreCoordinator = RestoreCoordinator(restorePlugin, kvRestore, fullRestore) + + + private fun getDeviceName(): String { + // TODO add device specific unique ID to the end + return "${Build.MANUFACTURER} ${Build.MODEL}" + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupCoordinator.kt new file mode 100644 index 00000000..65ad91b0 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupCoordinator.kt @@ -0,0 +1,144 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_OK +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import java.io.IOException + +private val TAG = BackupCoordinator::class.java.simpleName + +/** + * @author Steve Soltys + * @author Torsten Grote + */ +class BackupCoordinator( + private val plugin: BackupPlugin, + private val kv: KVBackup, + private val full: FullBackup) { + + private var calledInitialize = false + private var calledClearBackupData = false + + // ------------------------------------------------------------------------------------ + // Transport initialization and quota + // + + /** + * Initialize the storage for this device, erasing all stored data. + * The transport may send the request immediately, or may buffer it. + * After this is called, + * [finishBackup] will be called to ensure the request is sent and received successfully. + * + * If the transport returns anything other than [TRANSPORT_OK] from this method, + * the OS will halt the current initialize operation and schedule a retry in the near future. + * Even if the transport is in a state + * such that attempting to "initialize" the backend storage is meaningless - + * for example, if there is no current live data-set at all, + * or there is no authenticated account under which to store the data remotely - + * the transport should return [TRANSPORT_OK] here + * and treat the initializeDevice() / finishBackup() pair as a graceful no-op. + * + * @return One of [TRANSPORT_OK] (OK so far) or + * [TRANSPORT_ERROR] (to retry following network error or other failure). + */ + fun initializeDevice(): Int { + Log.i(TAG, "Initialize Device!") + return try { + plugin.initializeDevice() + // [finishBackup] will only be called when we return [TRANSPORT_OK] here + // so we remember that we initialized successfully + calledInitialize = true + TRANSPORT_OK + } catch (e: IOException) { + Log.e(TAG, "Error initializing device", e) + TRANSPORT_ERROR + } + } + + fun isAppEligibleForBackup(targetPackage: PackageInfo, @Suppress("UNUSED_PARAMETER") isFullBackup: Boolean): Boolean { + // We need to exclude the DocumentsProvider used to store backup data. + // Otherwise, it gets killed when we back it up, terminating our backup. + return targetPackage.packageName != plugin.providerPackageName + } + + fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { + Log.i(TAG, "Get backup quota for $packageName. Is full backup: $isFullBackup.") + val quota = if (isFullBackup) full.getQuota() else kv.getQuota() + Log.i(TAG, "Reported quota of $quota bytes.") + return quota + } + + // ------------------------------------------------------------------------------------ + // Key/value incremental backup support + // + + fun requestBackupTime() = kv.requestBackupTime() + + fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int) = + kv.performBackup(packageInfo, data, flags) + + // ------------------------------------------------------------------------------------ + // Full backup + // + + fun requestFullBackupTime() = full.requestFullBackupTime() + + fun checkFullBackupSize(size: Long) = full.checkFullBackupSize(size) + + fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int) = + full.performFullBackup(targetPackage, fileDescriptor, flags) + + fun sendBackupData(numBytes: Int) = full.sendBackupData(numBytes) + + fun cancelFullBackup() = full.cancelFullBackup() + + // Clear and Finish + + /** + * Erase the given application's data from the backup destination. + * This clears out the given package's data from the current backup set, + * making it as though the app had never yet been backed up. + * After this is called, [finishBackup] must be called + * to ensure that the operation is recorded successfully. + * + * @return the same error codes as [performFullBackup]. + */ + fun clearBackupData(packageInfo: PackageInfo): Int { + val packageName = packageInfo.packageName + Log.i(TAG, "Clear Backup Data of $packageName.") + try { + kv.clearBackupData(packageInfo) + } catch (e: IOException) { + Log.w(TAG, "Error clearing K/V backup data for $packageName", e) + return TRANSPORT_ERROR + } + try { + full.clearBackupData(packageInfo) + } catch (e: IOException) { + Log.w(TAG, "Error clearing full backup data for $packageName", e) + return TRANSPORT_ERROR + } + calledClearBackupData = true + return TRANSPORT_OK + } + + fun finishBackup(): Int = when { + kv.hasState() -> { + if (full.hasState()) throw IllegalStateException() + kv.finishBackup() + } + full.hasState() -> { + if (kv.hasState()) throw IllegalStateException() + full.finishBackup() + } + calledInitialize || calledClearBackupData -> { + calledInitialize = false + calledClearBackupData = false + TRANSPORT_OK + } + else -> throw IllegalStateException() + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupPlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupPlugin.kt new file mode 100644 index 00000000..b3d6f5dc --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/BackupPlugin.kt @@ -0,0 +1,27 @@ +package com.stevesoltys.backup.transport.backup + +import java.io.IOException + +interface BackupPlugin { + + val kvBackupPlugin: KVBackupPlugin + + val fullBackupPlugin: FullBackupPlugin + + /** + * Initialize the storage for this device, erasing all stored data. + */ + @Throws(IOException::class) + fun initializeDevice() + + /** + * Returns the package name of the app that provides the backend storage + * which is used for the current backup location. + * + * Plugins are advised to cache this as it will be requested frequently. + * + * @return null if no package name could be found + */ + val providerPackageName: String? + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackup.kt new file mode 100644 index 00000000..0a56c59b --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackup.kt @@ -0,0 +1,191 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupTransport.* +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import com.stevesoltys.backup.crypto.Crypto +import com.stevesoltys.backup.header.HeaderWriter +import com.stevesoltys.backup.header.VersionHeader +import libcore.io.IoUtils.closeQuietly +import org.apache.commons.io.IOUtils +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +private class FullBackupState( + internal val packageInfo: PackageInfo, + internal val inputFileDescriptor: ParcelFileDescriptor, + internal val inputStream: InputStream, + internal val outputStream: OutputStream) { + internal val packageName: String = packageInfo.packageName + internal var size: Long = 0 +} + +const val DEFAULT_QUOTA_FULL_BACKUP = (2 * (25 * 1024 * 1024)).toLong() + +private val TAG = FullBackup::class.java.simpleName + +class FullBackup( + private val plugin: FullBackupPlugin, + private val inputFactory: InputFactory, + private val headerWriter: HeaderWriter, + private val crypto: Crypto) { + + private var state: FullBackupState? = null + + fun hasState() = state != null + + fun requestFullBackupTime(): Long { + Log.i(TAG, "Request full backup time") + return 0 + } + + fun getQuota(): Long = plugin.getQuota() + + fun checkFullBackupSize(size: Long): Int { + Log.i(TAG, "Check full backup size of $size bytes.") + return when { + size <= 0 -> TRANSPORT_PACKAGE_REJECTED + size > plugin.getQuota() -> TRANSPORT_QUOTA_EXCEEDED + else -> TRANSPORT_OK + } + } + + /** + * Begin the process of sending a packages' full-data archive to the backend. + * The description of the package whose data will be delivered is provided, + * as well as the socket file descriptor on which the transport will receive the data itself. + * + * If the package is not eligible for backup, + * the transport should return [TRANSPORT_PACKAGE_REJECTED]. + * In this case the system will simply proceed with the next candidate if any, + * or finish the full backup operation if all apps have been processed. + * + * After the transport returns [TRANSPORT_OK] from this method, + * the OS will proceed to call [sendBackupData] one or more times + * to deliver the packages' data as a streamed tarball. + * The transport should not read() from the socket except as instructed to + * via the [sendBackupData] method. + * + * After all data has been delivered to the transport, the system will call [finishBackup]. + * At this point the transport should commit the data to its datastore, if appropriate, + * and close the socket that had been provided in [performFullBackup]. + * + * If the transport returns [TRANSPORT_OK] from this method, + * then the OS will always provide a matching call to [finishBackup] + * even if sending data via [sendBackupData] failed at some point. + * + * @param targetPackage The package whose data is to follow. + * @param socket The socket file descriptor through which the data will be provided. + * If the transport returns [TRANSPORT_PACKAGE_REJECTED] here, + * it must still close this file descriptor now; + * otherwise it should be cached for use during succeeding calls to [sendBackupData], + * and closed in response to [finishBackup]. + * @param flags [FLAG_USER_INITIATED] or 0. + * @return [TRANSPORT_PACKAGE_REJECTED] to indicate that the package is not to be backed up; + * [TRANSPORT_OK] to indicate that the OS may proceed with delivering backup data; + * [TRANSPORT_ERROR] to indicate an error that precludes performing a backup at this time. + */ + fun performFullBackup(targetPackage: PackageInfo, socket: ParcelFileDescriptor, @Suppress("UNUSED_PARAMETER") flags: Int = 0): Int { + if (state != null) throw AssertionError() + Log.i(TAG, "Perform full backup for ${targetPackage.packageName}.") + + // get OutputStream to write backup data into + val outputStream = try { + plugin.getOutputStream(targetPackage) + } catch (e: IOException) { + Log.e(TAG, "Error getting OutputStream for full backup of ${targetPackage.packageName}", e) + return backupError(TRANSPORT_ERROR) + } + + // create new state + val inputStream = inputFactory.getInputStream(socket) + state = FullBackupState(targetPackage, socket, inputStream, outputStream) + + // store version header + val state = this.state ?: throw AssertionError() + val header = VersionHeader(packageName = state.packageName) + try { + headerWriter.writeVersion(state.outputStream, header) + crypto.encryptHeader(state.outputStream, header) + } catch (e: IOException) { + Log.e(TAG, "Error writing backup header", e) + return backupError(TRANSPORT_ERROR) + } + return TRANSPORT_OK + } + + /** + * Method to reset state, + * because [finishBackup] is not called + * when we don't return [TRANSPORT_OK] from [performFullBackup]. + */ + private fun backupError(result: Int): Int { + Log.i(TAG, "Resetting state because of full backup error.") + state = null + return result + } + + fun sendBackupData(numBytes: Int): Int { + val state = this.state + ?: throw AssertionError("Attempted sendBackupData before performFullBackup") + + // check if size fits quota + state.size += numBytes + val quota = plugin.getQuota() + if (state.size > quota) { + Log.w(TAG, "Full backup of additional $numBytes exceeds quota of $quota with ${state.size}.") + return TRANSPORT_QUOTA_EXCEEDED + } + + Log.i(TAG, "Send full backup data of $numBytes bytes.") + + return try { + val payload = IOUtils.readFully(state.inputStream, numBytes) + crypto.encryptSegment(state.outputStream, payload) + TRANSPORT_OK + } catch (e: IOException) { + Log.e(TAG, "Error handling backup data for ${state.packageName}: ", e) + TRANSPORT_ERROR + } + } + + fun clearBackupData(packageInfo: PackageInfo) { + // TODO + } + + fun cancelFullBackup() { + Log.i(TAG, "Cancel full backup") + val state = this.state ?: throw AssertionError("No state when canceling") + clearState() + try { + plugin.cancelFullBackup(state.packageInfo) + } catch (e: IOException) { + Log.w(TAG, "Error cancelling full backup for ${state.packageName}", e) + } + // TODO roll back to the previous known-good archive + } + + fun finishBackup(): Int { + Log.i(TAG, "Finish full backup of ${state!!.packageName}.") + return clearState() + } + + private fun clearState(): Int { + val state = this.state ?: throw AssertionError("Trying to clear empty state.") + return try { + state.outputStream.flush() + closeQuietly(state.outputStream) + closeQuietly(state.inputStream) + closeQuietly(state.inputFileDescriptor) + TRANSPORT_OK + } catch (e: IOException) { + Log.w(TAG, "Error when clearing state", e) + TRANSPORT_ERROR + } finally { + this.state = null + } + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackupPlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackupPlugin.kt new file mode 100644 index 00000000..e1c882d6 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/FullBackupPlugin.kt @@ -0,0 +1,17 @@ +package com.stevesoltys.backup.transport.backup + +import android.content.pm.PackageInfo +import java.io.IOException +import java.io.OutputStream + +interface FullBackupPlugin { + + fun getQuota(): Long + + @Throws(IOException::class) + fun getOutputStream(targetPackage: PackageInfo): OutputStream + + @Throws(IOException::class) + fun cancelFullBackup(targetPackage: PackageInfo) + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/InputFactory.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/InputFactory.kt new file mode 100644 index 00000000..78db8dbb --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/InputFactory.kt @@ -0,0 +1,21 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupDataInput +import android.os.ParcelFileDescriptor +import java.io.FileInputStream +import java.io.InputStream + +/** + * This class exists for easier testing, so we can mock it and return custom data inputs. + */ +class InputFactory { + + fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput { + return BackupDataInput(inputFileDescriptor.fileDescriptor) + } + + fun getInputStream(inputFileDescriptor: ParcelFileDescriptor): InputStream { + return FileInputStream(inputFileDescriptor.fileDescriptor) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackup.kt new file mode 100644 index 00000000..5a14f43a --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackup.kt @@ -0,0 +1,200 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupTransport.* +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import com.stevesoltys.backup.crypto.Crypto +import com.stevesoltys.backup.header.HeaderWriter +import com.stevesoltys.backup.header.Utf8 +import com.stevesoltys.backup.header.VersionHeader +import libcore.io.IoUtils.closeQuietly +import java.io.IOException +import java.util.Base64.getUrlEncoder + +class KVBackupState(internal val packageName: String) + +const val DEFAULT_QUOTA_KEY_VALUE_BACKUP = (2 * (5 * 1024 * 1024)).toLong() + +private val TAG = KVBackup::class.java.simpleName + +class KVBackup( + private val plugin: KVBackupPlugin, + private val inputFactory: InputFactory, + private val headerWriter: HeaderWriter, + private val crypto: Crypto) { + + private var state: KVBackupState? = null + + fun hasState() = state != null + + fun requestBackupTime(): Long { + Log.i(TAG, "Request K/V backup time") + return 0 + } + + fun getQuota(): Long = plugin.getQuota() + + fun performBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int): Int { + val isIncremental = flags and FLAG_INCREMENTAL != 0 + val isNonIncremental = flags and FLAG_NON_INCREMENTAL != 0 + val packageName = packageInfo.packageName + + when { + isIncremental -> { + Log.i(TAG, "Performing incremental K/V backup for $packageName") + } + isNonIncremental -> { + Log.i(TAG, "Performing non-incremental K/V backup for $packageName") + } + else -> { + Log.i(TAG, "Performing K/V backup for $packageName") + } + } + + // initialize state + if (this.state != null) throw AssertionError() + this.state = KVBackupState(packageInfo.packageName) + + // check if we have existing data for the given package + val hasDataForPackage = try { + plugin.hasDataForPackage(packageInfo) + } catch (e: IOException) { + Log.e(TAG, "Error checking for existing data for ${packageInfo.packageName}.", e) + return backupError(TRANSPORT_ERROR) + } + if (isIncremental && !hasDataForPackage) { + Log.w(TAG, "Requested incremental, but transport currently stores no data $packageName, requesting non-incremental retry.") + return backupError(TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) + } + + // TODO check if package is over-quota + + if (isNonIncremental && hasDataForPackage) { + Log.w(TAG, "Requested non-incremental, deleting existing data.") + try { + clearBackupData(packageInfo) + } catch (e: IOException) { + Log.w(TAG, "Error clearing backup data for ${packageInfo.packageName}.", e) + } + } + + // ensure there's a place to store K/V for the given package + try { + plugin.ensureRecordStorageForPackage(packageInfo) + } catch (e: IOException) { + Log.e(TAG, "Error ensuring storage for ${packageInfo.packageName}.", e) + return backupError(TRANSPORT_ERROR) + } + + // parse and store the K/V updates + return storeRecords(packageInfo, data) + } + + private fun storeRecords(packageInfo: PackageInfo, data: ParcelFileDescriptor): Int { + // apply the delta operations + for (result in parseBackupStream(data)) { + if (result is Result.Error) { + Log.e(TAG, "Exception reading backup input", result.exception) + return backupError(TRANSPORT_ERROR) + } + val op = (result as Result.Ok).result + try { + if (op.value == null) { + Log.e(TAG, "Deleting record with base64Key ${op.base64Key}") + plugin.deleteRecord(packageInfo, op.base64Key) + } else { + val outputStream = plugin.getOutputStreamForRecord(packageInfo, op.base64Key) + val header = VersionHeader(packageName = packageInfo.packageName, key = op.key) + headerWriter.writeVersion(outputStream, header) + crypto.encryptHeader(outputStream, header) + crypto.encryptSegment(outputStream, op.value) + outputStream.flush() + closeQuietly(outputStream) + } + } catch (e: IOException) { + Log.e(TAG, "Unable to update base64Key file for base64Key ${op.base64Key}", e) + return backupError(TRANSPORT_ERROR) + } + } + return TRANSPORT_OK + } + + /** + * Parses a backup stream into individual key/value operations + */ + private fun parseBackupStream(data: ParcelFileDescriptor): Sequence<Result<KVOperation>> { + val changeSet = inputFactory.getBackupDataInput(data) + + // Each K/V pair in the restore set is kept in its own file, named by the record key. + // Wind through the data file, extracting individual record operations + // and building a sequence of all the updates to apply in this update. + return generateSequence { + // read the next header or end the sequence in case of error or no more headers + try { + if (!changeSet.readNextHeader()) return@generateSequence null // end the sequence + } catch (e: IOException) { + Log.e(TAG, "Error reading next header", e) + return@generateSequence Result.Error(e) + } + // encode key + val key = changeSet.key + val base64Key = getUrlEncoder().withoutPadding().encodeToString(key.toByteArray(Utf8)) + val dataSize = changeSet.dataSize + + // read and encrypt value + val value = if (dataSize >= 0) { + Log.v(TAG, " Delta operation key $key size $dataSize key64 $base64Key") + val bytes = ByteArray(dataSize) + val bytesRead = try { + changeSet.readEntityData(bytes, 0, dataSize) + } catch (e: IOException) { + Log.e(TAG, "Error reading entity data for key $key", e) + return@generateSequence Result.Error(e) + } + if (bytesRead != dataSize) { + Log.w(TAG, "Expecting $dataSize bytes, but only read $bytesRead.") + } + bytes + } else null + // add change operation to the sequence + Result.Ok(KVOperation(key, base64Key, value)) + } + } + + @Throws(IOException::class) + fun clearBackupData(packageInfo: PackageInfo) { + plugin.removeDataOfPackage(packageInfo) + } + + fun finishBackup(): Int { + Log.i(TAG, "Finish K/V Backup of ${state!!.packageName}") + state = null + return TRANSPORT_OK + } + + /** + * Method to reset state, + * because [finishBackup] is not called when we don't return [TRANSPORT_OK]. + */ + private fun backupError(result: Int): Int { + Log.i(TAG, "Resetting state because of K/V Backup error of ${state!!.packageName}") + state = null + return result + } + + private class KVOperation( + internal val key: String, + internal val base64Key: String, + /** + * value is null when this is a deletion operation + */ + internal val value: ByteArray? + ) + + private sealed class Result<out T> { + class Ok<out T>(val result: T) : Result<T>() + class Error(val exception: Exception) : Result<Nothing>() + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackupPlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackupPlugin.kt new file mode 100644 index 00000000..4e1146ee --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/KVBackupPlugin.kt @@ -0,0 +1,48 @@ +package com.stevesoltys.backup.transport.backup + +import android.content.pm.PackageInfo +import java.io.IOException +import java.io.OutputStream + +interface KVBackupPlugin { + + /** + * Get quota for key/value backups. + */ + fun getQuota(): Long + + /** + * Return true if there are records stored for the given package. + */ + @Throws(IOException::class) + fun hasDataForPackage(packageInfo: PackageInfo): Boolean + + /** + * This marks the beginning of a backup operation. + * + * Make sure that there is a place to store K/V pairs for the given package. + * E.g. file-based plugins should a create a directory for the package, if none exists. + */ + @Throws(IOException::class) + fun ensureRecordStorageForPackage(packageInfo: PackageInfo) + + /** + * Return an [OutputStream] for the given package and key + * which will receive the record's encrypted value. + */ + @Throws(IOException::class) + fun getOutputStreamForRecord(packageInfo: PackageInfo, key: String): OutputStream + + /** + * Delete the record for the given package identified by the given key. + */ + @Throws(IOException::class) + fun deleteRecord(packageInfo: PackageInfo, key: String) + + /** + * Remove all data associated with the given package. + */ + @Throws(IOException::class) + fun removeDataOfPackage(packageInfo: PackageInfo) + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderBackupPlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderBackupPlugin.kt new file mode 100644 index 00000000..2d80d581 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderBackupPlugin.kt @@ -0,0 +1,46 @@ +package com.stevesoltys.backup.transport.backup.plugins + +import android.content.pm.PackageManager +import com.stevesoltys.backup.transport.backup.BackupPlugin +import com.stevesoltys.backup.transport.backup.FullBackupPlugin +import com.stevesoltys.backup.transport.backup.KVBackupPlugin +import java.io.IOException + +private const val NO_MEDIA = ".nomedia" + +class DocumentsProviderBackupPlugin( + private val storage: DocumentsStorage, + packageManager: PackageManager) : BackupPlugin { + + override val kvBackupPlugin: KVBackupPlugin by lazy { + DocumentsProviderKVBackup(storage) + } + + override val fullBackupPlugin: FullBackupPlugin by lazy { + DocumentsProviderFullBackup(storage) + } + + @Throws(IOException::class) + override fun initializeDevice() { + // get or create root backup dir + val rootDir = storage.rootBackupDir ?: throw IOException() + + // create .nomedia file to prevent Android's MediaScanner from trying to index the backup + rootDir.createOrGetFile(NO_MEDIA) + + // create backup folders + val kvDir = storage.defaultKvBackupDir + val fullDir = storage.defaultFullBackupDir + + // wipe existing data + kvDir?.deleteContents() + fullDir?.deleteContents() + } + + override val providerPackageName: String? by lazy { + val authority = storage.rootBackupDir?.uri?.authority ?: return@lazy null + val providerInfo = packageManager.resolveContentProvider(authority, 0) ?: return@lazy null + providerInfo.packageName + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderFullBackup.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderFullBackup.kt new file mode 100644 index 00000000..2116b926 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderFullBackup.kt @@ -0,0 +1,33 @@ +package com.stevesoltys.backup.transport.backup.plugins + +import android.content.pm.PackageInfo +import android.util.Log +import com.stevesoltys.backup.transport.backup.DEFAULT_QUOTA_FULL_BACKUP +import com.stevesoltys.backup.transport.backup.FullBackupPlugin +import java.io.IOException +import java.io.OutputStream + +private val TAG = DocumentsProviderFullBackup::class.java.simpleName + +class DocumentsProviderFullBackup( + private val storage: DocumentsStorage) : FullBackupPlugin { + + override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP + + @Throws(IOException::class) + override fun getOutputStream(targetPackage: PackageInfo): OutputStream { + // TODO test file-size after overwriting bigger file + val file = storage.defaultFullBackupDir?.createOrGetFile(targetPackage.packageName) + ?: throw IOException() + return storage.getOutputStream(file) + } + + @Throws(IOException::class) + override fun cancelFullBackup(targetPackage: PackageInfo) { + val packageName = targetPackage.packageName + Log.i(TAG, "Deleting $packageName due to canceled backup...") + val file = storage.defaultFullBackupDir?.findFile(packageName) ?: return + if (!file.delete()) throw IOException("Failed to delete $packageName") + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderKVBackup.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderKVBackup.kt new file mode 100644 index 00000000..dd6a08ca --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsProviderKVBackup.kt @@ -0,0 +1,54 @@ +package com.stevesoltys.backup.transport.backup.plugins + +import android.content.pm.PackageInfo +import androidx.documentfile.provider.DocumentFile +import com.stevesoltys.backup.transport.backup.DEFAULT_QUOTA_KEY_VALUE_BACKUP +import com.stevesoltys.backup.transport.backup.KVBackupPlugin +import java.io.IOException +import java.io.OutputStream + +class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin { + + private var packageFile: DocumentFile? = null + + override fun getQuota(): Long = DEFAULT_QUOTA_KEY_VALUE_BACKUP + + @Throws(IOException::class) + override fun hasDataForPackage(packageInfo: PackageInfo): Boolean { + val packageFile = storage.defaultKvBackupDir?.findFile(packageInfo.packageName) + ?: return false + return packageFile.listFiles().isNotEmpty() + } + + @Throws(IOException::class) + override fun ensureRecordStorageForPackage(packageInfo: PackageInfo) { + // remember package file for subsequent operations + packageFile = storage.getOrCreateKVBackupDir().createOrGetDirectory(packageInfo.packageName) + } + + @Throws(IOException::class) + override fun removeDataOfPackage(packageInfo: PackageInfo) { + // we cannot use the cached this.packageFile here, + // because this can be called before [ensureRecordStorageForPackage] + val packageFile = storage.defaultKvBackupDir?.findFile(packageInfo.packageName) ?: return + packageFile.delete() + } + + @Throws(IOException::class) + override fun deleteRecord(packageInfo: PackageInfo, key: String) { + val packageFile = this.packageFile ?: throw AssertionError() + packageFile.assertRightFile(packageInfo) + val keyFile = packageFile.findFile(key) ?: return + keyFile.delete() + } + + @Throws(IOException::class) + override fun getOutputStreamForRecord(packageInfo: PackageInfo, key: String): OutputStream { + val packageFile = this.packageFile ?: throw AssertionError() + packageFile.assertRightFile(packageInfo) + // TODO check what happens if we overwrite a bigger file + val keyFile = packageFile.createOrGetFile(key) + return storage.getOutputStream(keyFile) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsStorage.kt new file mode 100644 index 00000000..20bf10df --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/backup/plugins/DocumentsStorage.kt @@ -0,0 +1,123 @@ +package com.stevesoltys.backup.transport.backup.plugins + +import android.content.Context +import android.content.pm.PackageInfo +import android.net.Uri +import android.util.Log +import androidx.documentfile.provider.DocumentFile +import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +const val DIRECTORY_FULL_BACKUP = "full" +const val DIRECTORY_KEY_VALUE_BACKUP = "kv" +private const val ROOT_DIR_NAME = ".AndroidBackup" +private const val MIME_TYPE = "application/octet-stream" + +private val TAG = DocumentsStorage::class.java.simpleName + +class DocumentsStorage(context: Context, parentFolder: Uri?, deviceName: String) { + + private val contentResolver = context.contentResolver + + internal val rootBackupDir: DocumentFile? by lazy { + val folderUri = parentFolder ?: return@lazy null + val parent = DocumentFile.fromTreeUri(context, folderUri) ?: throw AssertionError() + try { + parent.createOrGetDirectory(ROOT_DIR_NAME) + } catch (e: IOException) { + Log.e(TAG, "Error creating root backup dir.", e) + null + } + } + + private val deviceDir: DocumentFile? by lazy { + try { + rootBackupDir?.createOrGetDirectory(deviceName) + } catch (e: IOException) { + Log.e(TAG, "Error creating current restore set dir.", e) + null + } + } + + private val defaultSetDir: DocumentFile? by lazy { + val currentSetName = DEFAULT_RESTORE_SET_TOKEN.toString() + try { + deviceDir?.createOrGetDirectory(currentSetName) + } catch (e: IOException) { + Log.e(TAG, "Error creating current restore set dir.", e) + null + } + } + + val defaultFullBackupDir: DocumentFile? by lazy { + try { + defaultSetDir?.createOrGetDirectory(DIRECTORY_FULL_BACKUP) + } catch (e: IOException) { + Log.e(TAG, "Error creating full backup dir.", e) + null + } + } + + val defaultKvBackupDir: DocumentFile? by lazy { + try { + defaultSetDir?.createOrGetDirectory(DIRECTORY_KEY_VALUE_BACKUP) + } catch (e: IOException) { + Log.e(TAG, "Error creating K/V backup dir.", e) + null + } + } + + private fun getSetDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? { + if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultSetDir + return deviceDir?.findFile(token.toString()) + } + + fun getKVBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? { + if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultKvBackupDir ?: throw IOException() + return getSetDir(token)?.findFile(DIRECTORY_KEY_VALUE_BACKUP) + } + + @Throws(IOException::class) + fun getOrCreateKVBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile { + if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultKvBackupDir ?: throw IOException() + val setDir = getSetDir(token) ?: throw IOException() + return setDir.createOrGetDirectory(DIRECTORY_KEY_VALUE_BACKUP) + } + + fun getFullBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? { + if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultFullBackupDir ?: throw IOException() + return getSetDir(token)?.findFile(DIRECTORY_FULL_BACKUP) + } + + @Throws(IOException::class) + fun getInputStream(file: DocumentFile): InputStream { + return contentResolver.openInputStream(file.uri) ?: throw IOException() + } + + @Throws(IOException::class) + fun getOutputStream(file: DocumentFile): OutputStream { + return contentResolver.openOutputStream(file.uri) ?: throw IOException() + } + +} + +@Throws(IOException::class) +fun DocumentFile.createOrGetFile(name: String, mimeType: String = MIME_TYPE): DocumentFile { + return findFile(name) ?: createFile(mimeType, name) ?: throw IOException() +} + +@Throws(IOException::class) +fun DocumentFile.createOrGetDirectory(name: String): DocumentFile { + return findFile(name) ?: createDirectory(name) ?: throw IOException() +} + +@Throws(IOException::class) +fun DocumentFile.deleteContents() { + for (file in listFiles()) file.delete() +} + +fun DocumentFile.assertRightFile(packageInfo: PackageInfo) { + if (name != packageInfo.packageName) throw AssertionError() +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java deleted file mode 100644 index 92715d61..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.stevesoltys.backup.transport.component; - -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; - -/** - * @author Steve Soltys - */ -public interface BackupComponent { - - String currentDestinationString(); - - String dataManagementLabel(); - - int initializeDevice(); - - int clearBackupData(PackageInfo packageInfo); - - int finishBackup(); - - int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data, int flags); - - int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor); - - int checkFullBackupSize(long size); - - int sendBackupData(int numBytes); - - void cancelFullBackup(); - - long getBackupQuota(String packageName, boolean fullBackup); - - long requestBackupTime(); - - long requestFullBackupTime(); - - void backupFinished(); -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java deleted file mode 100644 index 08f866a0..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.stevesoltys.backup.transport.component; - -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; -import android.content.pm.PackageInfo; -import android.net.Uri; -import android.os.ParcelFileDescriptor; - -/** - * @author Steve Soltys - */ -public interface RestoreComponent { - - void prepareRestore(String password, Uri fileUri); - - int startRestore(long token, PackageInfo[] packages); - - RestoreDescription nextRestorePackage(); - - int getRestoreData(ParcelFileDescriptor outputFileDescriptor); - - int getNextFullRestoreDataChunk(ParcelFileDescriptor socket); - - int abortFullRestore(); - - long getCurrentRestoreSet(); - - void finishRestore(); - - RestoreSet[] getAvailableRestoreSets(); -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java deleted file mode 100644 index a951d50a..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java +++ /dev/null @@ -1,367 +0,0 @@ -package com.stevesoltys.backup.transport.component.provider; - -import android.app.backup.BackupDataInput; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Base64; -import android.util.Log; - -import com.stevesoltys.backup.security.CipherUtil; -import com.stevesoltys.backup.security.KeyGenerator; -import com.stevesoltys.backup.transport.component.BackupComponent; - -import org.apache.commons.io.IOUtils; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.Locale; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; - -import libcore.io.IoUtils; - -import static android.app.backup.BackupTransport.FLAG_INCREMENTAL; -import static android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL; -import static android.app.backup.BackupTransport.TRANSPORT_ERROR; -import static android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED; -import static android.app.backup.BackupTransport.TRANSPORT_OK; -import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED; -import static android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED; -import static android.provider.DocumentsContract.buildDocumentUriUsingTree; -import static android.provider.DocumentsContract.createDocument; -import static android.provider.DocumentsContract.getTreeDocumentId; -import static com.stevesoltys.backup.activity.MainActivityController.DOCUMENT_MIME_TYPE; -import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupFolderUri; -import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_BACKUP_QUOTA; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_INCREMENTAL_BACKUP_DIRECTORY; -import static java.util.Objects.requireNonNull; - -/** - * @author Steve Soltys - */ -public class ContentProviderBackupComponent implements BackupComponent { - - private static final String TAG = ContentProviderBackupComponent.class.getSimpleName(); - - private static final String DOCUMENT_SUFFIX = "yyyy-MM-dd_HH_mm_ss"; - - private static final String DESTINATION_DESCRIPTION = "Backing up to zip file"; - - private static final String TRANSPORT_DATA_MANAGEMENT_LABEL = ""; - - private static final int INITIAL_BUFFER_SIZE = 512; - - private final Context context; - - private ContentProviderBackupState backupState; - - public ContentProviderBackupComponent(Context context) { - this.context = context; - } - - @Override - public void cancelFullBackup() { - clearBackupState(false); - } - - @Override - public int checkFullBackupSize(long size) { - int result = TRANSPORT_OK; - - if (size <= 0) { - result = TRANSPORT_PACKAGE_REJECTED; - - } else if (size > DEFAULT_BACKUP_QUOTA) { - result = TRANSPORT_QUOTA_EXCEEDED; - } - - return result; - } - - @Override - public int clearBackupData(PackageInfo packageInfo) { - return TRANSPORT_OK; - } - - @Override - public String currentDestinationString() { - return DESTINATION_DESCRIPTION; - } - - @Override - public String dataManagementLabel() { - return TRANSPORT_DATA_MANAGEMENT_LABEL; - } - - @Override - public int finishBackup() { - return clearBackupState(false); - } - - @Override - public long getBackupQuota(String packageName, boolean fullBackup) { - return DEFAULT_BACKUP_QUOTA; - } - - @Override - public int initializeDevice() { - return TRANSPORT_OK; - } - - @Override - public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) { - - if (backupState != null && backupState.getInputFileDescriptor() != null) { - Log.e(TAG, "Attempt to initiate full backup while one is in progress"); - return TRANSPORT_ERROR; - } - - try { - initializeBackupState(); - backupState.setPackageName(targetPackage.packageName); - - backupState.setInputFileDescriptor(fileDescriptor); - backupState.setInputStream(new FileInputStream(fileDescriptor.getFileDescriptor())); - backupState.setBytesTransferred(0); - - Cipher cipher = CipherUtil.startEncrypt(backupState.getSecretKey(), backupState.getSalt()); - backupState.setCipher(cipher); - - ZipEntry zipEntry = new ZipEntry(DEFAULT_FULL_BACKUP_DIRECTORY + backupState.getPackageName()); - backupState.getOutputStream().putNextEntry(zipEntry); - - } catch (Exception ex) { - Log.e(TAG, "Error creating backup file for " + targetPackage.packageName + ": ", ex); - clearBackupState(true); - return TRANSPORT_ERROR; - } - - return TRANSPORT_OK; - } - - @Override - public int performIncrementalBackup(PackageInfo packageInfo, ParcelFileDescriptor data, int flags) { - boolean isIncremental = (flags & FLAG_INCREMENTAL) != 0; - if (isIncremental) { - Log.w(TAG, "Can not handle incremental backup. Requesting non-incremental for " + packageInfo.packageName); - return TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED; - } - - boolean isNonIncremental = (flags & FLAG_NON_INCREMENTAL) != 0; - if (isNonIncremental) { - Log.i(TAG, "Performing non-incremental backup for " + packageInfo.packageName); - } else { - Log.i(TAG, "Performing backup for " + packageInfo.packageName); - } - - BackupDataInput backupDataInput = new BackupDataInput(data.getFileDescriptor()); - - try { - initializeBackupState(); - backupState.setPackageName(packageInfo.packageName); - - return transferIncrementalBackupData(backupDataInput); - - } catch (Exception ex) { - Log.e(TAG, "Error reading backup input: ", ex); - return TRANSPORT_ERROR; - } - } - - @Override - public long requestBackupTime() { - return 0; - } - - @Override - public long requestFullBackupTime() { - return 0; - } - - @Override - public int sendBackupData(int numBytes) { - - if (backupState == null) { - Log.e(TAG, "Attempted sendBackupData() before performFullBackup()"); - return TRANSPORT_ERROR; - } - - long bytesTransferred = backupState.getBytesTransferred() + numBytes; - - if (bytesTransferred > DEFAULT_BACKUP_QUOTA) { - return TRANSPORT_QUOTA_EXCEEDED; - } - - InputStream inputStream = backupState.getInputStream(); - ZipOutputStream outputStream = backupState.getOutputStream(); - - try { - byte[] payload = IOUtils.readFully(inputStream, numBytes); - - if (backupState.getCipher() != null) { - payload = backupState.getCipher().update(payload); - } - - outputStream.write(payload, 0, numBytes); - backupState.setBytesTransferred(bytesTransferred); - - } catch (Exception ex) { - Log.e(TAG, "Error handling backup data for " + backupState.getPackageName() + ": ", ex); - return TRANSPORT_ERROR; - } - return TRANSPORT_OK; - } - - private int transferIncrementalBackupData(BackupDataInput backupDataInput) throws IOException { - ZipOutputStream outputStream = backupState.getOutputStream(); - - int bufferSize = INITIAL_BUFFER_SIZE; - byte[] buffer = new byte[bufferSize]; - - while (backupDataInput.readNextHeader()) { - String chunkFileName = Base64.encodeToString(backupDataInput.getKey().getBytes(), Base64.DEFAULT); - int dataSize = backupDataInput.getDataSize(); - - if (dataSize >= 0) { - ZipEntry zipEntry = new ZipEntry(DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + - backupState.getPackageName() + "/" + chunkFileName); - outputStream.putNextEntry(zipEntry); - - if (dataSize > bufferSize) { - bufferSize = dataSize; - buffer = new byte[bufferSize]; - } - - backupDataInput.readEntityData(buffer, 0, dataSize); - - try { - if (backupState.getSecretKey() != null) { - byte[] payload = Arrays.copyOfRange(buffer, 0, dataSize); - SecretKey secretKey = backupState.getSecretKey(); - byte[] salt = backupState.getSalt(); - - outputStream.write(CipherUtil.encrypt(payload, secretKey, salt)); - - } else { - outputStream.write(buffer, 0, dataSize); - } - - } catch (Exception ex) { - Log.e(TAG, "Error performing incremental backup for " + backupState.getPackageName() + ": ", ex); - clearBackupState(true); - return TRANSPORT_ERROR; - } - } - } - - return TRANSPORT_OK; - } - - @Override - public void backupFinished() { - clearBackupState(true); - } - - private void initializeBackupState() throws Exception { - if (backupState == null) { - backupState = new ContentProviderBackupState(); - } - - if (backupState.getOutputStream() == null) { - initializeOutputStream(); - - ZipEntry saltZipEntry = new ZipEntry(ContentProviderBackupConstants.SALT_FILE_PATH); - backupState.getOutputStream().putNextEntry(saltZipEntry); - backupState.getOutputStream().write(backupState.getSalt()); - backupState.getOutputStream().closeEntry(); - - String password = requireNonNull(getBackupPassword(context)); - backupState.setSecretKey(KeyGenerator.generate(password, backupState.getSalt())); - } - } - - private void initializeOutputStream() throws IOException { - Uri folderUri = getBackupFolderUri(context); - // TODO notify about failure with notification - Uri fileUri = createBackupFile(folderUri); - - ParcelFileDescriptor outputFileDescriptor = context.getContentResolver().openFileDescriptor(fileUri, "w"); - if (outputFileDescriptor == null) throw new IOException(); - backupState.setOutputFileDescriptor(outputFileDescriptor); - - FileOutputStream fileOutputStream = new FileOutputStream(outputFileDescriptor.getFileDescriptor()); - ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream); - backupState.setOutputStream(zipOutputStream); - } - - private Uri createBackupFile(Uri folderUri) throws IOException { - Uri documentUri = buildDocumentUriUsingTree(folderUri, getTreeDocumentId(folderUri)); - try { - Uri fileUri = createDocument(context.getContentResolver(), documentUri, DOCUMENT_MIME_TYPE, getBackupFileName()); - if (fileUri == null) throw new IOException(); - return fileUri; - - } catch (SecurityException e) { - // happens when folder was deleted and thus Uri permission don't exist anymore - throw new IOException(e); - } - } - - private String getBackupFileName() { - SimpleDateFormat dateFormat = new SimpleDateFormat(DOCUMENT_SUFFIX, Locale.US); - String date = dateFormat.format(new Date()); - return "backup-" + date; - } - - private int clearBackupState(boolean closeFile) { - - if (backupState == null) { - return TRANSPORT_OK; - } - - try { - IoUtils.closeQuietly(backupState.getInputFileDescriptor()); - backupState.setInputFileDescriptor(null); - - ZipOutputStream outputStream = backupState.getOutputStream(); - - if (outputStream != null) { - - if (backupState.getCipher() != null) { - outputStream.write(backupState.getCipher().doFinal()); - backupState.setCipher(null); - } - - outputStream.closeEntry(); - } - if (closeFile) { - Log.d(TAG, "Closing backup file..."); - if (outputStream != null) { - outputStream.finish(); - outputStream.close(); - } - - IoUtils.closeQuietly(backupState.getOutputFileDescriptor()); - backupState = null; - } - - } catch (Exception ex) { - Log.e(TAG, "Error cancelling full backup: ", ex); - return TRANSPORT_ERROR; - } - - return TRANSPORT_OK; - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java deleted file mode 100644 index 4020f9d9..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.stevesoltys.backup.transport.component.provider; - -/** - * @author Steve Soltys - */ -public interface ContentProviderBackupConstants { - - String SALT_FILE_PATH = "salt"; - - String DEFAULT_FULL_BACKUP_DIRECTORY = "full/"; - - String DEFAULT_INCREMENTAL_BACKUP_DIRECTORY = "incr/"; - - long DEFAULT_BACKUP_QUOTA = Long.MAX_VALUE; - -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java deleted file mode 100644 index 0adc8d17..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.stevesoltys.backup.transport.component.provider; - -import android.os.ParcelFileDescriptor; - -import java.io.InputStream; -import java.security.SecureRandom; -import java.util.zip.ZipOutputStream; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; - -/** - * @author Steve Soltys - */ -class ContentProviderBackupState { - - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - - private ParcelFileDescriptor inputFileDescriptor; - - private ParcelFileDescriptor outputFileDescriptor; - - private InputStream inputStream; - - private ZipOutputStream outputStream; - - private Cipher cipher; - - private long bytesTransferred; - - private String packageName; - - private byte[] salt; - - private SecretKey secretKey; - - ContentProviderBackupState() { - salt = new byte[16]; - SECURE_RANDOM.nextBytes(salt); - } - - long getBytesTransferred() { - return bytesTransferred; - } - - void setBytesTransferred(long bytesTransferred) { - this.bytesTransferred = bytesTransferred; - } - - Cipher getCipher() { - return cipher; - } - - void setCipher(Cipher cipher) { - this.cipher = cipher; - } - - ParcelFileDescriptor getInputFileDescriptor() { - return inputFileDescriptor; - } - - void setInputFileDescriptor(ParcelFileDescriptor inputFileDescriptor) { - this.inputFileDescriptor = inputFileDescriptor; - } - - InputStream getInputStream() { - return inputStream; - } - - void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; - } - - ParcelFileDescriptor getOutputFileDescriptor() { - return outputFileDescriptor; - } - - void setOutputFileDescriptor(ParcelFileDescriptor outputFileDescriptor) { - this.outputFileDescriptor = outputFileDescriptor; - } - - ZipOutputStream getOutputStream() { - return outputStream; - } - - void setOutputStream(ZipOutputStream outputStream) { - this.outputStream = outputStream; - } - - String getPackageName() { - return packageName; - } - - void setPackageName(String packageName) { - this.packageName = packageName; - } - - byte[] getSalt() { - return salt; - } - - SecretKey getSecretKey() { - return secretKey; - } - - void setSecretKey(SecretKey secretKey) { - this.secretKey = secretKey; - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java deleted file mode 100644 index f983bed9..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java +++ /dev/null @@ -1,360 +0,0 @@ -package com.stevesoltys.backup.transport.component.provider; - -import android.annotation.Nullable; -import android.app.backup.BackupDataOutput; -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; -import android.content.ContentResolver; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Base64; -import android.util.Log; - -import com.android.internal.util.Preconditions; -import com.stevesoltys.backup.security.CipherUtil; -import com.stevesoltys.backup.security.KeyGenerator; -import com.stevesoltys.backup.transport.component.RestoreComponent; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.crypto.SecretKey; - -import libcore.io.IoUtils; -import libcore.io.Streams; - -import static android.app.backup.BackupTransport.NO_MORE_DATA; -import static android.app.backup.BackupTransport.TRANSPORT_ERROR; -import static android.app.backup.BackupTransport.TRANSPORT_OK; -import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED; -import static android.app.backup.RestoreDescription.TYPE_FULL_STREAM; -import static android.app.backup.RestoreDescription.TYPE_KEY_VALUE; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY; -import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_INCREMENTAL_BACKUP_DIRECTORY; -import static java.util.Objects.requireNonNull; - -/** - * @author Steve Soltys - */ -public class ContentProviderRestoreComponent implements RestoreComponent { - - private static final String TAG = ContentProviderRestoreComponent.class.getName(); - - private static final int DEFAULT_RESTORE_SET = 1; - - private static final int DEFAULT_BUFFER_SIZE = 2048; - - @Nullable - private String password; - @Nullable - private Uri fileUri; - - private ContentProviderRestoreState restoreState; - - private final Context context; - - public ContentProviderRestoreComponent(Context context) { - this.context = context; - } - - @Override - public void prepareRestore(String password, Uri fileUri) { - this.password = password; - this.fileUri = fileUri; - } - - @Override - public int startRestore(long token, PackageInfo[] packages) { - restoreState = new ContentProviderRestoreState(); - restoreState.setPackages(packages); - restoreState.setPackageIndex(-1); - - String password = requireNonNull(this.password); - - if (!password.isEmpty()) { - try { - ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); - ZipInputStream inputStream = buildInputStream(inputFileDescriptor); - seekToEntry(inputStream, ContentProviderBackupConstants.SALT_FILE_PATH); - - restoreState.setSalt(Streams.readFullyNoClose(inputStream)); - restoreState.setSecretKey(KeyGenerator.generate(password, restoreState.getSalt())); - - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(inputStream); - - } catch (Exception ex) { - Log.e(TAG, "Salt not found", ex); - } - } - - try { - List<ZipEntry> zipEntries = new LinkedList<>(); - - ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); - ZipInputStream inputStream = buildInputStream(inputFileDescriptor); - - ZipEntry zipEntry; - while ((zipEntry = inputStream.getNextEntry()) != null) { - zipEntries.add(zipEntry); - inputStream.closeEntry(); - } - - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(inputStream); - - restoreState.setZipEntries(zipEntries); - - } catch (Exception ex) { - Log.e(TAG, "Error while caching zip entries", ex); - } - - return TRANSPORT_OK; - } - - @Override - public RestoreDescription nextRestorePackage() { - Preconditions.checkNotNull(restoreState, "startRestore() not called"); - - int packageIndex = restoreState.getPackageIndex(); - PackageInfo[] packages = restoreState.getPackages(); - - while (++packageIndex < packages.length) { - restoreState.setPackageIndex(packageIndex); - String name = packages[packageIndex].packageName; - - if (containsPackageFile(DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + name)) { - restoreState.setRestoreType(TYPE_KEY_VALUE); - return new RestoreDescription(name, restoreState.getRestoreType()); - - } else if (containsPackageFile(DEFAULT_FULL_BACKUP_DIRECTORY + name)) { - restoreState.setRestoreType(TYPE_FULL_STREAM); - return new RestoreDescription(name, restoreState.getRestoreType()); - } - } - return RestoreDescription.NO_MORE_PACKAGES; - } - - private boolean containsPackageFile(String fileName) { - return restoreState.getZipEntries().stream() - .anyMatch(zipEntry -> zipEntry.getName().startsWith(fileName)); - } - - @Override - public int getRestoreData(ParcelFileDescriptor outputFileDescriptor) { - Preconditions.checkState(restoreState != null, "startRestore() not called"); - Preconditions.checkState(restoreState.getPackageIndex() >= 0, "nextRestorePackage() not called"); - Preconditions.checkState(restoreState.getRestoreType() == TYPE_KEY_VALUE, - "getRestoreData() for non-key/value dataset"); - - PackageInfo packageInfo = restoreState.getPackages()[restoreState.getPackageIndex()]; - - try { - return transferIncrementalRestoreData(packageInfo.packageName, outputFileDescriptor); - - } catch (Exception ex) { - Log.e(TAG, "Unable to read backup records: ", ex); - return TRANSPORT_ERROR; - } - } - - private int transferIncrementalRestoreData(String packageName, ParcelFileDescriptor outputFileDescriptor) - throws Exception { - - ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); - ZipInputStream inputStream = buildInputStream(inputFileDescriptor); - BackupDataOutput backupDataOutput = new BackupDataOutput(outputFileDescriptor.getFileDescriptor()); - - Optional<ZipEntry> zipEntryOptional = seekToEntry(inputStream, - DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + packageName); - - while (zipEntryOptional.isPresent()) { - String fileName = new File(zipEntryOptional.get().getName()).getName(); - String blobKey = new String(Base64.decode(fileName, Base64.DEFAULT)); - - byte[] backupData = readBackupData(inputStream); - backupDataOutput.writeEntityHeader(blobKey, backupData.length); - backupDataOutput.writeEntityData(backupData, backupData.length); - inputStream.closeEntry(); - - zipEntryOptional = seekToEntry(inputStream, DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + packageName); - } - - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(outputFileDescriptor); - return TRANSPORT_OK; - } - - private byte[] readBackupData(ZipInputStream inputStream) throws Exception { - byte[] backupData = Streams.readFullyNoClose(inputStream); - SecretKey secretKey = restoreState.getSecretKey(); - byte[] initializationVector = restoreState.getSalt(); - - if (secretKey != null) { - backupData = CipherUtil.decrypt(backupData, secretKey, initializationVector); - } - - return backupData; - } - - @Override - public int getNextFullRestoreDataChunk(ParcelFileDescriptor outputFileDescriptor) { - Preconditions.checkState(restoreState.getRestoreType() == TYPE_FULL_STREAM, - "Asked for full restore data for non-stream package"); - - ParcelFileDescriptor inputFileDescriptor = restoreState.getInputFileDescriptor(); - - if (inputFileDescriptor == null) { - String name = restoreState.getPackages()[restoreState.getPackageIndex()].packageName; - - try { - inputFileDescriptor = buildInputFileDescriptor(); - restoreState.setInputFileDescriptor(inputFileDescriptor); - - ZipInputStream inputStream = buildInputStream(inputFileDescriptor); - restoreState.setInputStream(inputStream); - - if (!seekToEntry(inputStream, DEFAULT_FULL_BACKUP_DIRECTORY + name).isPresent()) { - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(outputFileDescriptor); - return TRANSPORT_PACKAGE_REJECTED; - } - - } catch (IOException ex) { - Log.e(TAG, "Unable to read archive for " + name, ex); - - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(outputFileDescriptor); - return TRANSPORT_PACKAGE_REJECTED; - } - } - - return transferFullRestoreData(outputFileDescriptor); - } - - private int transferFullRestoreData(ParcelFileDescriptor outputFileDescriptor) { - ZipInputStream inputStream = restoreState.getInputStream(); - OutputStream outputStream = new FileOutputStream(outputFileDescriptor.getFileDescriptor()); - - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - int bytesRead = NO_MORE_DATA; - - try { - bytesRead = inputStream.read(buffer); - - if (bytesRead <= 0) { - bytesRead = NO_MORE_DATA; - - if (restoreState.getCipher() != null) { - buffer = restoreState.getCipher().doFinal(); - bytesRead = buffer.length; - - outputStream.write(buffer, 0, bytesRead); - restoreState.setCipher(null); - } - - } else { - if (restoreState.getSecretKey() != null) { - SecretKey secretKey = restoreState.getSecretKey(); - byte[] salt = restoreState.getSalt(); - - if (restoreState.getCipher() == null) { - restoreState.setCipher(CipherUtil.startDecrypt(secretKey, salt)); - } - - buffer = restoreState.getCipher().update(Arrays.copyOfRange(buffer, 0, bytesRead)); - bytesRead = buffer.length; - } - - outputStream.write(buffer, 0, bytesRead); - } - - } catch (Exception e) { - Log.e(TAG, "Exception while streaming restore data: ", e); - return TRANSPORT_ERROR; - - } finally { - if (bytesRead == NO_MORE_DATA) { - - if (restoreState.getInputFileDescriptor() != null) { - IoUtils.closeQuietly(restoreState.getInputFileDescriptor()); - } - - restoreState.setInputFileDescriptor(null); - restoreState.setInputStream(null); - } - - IoUtils.closeQuietly(outputFileDescriptor); - } - - return bytesRead; - } - - @Override - public int abortFullRestore() { - resetFullRestoreState(); - return TRANSPORT_OK; - } - - @Override - public long getCurrentRestoreSet() { - return DEFAULT_RESTORE_SET; - } - - @Override - public void finishRestore() { - if (restoreState.getRestoreType() == TYPE_FULL_STREAM) { - resetFullRestoreState(); - } - - restoreState = null; - } - - @Override - public RestoreSet[] getAvailableRestoreSets() { - return new RestoreSet[]{new RestoreSet("Local disk image", "flash", DEFAULT_RESTORE_SET)}; - } - - private void resetFullRestoreState() { - Preconditions.checkNotNull(restoreState); - Preconditions.checkState(restoreState.getRestoreType() == TYPE_FULL_STREAM); - - IoUtils.closeQuietly(restoreState.getInputFileDescriptor()); - restoreState = null; - } - - private ParcelFileDescriptor buildInputFileDescriptor() throws FileNotFoundException { - ContentResolver contentResolver = context.getContentResolver(); - return contentResolver.openFileDescriptor(requireNonNull(fileUri), "r"); - } - - private ZipInputStream buildInputStream(ParcelFileDescriptor inputFileDescriptor) { - FileInputStream fileInputStream = new FileInputStream(inputFileDescriptor.getFileDescriptor()); - return new ZipInputStream(fileInputStream); - } - - private Optional<ZipEntry> seekToEntry(ZipInputStream inputStream, String entryPath) throws IOException { - ZipEntry zipEntry; - while ((zipEntry = inputStream.getNextEntry()) != null) { - - if (zipEntry.getName().startsWith(entryPath)) { - return Optional.of(zipEntry); - } - inputStream.closeEntry(); - } - - return Optional.empty(); - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java deleted file mode 100644 index a7b6e3c6..00000000 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.stevesoltys.backup.transport.component.provider; - -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * @author Steve Soltys - */ -class ContentProviderRestoreState { - - private ParcelFileDescriptor inputFileDescriptor; - - private PackageInfo[] packages; - - private int packageIndex; - - private int restoreType; - - private ZipInputStream inputStream; - - private Cipher cipher; - - private byte[] salt; - - private SecretKey secretKey; - - private List<ZipEntry> zipEntries; - - Cipher getCipher() { - return cipher; - } - - ParcelFileDescriptor getInputFileDescriptor() { - return inputFileDescriptor; - } - - void setCipher(Cipher cipher) { - this.cipher = cipher; - } - - void setInputFileDescriptor(ParcelFileDescriptor inputFileDescriptor) { - this.inputFileDescriptor = inputFileDescriptor; - } - - ZipInputStream getInputStream() { - return inputStream; - } - - void setInputStream(ZipInputStream inputStream) { - this.inputStream = inputStream; - } - - int getPackageIndex() { - return packageIndex; - } - - void setPackageIndex(int packageIndex) { - this.packageIndex = packageIndex; - } - - PackageInfo[] getPackages() { - return packages; - } - - void setPackages(PackageInfo[] packages) { - this.packages = packages; - } - - int getRestoreType() { - return restoreType; - } - - void setRestoreType(int restoreType) { - this.restoreType = restoreType; - } - - byte[] getSalt() { - return salt; - } - - void setSalt(byte[] salt) { - this.salt = salt; - } - - public SecretKey getSecretKey() { - return secretKey; - } - - public void setSecretKey(SecretKey secretKey) { - this.secretKey = secretKey; - } - - public List<ZipEntry> getZipEntries() { - return zipEntries; - } - - public void setZipEntries(List<ZipEntry> zipEntries) { - this.zipEntries = zipEntries; - } -} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestore.kt new file mode 100644 index 00000000..66588567 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestore.kt @@ -0,0 +1,172 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupTransport.* +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import com.stevesoltys.backup.crypto.Crypto +import com.stevesoltys.backup.header.HeaderReader +import com.stevesoltys.backup.header.UnsupportedVersionException +import libcore.io.IoUtils.closeQuietly +import java.io.EOFException +import java.io.IOException +import java.io.InputStream + +private class FullRestoreState( + internal val token: Long, + internal val packageInfo: PackageInfo) { + internal var inputStream: InputStream? = null +} + +private val TAG = FullRestore::class.java.simpleName + +internal class FullRestore( + private val plugin: FullRestorePlugin, + private val outputFactory: OutputFactory, + private val headerReader: HeaderReader, + private val crypto: Crypto) { + + private var state: FullRestoreState? = null + + fun hasState() = state != null + + /** + * Return true if there is data stored for the given package. + */ + @Throws(IOException::class) + fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean { + return plugin.hasDataForPackage(token, packageInfo) + } + + /** + * This prepares to restore the given package from the given restore token. + * + * It is possible that the system decides to not restore the package. + * Then a new state will be initialized right away without calling other methods. + */ + fun initializeState(token: Long, packageInfo: PackageInfo) { + state = FullRestoreState(token, packageInfo) + } + + /** + * Ask the transport to provide data for the "current" package being restored. + * + * The transport writes some data to the socket supplied to this call, + * and returns the number of bytes written. + * The system will then read that many bytes + * and stream them to the application's agent for restore, + * then will call this method again to receive the next chunk of the archive. + * This sequence will be repeated until the transport returns zero + * indicating that all of the package's data has been delivered + * (or returns a negative value indicating a hard error condition at the transport level). + * + * The transport should always close this socket when returning from this method. + * Do not cache this socket across multiple calls or you may leak file descriptors. + * + * @param socket The file descriptor for delivering the streamed archive. + * The transport must close this socket in all cases when returning from this method. + * @return [NO_MORE_DATA] when no more data for the current package is available. + * A positive value indicates the presence of that many bytes to be delivered to the app. + * A value of zero indicates that no data was deliverable at this time, + * but the restore is still running and the caller should retry. + * [TRANSPORT_PACKAGE_REJECTED] means that the package's restore operation should be aborted, + * but that the transport itself is still in a good state + * and so a multiple-package restore sequence can still be continued. + * Any other negative value such as [TRANSPORT_ERROR] is treated as a fatal error condition + * that aborts all further restore operations on the current dataset. + */ + fun getNextFullRestoreDataChunk(socket: ParcelFileDescriptor): Int { + Log.i(TAG, "Get next full restore data chunk.") + val state = this.state ?: throw IllegalStateException() + val packageName = state.packageInfo.packageName + + if (state.inputStream == null) { + Log.i(TAG, "First Chunk, initializing package input stream.") + try { + val inputStream = plugin.getInputStreamForPackage(state.token, state.packageInfo) + val version = headerReader.readVersion(inputStream) + crypto.decryptHeader(inputStream, version, packageName) + state.inputStream = inputStream + } catch (e: IOException) { + Log.w(TAG, "Error getting input stream for $packageName", e) + return TRANSPORT_PACKAGE_REJECTED + } catch (e: SecurityException) { + Log.e(TAG, "Security Exception while getting input stream for $packageName", e) + return TRANSPORT_ERROR + } catch (e: UnsupportedVersionException) { + Log.e(TAG, "Backup data for $packageName uses unsupported version ${e.version}.", e) + return TRANSPORT_PACKAGE_REJECTED + } + } + + return readInputStream(socket) + } + + private fun readInputStream(socket: ParcelFileDescriptor): Int = socket.use { fileDescriptor -> + val state = this.state ?: throw IllegalStateException() + val packageName = state.packageInfo.packageName + val inputStream = state.inputStream ?: throw IllegalStateException() + val outputStream = outputFactory.getOutputStream(fileDescriptor) + + try { + // read segment from input stream and decrypt it + val decrypted = try { + crypto.decryptSegment(inputStream) + } catch (e: EOFException) { + Log.i(TAG, " EOF") + // close input stream here as we won't need it anymore + closeQuietly(inputStream) + return NO_MORE_DATA + } + + // write decrypted segment to output stream (without header) + outputStream.write(decrypted) + // return number of written bytes + return decrypted.size + } catch (e: IOException) { + Log.w(TAG, "Error processing stream for package $packageName.", e) + closeQuietly(inputStream) + return TRANSPORT_PACKAGE_REJECTED + } finally { + closeQuietly(outputStream) + } + } + + /** + * If the OS encounters an error while processing full data for restore, + * it will invoke this method + * to tell the transport that it should abandon the data download for the current package. + * + * @return [TRANSPORT_OK] if the transport shut down the current stream cleanly, + * or [TRANSPORT_ERROR] to indicate a serious transport-level failure. + * If the transport reports an error here, + * the entire restore operation will immediately be finished + * with no further attempts to restore app data. + */ + fun abortFullRestore(): Int { + val state = this.state ?: throw IllegalStateException() + Log.i(TAG, "Abort full restore of ${state.packageInfo.packageName}!") + + resetState() + return TRANSPORT_OK + } + + /** + * End a restore session (aborting any in-process data transfer as necessary), + * freeing any resources and connections used during the restore process. + */ + fun finishRestore() { + val state = this.state ?: throw IllegalStateException() + Log.i(TAG, "Finish restore of ${state.packageInfo.packageName}!") + + resetState() + } + + private fun resetState() { + Log.i(TAG, "Resetting state.") + + closeQuietly(state?.inputStream) + state = null + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestorePlugin.kt new file mode 100644 index 00000000..9281d76c --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/FullRestorePlugin.kt @@ -0,0 +1,18 @@ +package com.stevesoltys.backup.transport.restore + +import android.content.pm.PackageInfo +import java.io.IOException +import java.io.InputStream + +interface FullRestorePlugin { + + /** + * Return true if there is data stored for the given package. + */ + @Throws(IOException::class) + fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean + + @Throws(IOException::class) + fun getInputStreamForPackage(token: Long, packageInfo: PackageInfo): InputStream + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestore.kt new file mode 100644 index 00000000..67f6b1a5 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestore.kt @@ -0,0 +1,140 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupDataOutput +import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_OK +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import com.stevesoltys.backup.crypto.Crypto +import com.stevesoltys.backup.header.HeaderReader +import com.stevesoltys.backup.header.UnsupportedVersionException +import libcore.io.IoUtils.closeQuietly +import java.io.IOException +import java.util.* +import java.util.Base64.getUrlDecoder + +private class KVRestoreState( + internal val token: Long, + internal val packageInfo: PackageInfo) + +private val TAG = KVRestore::class.java.simpleName + +internal class KVRestore( + private val plugin: KVRestorePlugin, + private val outputFactory: OutputFactory, + private val headerReader: HeaderReader, + private val crypto: Crypto) { + + private var state: KVRestoreState? = null + + /** + * Return true if there are records stored for the given package. + */ + @Throws(IOException::class) + fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean { + return plugin.hasDataForPackage(token, packageInfo) + } + + /** + * This prepares to restore the given package from the given restore token. + * + * It is possible that the system decides to not restore the package. + * Then a new state will be initialized right away without calling other methods. + */ + fun initializeState(token: Long, packageInfo: PackageInfo) { + state = KVRestoreState(token, packageInfo) + } + + /** + * Get the data for the current package. + * + * @param data An open, writable file into which the key/value backup data should be stored. + * @return One of [TRANSPORT_OK] + * or [TRANSPORT_ERROR] (an error occurred, the restore should be aborted and rescheduled). + */ + fun getRestoreData(data: ParcelFileDescriptor): Int { + val state = this.state ?: throw IllegalStateException() + + // 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) + if (sortedKeys == null) { + // nextRestorePackage() ensures the dir exists, so this is an error + Log.e(TAG, "No keys for package: ${state.packageInfo.packageName}") + return TRANSPORT_ERROR + } + + // We expect at least some data if the directory exists in the first place + Log.v(TAG, " getRestoreData() found ${sortedKeys.size} key files") + + return try { + val dataOutput = outputFactory.getBackupDataOutput(data) + for (keyEntry in sortedKeys) { + readAndWriteValue(state, keyEntry, dataOutput) + } + TRANSPORT_OK + } catch (e: IOException) { + Log.e(TAG, "Unable to read backup records", e) + TRANSPORT_ERROR + } catch (e: SecurityException) { + Log.e(TAG, "Security exception while reading backup records", e) + TRANSPORT_ERROR + } catch (e: UnsupportedVersionException) { + Log.e(TAG, "Unsupported version in backup: ${e.version}", e) + TRANSPORT_ERROR + } finally { + this.state = null + closeQuietly(data) + } + } + + /** + * 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 fun getSortedKeys(token: Long, packageInfo: PackageInfo): List<DecodedKey>? { + val records: List<String> = try { + plugin.listRecords(token, packageInfo) + } catch (e: IOException) { + return null + } + if (records.isEmpty()) return null + + // Decode the key filenames into keys then sort lexically by key + val contents = ArrayList<DecodedKey>() + for (recordKey in records) contents.add(DecodedKey(recordKey)) + contents.sort() + return contents + } + + /** + * Read the encrypted value for the given key and write it to the given [BackupDataOutput]. + */ + @Throws(IOException::class, UnsupportedVersionException::class, SecurityException::class) + private fun readAndWriteValue(state: KVRestoreState, dKey: DecodedKey, out: BackupDataOutput) { + val inputStream = plugin.getInputStreamForRecord(state.token, state.packageInfo, dKey.base64Key) + try { + val version = headerReader.readVersion(inputStream) + crypto.decryptHeader(inputStream, version, state.packageInfo.packageName, dKey.key) + val value = crypto.decryptSegment(inputStream) + val size = value.size + Log.v(TAG, " ... key=${dKey.key} size=$size") + + out.writeEntityHeader(dKey.key, size) + out.writeEntityData(value, size) + } finally { + closeQuietly(inputStream) + } + } + + private class DecodedKey(internal val base64Key: String) : Comparable<DecodedKey> { + internal val key = String(getUrlDecoder().decode(base64Key)) + + override fun compareTo(other: DecodedKey) = key.compareTo(other.key) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestorePlugin.kt new file mode 100644 index 00000000..e425752c --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/KVRestorePlugin.kt @@ -0,0 +1,30 @@ +package com.stevesoltys.backup.transport.restore + +import android.content.pm.PackageInfo +import java.io.IOException +import java.io.InputStream + +interface KVRestorePlugin { + + /** + * Return true if there is data stored for the given package. + */ + @Throws(IOException::class) + fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean + + /** + * Return all record keys for the given token and package. + * + * For file-based plugins, this is usually a list of file names in the package directory. + */ + @Throws(IOException::class) + fun listRecords(token: Long, packageInfo: PackageInfo): List<String> + + /** + * Return an [InputStream] for the given token, package and key + * which will provide the record's encrypted value. + */ + @Throws(IOException::class) + fun getInputStreamForRecord(token: Long, packageInfo: PackageInfo, key: String): InputStream + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/OutputFactory.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/OutputFactory.kt new file mode 100644 index 00000000..16162bb6 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/OutputFactory.kt @@ -0,0 +1,21 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupDataOutput +import android.os.ParcelFileDescriptor +import java.io.FileOutputStream +import java.io.OutputStream + +/** + * This class exists for easier testing, so we can mock it and return custom data outputs. + */ +class OutputFactory { + + fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput { + return BackupDataOutput(outputFileDescriptor.fileDescriptor) + } + + fun getOutputStream(outputFileDescriptor: ParcelFileDescriptor): OutputStream { + return FileOutputStream(outputFileDescriptor.fileDescriptor) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/RestoreCoordinator.kt new file mode 100644 index 00000000..d6dc397a --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/RestoreCoordinator.kt @@ -0,0 +1,155 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_OK +import android.app.backup.RestoreDescription +import android.app.backup.RestoreDescription.* +import android.app.backup.RestoreSet +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import android.util.Log +import java.io.IOException + +private class RestoreCoordinatorState( + internal val token: Long, + internal val packages: Iterator<PackageInfo>) + +private val TAG = RestoreCoordinator::class.java.simpleName + +internal class RestoreCoordinator( + private val plugin: RestorePlugin, + private val kv: KVRestore, + private val full: FullRestore) { + + private var state: RestoreCoordinatorState? = null + + fun getAvailableRestoreSets(): Array<RestoreSet>? { + return plugin.getAvailableRestoreSets() + .apply { Log.i(TAG, "Got available restore sets: $this") } + } + + fun getCurrentRestoreSet(): Long { + return plugin.getCurrentRestoreSet() + .apply { Log.i(TAG, "Got current restore set token: $this") } + } + + /** + * Start restoring application data from backup. + * After calling this function, + * there will be alternate calls to [nextRestorePackage] and [getRestoreData] + * to walk through the actual application data. + * + * @param token A backup token as returned by [getAvailableRestoreSets] or [getCurrentRestoreSet]. + * @param packages List of applications to restore (if data is available). + * Application data will be restored in the order given. + * @return One of [TRANSPORT_OK] (OK so far, call [nextRestorePackage]) + * or [TRANSPORT_ERROR] (an error occurred, the restore should be aborted and rescheduled). + */ + fun startRestore(token: Long, packages: Array<out PackageInfo>): Int { + if (state != null) throw IllegalStateException() + Log.i(TAG, "Start restore with ${packages.map { info -> info.packageName }}") + state = RestoreCoordinatorState(token, packages.iterator()) + return TRANSPORT_OK + } + + /** + * Get the package name of the next package with data in the backup store, + * plus a description of the structure of the restored archive: + * either [TYPE_KEY_VALUE] for an original-API key/value dataset, + * or [TYPE_FULL_STREAM] for a tarball-type archive stream. + * + * If the package name in the returned [RestoreDescription] object is [NO_MORE_PACKAGES], + * it indicates that no further data is available in the current restore session, + * i.e. all packages described in [startRestore] have been processed. + * + * If this method returns null, it means that a transport-level error has + * occurred and the entire restore operation should be abandoned. + * + * The OS may call [nextRestorePackage] multiple times + * before calling either [getRestoreData] or [getNextFullRestoreDataChunk]. + * It does this when it has determined + * that it needs to skip restore of one or more packages. + * The transport should not actually transfer any restore data + * for the given package in response to [nextRestorePackage], + * but rather wait for an explicit request before doing so. + * + * @return A [RestoreDescription] object containing the name of one of the packages + * supplied to [startRestore] plus an indicator of the data type of that restore data; + * or [NO_MORE_PACKAGES] to indicate that no more packages can be restored in this session; + * or null to indicate a transport-level error. + */ + fun nextRestorePackage(): RestoreDescription? { + Log.i(TAG, "Next restore package!") + val state = this.state ?: throw IllegalStateException() + + if (!state.packages.hasNext()) return NO_MORE_PACKAGES + val packageInfo = state.packages.next() + 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(state.token, packageInfo) + TYPE_KEY_VALUE + } + full.hasDataForPackage(state.token, packageInfo) -> { + Log.i(TAG, "Found full backup data for $packageName.") + full.initializeState(state.token, packageInfo) + TYPE_FULL_STREAM + } + else -> { + Log.i(TAG, "No data found for $packageName. Skipping.") + return nextRestorePackage() + } + } + } catch (e: IOException) { + Log.e(TAG, "Error finding restore data for $packageName.", e) + return null + } + return RestoreDescription(packageName, type) + } + + /** + * Get the data for the application returned by [nextRestorePackage], + * if that method reported [TYPE_KEY_VALUE] as its delivery type. + * If the package has only TYPE_FULL_STREAM data, then this method will return an error. + * + * @param data An open, writable file into which the key/value backup data should be stored. + * @return the same error codes as [startRestore]. + */ + fun getRestoreData(data: ParcelFileDescriptor): Int { + return kv.getRestoreData(data) + } + + /** + * Ask the transport to provide data for the "current" package being restored. + * + * After this method returns zero, the system will then call [nextRestorePackage] + * to begin the restore process for the next application, and the sequence begins again. + */ + fun getNextFullRestoreDataChunk(outputFileDescriptor: ParcelFileDescriptor): Int { + return full.getNextFullRestoreDataChunk(outputFileDescriptor) + } + + /** + * If the OS encounters an error while processing full data for restore, it will abort. + * + * The OS will then either call [nextRestorePackage] again to move on + * to restoring the next package in the set being iterated over, + * or will call [finishRestore] to shut down the restore operation. + */ + fun abortFullRestore(): Int { + return full.abortFullRestore() + } + + /** + * End a restore session (aborting any in-process data transfer as necessary), + * freeing any resources and connections used during the restore process. + */ + fun finishRestore() { + if (full.hasState()) full.finishRestore() + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/RestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/RestorePlugin.kt new file mode 100644 index 00000000..17f4f0ad --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/RestorePlugin.kt @@ -0,0 +1,28 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.RestoreSet + +interface RestorePlugin { + + val kvRestorePlugin: KVRestorePlugin + + val fullRestorePlugin: FullRestorePlugin + + /** + * Get the set of all backups currently available for restore. + * + * @return Descriptions of the set of restore images available for this device, + * or null if an error occurred (the attempt should be rescheduled). + **/ + fun getAvailableRestoreSets(): Array<RestoreSet>? + + /** + * Get the identifying token of the backup set currently being stored from this device. + * This is used in the case of applications wishing to restore their last-known-good data. + * + * @return A token that can be used for restore, + * or 0 if there is no backup set available corresponding to the current device state. + */ + fun getCurrentRestoreSet(): Long + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt new file mode 100644 index 00000000..83825a06 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt @@ -0,0 +1,25 @@ +package com.stevesoltys.backup.transport.restore.plugins + +import android.content.pm.PackageInfo +import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage +import com.stevesoltys.backup.transport.restore.FullRestorePlugin +import java.io.IOException +import java.io.InputStream + +class DocumentsProviderFullRestorePlugin( + private val documentsStorage: DocumentsStorage) : FullRestorePlugin { + + @Throws(IOException::class) + override fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean { + val backupDir = documentsStorage.getFullBackupDir(token) ?: throw IOException() + return backupDir.findFile(packageInfo.packageName) != null + } + + @Throws(IOException::class) + override fun getInputStreamForPackage(token: Long, packageInfo: PackageInfo): InputStream { + val backupDir = documentsStorage.getFullBackupDir(token) ?: throw IOException() + val packageFile = backupDir.findFile(packageInfo.packageName) ?: throw IOException() + return documentsStorage.getInputStream(packageFile) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt new file mode 100644 index 00000000..d24b1bd1 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt @@ -0,0 +1,42 @@ +package com.stevesoltys.backup.transport.restore.plugins + +import android.content.pm.PackageInfo +import androidx.documentfile.provider.DocumentFile +import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage +import com.stevesoltys.backup.transport.backup.plugins.assertRightFile +import com.stevesoltys.backup.transport.restore.KVRestorePlugin +import java.io.IOException +import java.io.InputStream + +class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin { + + private var packageDir: DocumentFile? = null + + override fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean { + return try { + val backupDir = storage.getKVBackupDir(token) ?: return false + // remember package file for subsequent operations + packageDir = backupDir.findFile(packageInfo.packageName) + packageDir != null + } catch (e: IOException) { + false + } + } + + override fun listRecords(token: Long, packageInfo: PackageInfo): List<String> { + val packageDir = this.packageDir ?: throw AssertionError() + packageDir.assertRightFile(packageInfo) + return packageDir.listFiles() + .filter { file -> file.name != null } + .map { file -> file.name!! } + } + + @Throws(IOException::class) + override fun getInputStreamForRecord(token: Long, packageInfo: PackageInfo, key: String): InputStream { + val packageDir = this.packageDir ?: throw AssertionError() + packageDir.assertRightFile(packageInfo) + val keyFile = packageDir.findFile(key) ?: throw IOException() + return storage.getInputStream(keyFile) + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderRestorePlugin.kt b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderRestorePlugin.kt new file mode 100644 index 00000000..deb9e327 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/transport/restore/plugins/DocumentsProviderRestorePlugin.kt @@ -0,0 +1,29 @@ +package com.stevesoltys.backup.transport.restore.plugins + +import android.app.backup.RestoreSet +import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN +import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage +import com.stevesoltys.backup.transport.restore.FullRestorePlugin +import com.stevesoltys.backup.transport.restore.KVRestorePlugin +import com.stevesoltys.backup.transport.restore.RestorePlugin + +class DocumentsProviderRestorePlugin( + private val documentsStorage: DocumentsStorage) : RestorePlugin { + + override val kvRestorePlugin: KVRestorePlugin by lazy { + DocumentsProviderKVRestorePlugin(documentsStorage) + } + + override val fullRestorePlugin: FullRestorePlugin by lazy { + DocumentsProviderFullRestorePlugin(documentsStorage) + } + + override fun getAvailableRestoreSets(): Array<RestoreSet>? { + return arrayOf(RestoreSet("default", "device", DEFAULT_RESTORE_SET_TOKEN)) + } + + override fun getCurrentRestoreSet(): Long { + return DEFAULT_RESTORE_SET_TOKEN + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c8e82e3..e06e4f21 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,7 @@ <!-- Settings --> <string name="settings_backup">Backup my data</string> <string name="settings_backup_location">Backup location</string> + <string name="settings_backup_location_picker">Choose backup location</string> <string name="settings_backup_location_title">Backup Location</string> <string name="settings_backup_location_info">Choose where to store your backups. More options might get added in the future.</string> <string name="settings_backup_external_storage">External Storage</string> diff --git a/app/src/test/java/com/stevesoltys/backup/TestUtils.kt b/app/src/test/java/com/stevesoltys/backup/TestUtils.kt new file mode 100644 index 00000000..6644f3b1 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/TestUtils.kt @@ -0,0 +1,38 @@ +package com.stevesoltys.backup + +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.fail +import kotlin.random.Random + +fun assertContains(stack: String?, needle: String) { + assertTrue(stack?.contains(needle) ?: fail()) +} + +fun getRandomByteArray(size: Int = Random.nextInt(1337)) = ByteArray(size).apply { + Random.nextBytes(this) +} + +private val charPool : List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') + '_' + '.' + +fun getRandomString(size: Int = Random.nextInt(1, 255)): String { + return (1..size) + .map { Random.nextInt(0, charPool.size) } + .map(charPool::get) + .joinToString("") +} + +fun ByteArray.toHexString(): String { + var str = "" + for (b in this) { + str += String.format("%02X ", b) + } + return str +} + +fun ByteArray.toIntString(): String { + var str = "" + for (b in this) { + str += String.format("%02d ", b) + } + return str +} diff --git a/app/src/test/java/com/stevesoltys/backup/crypto/CryptoImplTest.kt b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoImplTest.kt new file mode 100644 index 00000000..e606bb67 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoImplTest.kt @@ -0,0 +1,53 @@ +package com.stevesoltys.backup.crypto + +import com.stevesoltys.backup.header.HeaderReaderImpl +import com.stevesoltys.backup.header.HeaderWriterImpl +import com.stevesoltys.backup.header.IV_SIZE +import com.stevesoltys.backup.header.MAX_SEGMENT_LENGTH +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import javax.crypto.Cipher +import kotlin.random.Random + +@TestInstance(PER_METHOD) +class CryptoImplTest { + + private val cipherFactory = mockk<CipherFactory>() + private val headerWriter = HeaderWriterImpl() + private val headerReader = HeaderReaderImpl() + + private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader) + + private val cipher = mockk<Cipher>() + + private val iv = ByteArray(IV_SIZE).apply { Random.nextBytes(this) } + private val cleartext = ByteArray(Random.nextInt(Short.MAX_VALUE.toInt())) + .apply { Random.nextBytes(this) } + private val ciphertext = ByteArray(Random.nextInt(Short.MAX_VALUE.toInt())) + .apply { Random.nextBytes(this) } + private val outputStream = ByteArrayOutputStream() + + @Test + fun `encrypted cleartext gets decrypted as expected`() { + every { cipherFactory.createEncryptionCipher() } returns cipher + every { cipher.getOutputSize(cleartext.size) } returns MAX_SEGMENT_LENGTH + every { cipher.doFinal(cleartext) } returns ciphertext + every { cipher.iv } returns iv + + crypto.encryptSegment(outputStream, cleartext) + + val inputStream = ByteArrayInputStream(outputStream.toByteArray()) + + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(ciphertext) } returns cleartext + + assertArrayEquals(cleartext, crypto.decryptSegment(inputStream)) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/crypto/CryptoIntegrationTest.kt b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoIntegrationTest.kt new file mode 100644 index 00000000..a48d31e8 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoIntegrationTest.kt @@ -0,0 +1,44 @@ +package com.stevesoltys.backup.crypto + +import com.stevesoltys.backup.header.HeaderReaderImpl +import com.stevesoltys.backup.header.HeaderWriterImpl +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +@TestInstance(PER_METHOD) +class CryptoIntegrationTest { + + private val keyManager = KeyManagerTestImpl() + private val cipherFactory = CipherFactoryImpl(keyManager) + private val headerWriter = HeaderWriterImpl() + private val headerReader = HeaderReaderImpl() + + private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader) + + private val cleartext = byteArrayOf(0x01, 0x02, 0x03) + + private val outputStream = ByteArrayOutputStream() + + @Test + fun `the plain crypto works`() { + val eCipher = cipherFactory.createEncryptionCipher() + val encrypted = eCipher.doFinal(cleartext) + + val dCipher = cipherFactory.createDecryptionCipher(eCipher.iv) + val decrypted = dCipher.doFinal(encrypted) + + assertArrayEquals(cleartext, decrypted) + } + + @Test + fun `encrypted cleartext gets decrypted as expected`() { + crypto.encryptSegment(outputStream, cleartext) + val inputStream = ByteArrayInputStream(outputStream.toByteArray()) + assertArrayEquals(cleartext, crypto.decryptSegment(inputStream)) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/crypto/CryptoTest.kt b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoTest.kt new file mode 100644 index 00000000..e1d129dd --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/crypto/CryptoTest.kt @@ -0,0 +1,192 @@ +package com.stevesoltys.backup.crypto + +import com.stevesoltys.backup.assertContains +import com.stevesoltys.backup.getRandomByteArray +import com.stevesoltys.backup.getRandomString +import com.stevesoltys.backup.header.* +import io.mockk.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD +import java.io.* +import javax.crypto.Cipher +import kotlin.random.Random + +@TestInstance(PER_METHOD) +class CryptoTest { + + private val cipherFactory = mockk<CipherFactory>() + private val headerWriter = mockk<HeaderWriter>() + private val headerReader = mockk<HeaderReader>() + + private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader) + + private val cipher = mockk<Cipher>() + + private val iv = getRandomByteArray(IV_SIZE) + private val cleartext = getRandomByteArray(Random.nextInt(MAX_SEGMENT_LENGTH)) + private val ciphertext = getRandomByteArray(Random.nextInt(MAX_SEGMENT_LENGTH)) + private val versionHeader = VersionHeader(VERSION, getRandomString(MAX_PACKAGE_LENGTH_SIZE), getRandomString(MAX_KEY_LENGTH_SIZE)) + private val versionCiphertext = getRandomByteArray(MAX_VERSION_HEADER_SIZE) + private val versionSegmentHeader = SegmentHeader(versionCiphertext.size.toShort(), iv) + private val outputStream = ByteArrayOutputStream() + private val segmentHeader = SegmentHeader(ciphertext.size.toShort(), iv) + // the headerReader will not actually read the header, so only insert cipher text + private val inputStream = ByteArrayInputStream(ciphertext) + private val versionInputStream = ByteArrayInputStream(versionCiphertext) + + // encrypting + + @Test + fun `encrypt header works as expected`() { + val segmentHeader = CapturingSlot<SegmentHeader>() + every { headerWriter.getEncodedVersionHeader(versionHeader) } returns ciphertext + encryptSegmentHeader(ciphertext, segmentHeader) + + crypto.encryptHeader(outputStream, versionHeader) + assertArrayEquals(iv, segmentHeader.captured.nonce) + assertEquals(ciphertext.size, segmentHeader.captured.segmentLength.toInt()) + } + + @Test + fun `encrypting segment works as expected`() { + val segmentHeader = CapturingSlot<SegmentHeader>() + encryptSegmentHeader(cleartext, segmentHeader) + + crypto.encryptSegment(outputStream, cleartext) + + assertArrayEquals(ciphertext, outputStream.toByteArray()) + assertArrayEquals(iv, segmentHeader.captured.nonce) + assertEquals(ciphertext.size, segmentHeader.captured.segmentLength.toInt()) + } + + private fun encryptSegmentHeader(toEncrypt: ByteArray, segmentHeader: CapturingSlot<SegmentHeader>) { + every { cipherFactory.createEncryptionCipher() } returns cipher + every { cipher.getOutputSize(toEncrypt.size) } returns toEncrypt.size + every { cipher.iv } returns iv + every { headerWriter.writeSegmentHeader(outputStream, capture(segmentHeader)) } just Runs + every { cipher.doFinal(toEncrypt) } returns ciphertext + } + + // decrypting + + @Test + fun `decrypting header works as expected`() { + every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(versionCiphertext) } returns cleartext + every { headerReader.getVersionHeader(cleartext) } returns versionHeader + + assertEquals( + versionHeader, + crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, versionHeader.key) + ) + } + + @Test + fun `decrypting header throws if too large`() { + val size = MAX_VERSION_HEADER_SIZE + 1 + val versionCiphertext = getRandomByteArray(size) + val versionInputStream = ByteArrayInputStream(versionCiphertext) + val versionSegmentHeader = SegmentHeader(size.toShort(), iv) + + every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + + val e = assertThrows(SecurityException::class.java) { + crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, versionHeader.key) + } + assertContains(e.message, size.toString()) + } + + @Test + fun `decrypting header throws because of different version`() { + every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(versionCiphertext) } returns cleartext + every { headerReader.getVersionHeader(cleartext) } returns versionHeader + + val version = (VERSION + 1).toByte() + val e = assertThrows(SecurityException::class.java) { + crypto.decryptHeader(versionInputStream, version, versionHeader.packageName, versionHeader.key) + } + assertContains(e.message, version.toString()) + } + + @Test + fun `decrypting header throws because of different package name`() { + every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(versionCiphertext) } returns cleartext + every { headerReader.getVersionHeader(cleartext) } returns versionHeader + + val packageName = getRandomString(MAX_PACKAGE_LENGTH_SIZE) + val e = assertThrows(SecurityException::class.java) { + crypto.decryptHeader(versionInputStream, versionHeader.version, packageName, versionHeader.key) + } + assertContains(e.message, packageName) + } + + @Test + fun `decrypting header throws because of different key`() { + every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(versionCiphertext) } returns cleartext + every { headerReader.getVersionHeader(cleartext) } returns versionHeader + + val e = assertThrows(SecurityException::class.java) { + crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, null) + } + assertContains(e.message, "null") + assertContains(e.message, versionHeader.key ?: fail()) + } + + @Test + fun `decrypting data segment header works as expected`() { + every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { cipherFactory.createDecryptionCipher(iv) } returns cipher + every { cipher.doFinal(ciphertext) } returns cleartext + + assertArrayEquals(cleartext, crypto.decryptSegment(inputStream)) + } + + @Test + fun `decrypting data segment throws if reading 0 bytes`() { + val inputStream = mockk<InputStream>() + val buffer = ByteArray(segmentHeader.segmentLength.toInt()) + + every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { inputStream.read(buffer) } returns 0 + + assertThrows(IOException::class.java) { + crypto.decryptSegment(inputStream) + } + } + + @Test + fun `decrypting data segment throws if reaching end of stream`() { + val inputStream = mockk<InputStream>() + val buffer = ByteArray(segmentHeader.segmentLength.toInt()) + + every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { inputStream.read(buffer) } returns -1 + + assertThrows(EOFException::class.java) { + crypto.decryptSegment(inputStream) + } + } + + @Test + fun `decrypting data segment throws if reading less than expected`() { + val inputStream = mockk<InputStream>() + val buffer = ByteArray(segmentHeader.segmentLength.toInt()) + + every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { inputStream.read(buffer) } returns buffer.size - 1 + + assertThrows(IOException::class.java) { + crypto.decryptSegment(inputStream) + } + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/crypto/KeyManagerTestImpl.kt b/app/src/test/java/com/stevesoltys/backup/crypto/KeyManagerTestImpl.kt new file mode 100644 index 00000000..e9473c89 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/crypto/KeyManagerTestImpl.kt @@ -0,0 +1,26 @@ +package com.stevesoltys.backup.crypto + +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey + +class KeyManagerTestImpl : KeyManager { + + private val key by lazy { + val keyGenerator = KeyGenerator.getInstance("AES") + keyGenerator.init(KEY_SIZE) + keyGenerator.generateKey() + } + + override fun storeBackupKey(seed: ByteArray) { + TODO("not implemented") + } + + override fun hasBackupKey(): Boolean { + return true + } + + override fun getBackupKey(): SecretKey { + return key + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/stevesoltys/backup/header/HeaderReaderTest.kt b/app/src/test/java/com/stevesoltys/backup/header/HeaderReaderTest.kt new file mode 100644 index 00000000..94b85b6f --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/header/HeaderReaderTest.kt @@ -0,0 +1,274 @@ +package com.stevesoltys.backup.header + +import com.stevesoltys.backup.assertContains +import com.stevesoltys.backup.getRandomString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.io.ByteArrayInputStream +import java.io.IOException +import java.nio.ByteBuffer +import kotlin.random.Random + +@TestInstance(PER_CLASS) +internal class HeaderReaderTest { + + private val reader = HeaderReaderImpl() + + // Version Tests + + @Test + fun `valid version is read`() { + val input = byteArrayOf(VERSION) + val inputStream = ByteArrayInputStream(input) + + assertEquals(VERSION, reader.readVersion(inputStream)) + } + + @Test + fun `too short version stream throws exception`() { + val input = ByteArray(0) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readVersion(inputStream) + } + } + + @Test + fun `unsupported version throws exception`() { + val input = byteArrayOf((VERSION + 1).toByte()) + val inputStream = ByteArrayInputStream(input) + assertThrows(UnsupportedVersionException::class.javaObjectType) { + reader.readVersion(inputStream) + } + } + + @Test + fun `negative version throws exception`() { + val input = byteArrayOf((-1).toByte()) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readVersion(inputStream) + } + } + + @Test + fun `max version byte throws exception`() { + val input = byteArrayOf(Byte.MAX_VALUE) + val inputStream = ByteArrayInputStream(input) + assertThrows(UnsupportedVersionException::class.javaObjectType) { + reader.readVersion(inputStream) + } + } + + // VersionHeader Tests + + @Test + fun `valid VersionHeader is read`() { + val input = byteArrayOf(VERSION, 0x00, 0x01, 0x61, 0x00, 0x01, 0x62) + + val versionHeader = VersionHeader(VERSION, "a", "b") + assertEquals(versionHeader, reader.getVersionHeader(input)) + } + + @Test + fun `zero package length in VersionHeader throws`() { + val input = byteArrayOf(VERSION, 0x00, 0x00, 0x00, 0x01, 0x62) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `negative package length in VersionHeader throws`() { + val input = byteArrayOf(0x00, 0xFF, 0xFF, 0x00, 0x01, 0x62) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `too large package length in VersionHeader throws`() { + val size = MAX_PACKAGE_LENGTH_SIZE + 1 + val input = ByteBuffer.allocate(3 + size) + .put(VERSION) + .putShort(size.toShort()) + .put(ByteArray(size)) + .array() + val e = assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + assertContains(e.message, size.toString()) + } + + @Test + fun `insufficient bytes for package in VersionHeader throws`() { + val input = byteArrayOf(VERSION, 0x00, 0x50) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `zero key length in VersionHeader gets accepted`() { + val input = byteArrayOf(VERSION, 0x00, 0x01, 0x61, 0x00, 0x00) + + val versionHeader = VersionHeader(VERSION, "a", null) + assertEquals(versionHeader, reader.getVersionHeader(input)) + } + + @Test + fun `negative key length in VersionHeader throws`() { + val input = byteArrayOf(0x00, 0x00, 0x01, 0x61, 0xFF, 0xFF) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `too large key length in VersionHeader throws`() { + val size = MAX_KEY_LENGTH_SIZE + 1 + val input = ByteBuffer.allocate(4 + size) + .put(VERSION) + .putShort(1.toShort()) + .put("a".toByteArray(Utf8)) + .putShort(size.toShort()) + .array() + val e = assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + assertContains(e.message, size.toString()) + } + + @Test + fun `insufficient bytes for key in VersionHeader throws`() { + val input = byteArrayOf(0x00, 0x00, 0x01, 0x61, 0x00, 0x50) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `extra bytes in VersionHeader throws`() { + val input = byteArrayOf(VERSION, 0x00, 0x01, 0x61, 0x00, 0x01, 0x62, 0x00) + + assertThrows(SecurityException::class.javaObjectType) { + reader.getVersionHeader(input) + } + } + + @Test + fun `max sized VersionHeader gets accepted`() { + val packageName = getRandomString(MAX_PACKAGE_LENGTH_SIZE) + val key = getRandomString(MAX_KEY_LENGTH_SIZE) + val input = ByteBuffer.allocate(MAX_VERSION_HEADER_SIZE) + .put(VERSION) + .putShort(MAX_PACKAGE_LENGTH_SIZE.toShort()) + .put(packageName.toByteArray(Utf8)) + .putShort(MAX_KEY_LENGTH_SIZE.toShort()) + .put(key.toByteArray(Utf8)) + .array() + assertEquals(MAX_VERSION_HEADER_SIZE, input.size) + val h = reader.getVersionHeader(input) + assertEquals(VERSION, h.version) + assertEquals(packageName, h.packageName) + assertEquals(key, h.key) + } + + // SegmentHeader Tests + + @Test + fun `too short SegmentHeader throws exception`() { + val input = byteArrayOf(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readSegmentHeader(inputStream) + } + } + + @Test + fun `segment length of zero is rejected`() { + val input = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readSegmentHeader(inputStream) + } + } + + @Test + fun `negative segment length is rejected`() { + val input = byteArrayOf(0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readSegmentHeader(inputStream) + } + } + + @Test + fun `minimum negative segment length is rejected`() { + val input = byteArrayOf(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertThrows(IOException::class.javaObjectType) { + reader.readSegmentHeader(inputStream) + } + } + + @Test + fun `max segment length is accepted`() { + val input = byteArrayOf(0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertEquals(MAX_SEGMENT_LENGTH, reader.readSegmentHeader(inputStream).segmentLength.toInt()) + } + + @Test + fun `min segment length of 1 is accepted`() { + val input = byteArrayOf(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + val inputStream = ByteArrayInputStream(input) + assertEquals(1, reader.readSegmentHeader(inputStream).segmentLength.toInt()) + } + + @Test + fun `segment length is always read correctly`() { + val segmentLength = getRandomValidSegmentLength() + val input = ByteBuffer.allocate(SEGMENT_HEADER_SIZE) + .putShort(segmentLength) + .put(ByteArray(IV_SIZE)) + .array() + val inputStream = ByteArrayInputStream(input) + assertEquals(segmentLength, reader.readSegmentHeader(inputStream).segmentLength) + } + + @Test + fun `nonce is read in big endian`() { + val nonce = byteArrayOf(0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01) + val input = byteArrayOf(0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01) + val inputStream = ByteArrayInputStream(input) + assertArrayEquals(nonce, reader.readSegmentHeader(inputStream).nonce) + } + + @Test + fun `nonce is always read correctly`() { + val nonce = ByteArray(IV_SIZE).apply { Random.nextBytes(this) } + val input = ByteBuffer.allocate(SEGMENT_HEADER_SIZE) + .putShort(1) + .put(nonce) + .array() + val inputStream = ByteArrayInputStream(input) + assertArrayEquals(nonce, reader.readSegmentHeader(inputStream).nonce) + } + + private fun byteArrayOf(vararg elements: Int): ByteArray { + return elements.map { it.toByte() }.toByteArray() + } + +} + +internal fun getRandomValidSegmentLength(): Short { + return Random.nextInt(1, Short.MAX_VALUE.toInt()).toShort() +} diff --git a/app/src/test/java/com/stevesoltys/backup/header/HeaderWriterReaderTest.kt b/app/src/test/java/com/stevesoltys/backup/header/HeaderWriterReaderTest.kt new file mode 100644 index 00000000..a1df050b --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/header/HeaderWriterReaderTest.kt @@ -0,0 +1,102 @@ +package com.stevesoltys.backup.header + +import com.stevesoltys.backup.getRandomByteArray +import com.stevesoltys.backup.getRandomString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import kotlin.random.Random + +@TestInstance(PER_CLASS) +internal class HeaderWriterReaderTest { + + private val writer = HeaderWriterImpl() + private val reader = HeaderReaderImpl() + + private val packageName = getRandomString(MAX_PACKAGE_LENGTH_SIZE) + private val key = getRandomString(MAX_KEY_LENGTH_SIZE) + private val versionHeader = VersionHeader(VERSION, packageName, key) + private val unsupportedVersionHeader = VersionHeader((VERSION + 1).toByte(), packageName) + + private val segmentLength = getRandomValidSegmentLength() + private val nonce = getRandomByteArray(IV_SIZE) + private val segmentHeader = SegmentHeader(segmentLength, nonce) + + @Test + fun `written version matches read input`() { + assertEquals(versionHeader.version, readWriteVersion(versionHeader)) + } + + @Test + fun `reading unsupported version throws exception`() { + assertThrows(UnsupportedVersionException::class.javaObjectType) { + readWriteVersion(unsupportedVersionHeader) + } + } + + @Test + fun `VersionHeader output matches read input`() { + assertEquals(versionHeader, readWrite(versionHeader)) + } + + @Test + fun `VersionHeader with no key output matches read input`() { + val versionHeader = VersionHeader(VERSION, packageName, null) + assertEquals(versionHeader, readWrite(versionHeader)) + } + + @Test + fun `VersionHeader with empty package name throws`() { + val versionHeader = VersionHeader(VERSION, "") + assertThrows(SecurityException::class.java) { + readWrite(versionHeader) + } + } + + @Test + fun `SegmentHeader constructor needs right IV size`() { + val nonceTooBig = ByteArray(IV_SIZE + 1).apply { Random.nextBytes(this) } + assertThrows(IllegalStateException::class.javaObjectType) { + SegmentHeader(segmentLength, nonceTooBig) + } + val nonceTooSmall = ByteArray(IV_SIZE - 1).apply { Random.nextBytes(this) } + assertThrows(IllegalStateException::class.javaObjectType) { + SegmentHeader(segmentLength, nonceTooSmall) + } + } + + @Test + fun `SegmentHeader output matches read input`() { + assertEquals(segmentHeader, readWriteVersion(segmentHeader)) + } + + private fun readWriteVersion(header: VersionHeader): Byte { + val outputStream = ByteArrayOutputStream() + writer.writeVersion(outputStream, header) + val written = outputStream.toByteArray() + val inputStream = ByteArrayInputStream(written) + return reader.readVersion(inputStream) + } + + private fun readWrite(header: VersionHeader): VersionHeader { + val written = writer.getEncodedVersionHeader(header) + return reader.getVersionHeader(written) + } + + private fun readWriteVersion(header: SegmentHeader): SegmentHeader { + val outputStream = ByteArrayOutputStream() + writer.writeSegmentHeader(outputStream, header) + val written = outputStream.toByteArray() + val inputStream = ByteArrayInputStream(written) + return reader.readSegmentHeader(inputStream) + } + + private fun assertEquals(expected: SegmentHeader, actual: SegmentHeader) { + assertEquals(expected.segmentLength, actual.segmentLength) + assertArrayEquals(expected.nonce, actual.nonce) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/CoordinatorIntegrationTest.kt new file mode 100644 index 00000000..9622568c --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/CoordinatorIntegrationTest.kt @@ -0,0 +1,162 @@ +package com.stevesoltys.backup.transport + +import android.app.backup.BackupDataInput +import android.app.backup.BackupDataOutput +import android.app.backup.BackupTransport.NO_MORE_DATA +import android.app.backup.BackupTransport.TRANSPORT_OK +import android.app.backup.RestoreDescription +import android.app.backup.RestoreDescription.TYPE_FULL_STREAM +import android.os.ParcelFileDescriptor +import com.stevesoltys.backup.crypto.CipherFactoryImpl +import com.stevesoltys.backup.crypto.CryptoImpl +import com.stevesoltys.backup.crypto.KeyManagerTestImpl +import com.stevesoltys.backup.transport.backup.* +import com.stevesoltys.backup.header.HeaderReaderImpl +import com.stevesoltys.backup.header.HeaderWriterImpl +import com.stevesoltys.backup.header.Utf8 +import com.stevesoltys.backup.transport.restore.* +import io.mockk.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.* +import kotlin.random.Random + +internal class CoordinatorIntegrationTest : TransportTest() { + + private val inputFactory = mockk<InputFactory>() + private val outputFactory = mockk<OutputFactory>() + private val keyManager = KeyManagerTestImpl() + private val cipherFactory = CipherFactoryImpl(keyManager) + private val headerWriter = HeaderWriterImpl() + private val headerReader = HeaderReaderImpl() + private val cryptoImpl = CryptoImpl(cipherFactory, headerWriter, headerReader) + + private val backupPlugin = mockk<BackupPlugin>() + private val kvBackupPlugin = mockk<KVBackupPlugin>() + private val kvBackup = KVBackup(kvBackupPlugin, inputFactory, headerWriter, cryptoImpl) + private val fullBackupPlugin = mockk<FullBackupPlugin>() + private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl) + private val backup = BackupCoordinator(backupPlugin, kvBackup, fullBackup) + + private val restorePlugin = mockk<RestorePlugin>() + private val kvRestorePlugin = mockk<KVRestorePlugin>() + private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl) + private val fullRestorePlugin = mockk<FullRestorePlugin>() + private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl) + private val restore = RestoreCoordinator(restorePlugin, kvRestore, fullRestore) + + private val backupDataInput = mockk<BackupDataInput>() + private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true) + private val token = DEFAULT_RESTORE_SET_TOKEN + private val appData = ByteArray(42).apply { Random.nextBytes(this) } + private val appData2 = ByteArray(1337).apply { Random.nextBytes(this) } + private val key = "RestoreKey" + private val key64 = Base64.getUrlEncoder().withoutPadding().encodeToString(key.toByteArray(Utf8)) + private val key2 = "RestoreKey2" + private val key264 = Base64.getUrlEncoder().withoutPadding().encodeToString(key2.toByteArray(Utf8)) + + init { + every { backupPlugin.kvBackupPlugin } returns kvBackupPlugin + every { backupPlugin.fullBackupPlugin } returns fullBackupPlugin + } + + @Test + fun `test key-value backup and restore with 2 records`() { + val value = CapturingSlot<ByteArray>() + val value2 = CapturingSlot<ByteArray>() + val bOutputStream = ByteArrayOutputStream() + val bOutputStream2 = ByteArrayOutputStream() + + // read one key/value record and write it to output stream + every { kvBackupPlugin.hasDataForPackage(packageInfo) } returns false + every { kvBackupPlugin.ensureRecordStorageForPackage(packageInfo) } just Runs + every { inputFactory.getBackupDataInput(fileDescriptor) } returns backupDataInput + every { backupDataInput.readNextHeader() } returns true andThen true andThen false + every { backupDataInput.key } returns key andThen key2 + every { backupDataInput.dataSize } returns appData.size andThen appData2.size + every { backupDataInput.readEntityData(capture(value), 0, appData.size) } answers { + appData.copyInto(value.captured) // write the app data into the passed ByteArray + appData.size + } + every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key64) } returns bOutputStream + every { backupDataInput.readEntityData(capture(value2), 0, appData2.size) } answers { + appData2.copyInto(value2.captured) // write the app data into the passed ByteArray + appData2.size + } + every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2 + + // start and finish K/V backup + assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + + // start restore + assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) + + // find data for K/V backup + every { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true + + val restoreDescription = restore.nextRestorePackage() ?: fail() + assertEquals(packageInfo.packageName, restoreDescription.packageName) + assertEquals(RestoreDescription.TYPE_KEY_VALUE, restoreDescription.dataType) + + // 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()) + every { kvRestorePlugin.listRecords(token, packageInfo) } returns listOf(key64, key264) + every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput + every { kvRestorePlugin.getInputStreamForRecord(token, packageInfo, key64) } returns rInputStream + every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137 + every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size + every { 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)) + } + + @Test + fun `test full backup and restore with two chunks`() { + // return streams from plugin and app data + val bOutputStream = ByteArrayOutputStream() + val bInputStream = ByteArrayInputStream(appData) + every { fullBackupPlugin.getOutputStream(packageInfo) } returns bOutputStream + every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream + every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP + + // perform backup to output stream + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0)) + assertEquals(TRANSPORT_OK, backup.sendBackupData(appData.size / 2)) + assertEquals(TRANSPORT_OK, backup.sendBackupData(appData.size / 2)) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + + // start restore + assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) + + // find data only for full backup + every { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns false + every { fullRestorePlugin.hasDataForPackage(token, packageInfo) } returns true + + val restoreDescription = restore.nextRestorePackage() ?: fail() + assertEquals(packageInfo.packageName, restoreDescription.packageName) + assertEquals(TYPE_FULL_STREAM, restoreDescription.dataType) + + // reverse the backup streams into restore input + val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) + val rOutputStream = ByteArrayOutputStream() + every { fullRestorePlugin.getInputStreamForPackage(token, packageInfo) } returns rInputStream + every { outputFactory.getOutputStream(fileDescriptor) } returns rOutputStream + + // restore data + assertEquals(appData.size / 2, restore.getNextFullRestoreDataChunk(fileDescriptor)) + assertEquals(appData.size / 2, restore.getNextFullRestoreDataChunk(fileDescriptor)) + assertEquals(NO_MORE_DATA, restore.getNextFullRestoreDataChunk(fileDescriptor)) + restore.finishRestore() + + // assert that restored data matches original app data + assertArrayEquals(appData, rOutputStream.toByteArray()) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/TransportTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/TransportTest.kt new file mode 100644 index 00000000..6c425339 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/TransportTest.kt @@ -0,0 +1,30 @@ +package com.stevesoltys.backup.transport + +import android.content.pm.PackageInfo +import android.util.Log +import com.stevesoltys.backup.crypto.Crypto +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD + +@TestInstance(PER_METHOD) +abstract class TransportTest { + + protected val crypto = mockk<Crypto>() + + protected val packageInfo = PackageInfo().apply { packageName = "org.example" } + + init { + mockkStatic(Log::class) + every { Log.v(any(), any()) } returns 0 + every { Log.d(any(), any()) } returns 0 + every { Log.i(any(), any()) } returns 0 + every { Log.w(any(), ofType(String::class)) } returns 0 + every { Log.w(any(), ofType(String::class), any()) } returns 0 + every { Log.e(any(), any()) } returns 0 + every { Log.e(any(), any(), any()) } returns 0 + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupCoordinatorTest.kt new file mode 100644 index 00000000..e5833277 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupCoordinatorTest.kt @@ -0,0 +1,110 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_OK +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import java.io.IOException +import kotlin.random.Random + +internal class BackupCoordinatorTest: BackupTest() { + + private val plugin = mockk<BackupPlugin>() + private val kv = mockk<KVBackup>() + private val full = mockk<FullBackup>() + + private val backup = BackupCoordinator(plugin, kv, full) + + @Test + fun `device initialization succeeds and delegates to plugin`() { + every { plugin.initializeDevice() } just Runs + every { kv.hasState() } returns false + every { full.hasState() } returns false + + assertEquals(TRANSPORT_OK, backup.initializeDevice()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + } + + @Test + fun `device initialization fails`() { + every { plugin.initializeDevice() } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.initializeDevice()) + + // finish will only be called when TRANSPORT_OK is returned, so it should throw + every { kv.hasState() } returns false + every { full.hasState() } returns false + assertThrows(IllegalStateException::class.java) { + backup.finishBackup() + } + } + + @Test + fun `getBackupQuota() delegates to right plugin`() { + val isFullBackup = Random.nextBoolean() + val quota = Random.nextLong() + + if (isFullBackup) { + every { full.getQuota() } returns quota + } else { + every { kv.getQuota() } returns quota + } + assertEquals(quota, backup.getBackupQuota(packageInfo.packageName, isFullBackup)) + } + + @Test + fun `clearing KV backup data throws`() { + every { kv.clearBackupData(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.clearBackupData(packageInfo)) + } + + @Test + fun `clearing full backup data throws`() { + every { kv.clearBackupData(packageInfo) } just Runs + every { full.clearBackupData(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.clearBackupData(packageInfo)) + } + + @Test + fun `clearing backup data succeeds`() { + every { kv.clearBackupData(packageInfo) } just Runs + every { full.clearBackupData(packageInfo) } just Runs + + assertEquals(TRANSPORT_OK, backup.clearBackupData(packageInfo)) + + every { kv.hasState() } returns false + every { full.hasState() } returns false + + assertEquals(TRANSPORT_OK, backup.finishBackup()) + } + + @Test + fun `finish backup delegates to KV plugin if it has state`() { + val result = Random.nextInt() + + every { kv.hasState() } returns true + every { full.hasState() } returns false + every { kv.finishBackup() } returns result + + assertEquals(result, backup.finishBackup()) + } + + @Test + fun `finish backup delegates to full plugin if it has state`() { + val result = Random.nextInt() + + every { kv.hasState() } returns false + every { full.hasState() } returns true + every { full.finishBackup() } returns result + + assertEquals(result, backup.finishBackup()) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupTest.kt new file mode 100644 index 00000000..df96b38e --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/backup/BackupTest.kt @@ -0,0 +1,20 @@ +package com.stevesoltys.backup.transport.backup + +import android.os.ParcelFileDescriptor +import com.stevesoltys.backup.transport.TransportTest +import com.stevesoltys.backup.header.HeaderWriter +import com.stevesoltys.backup.header.VersionHeader +import io.mockk.mockk +import java.io.OutputStream + +internal abstract class BackupTest : TransportTest() { + + protected val inputFactory = mockk<InputFactory>() + protected val headerWriter = mockk<HeaderWriter>() + protected val data = mockk<ParcelFileDescriptor>() + protected val outputStream = mockk<OutputStream>() + + protected val header = VersionHeader(packageName = packageInfo.packageName) + protected val quota = 42L + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/backup/FullBackupTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/backup/FullBackupTest.kt new file mode 100644 index 00000000..b6f1972e --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/backup/FullBackupTest.kt @@ -0,0 +1,276 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupTransport.* +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.io.FileInputStream +import java.io.IOException +import kotlin.random.Random + +internal class FullBackupTest : BackupTest() { + + private val plugin = mockk<FullBackupPlugin>() + private val backup = FullBackup(plugin, inputFactory, headerWriter, crypto) + + private val bytes = ByteArray(23).apply { Random.nextBytes(this) } + private val closeBytes = ByteArray(42).apply { Random.nextBytes(this) } + private val inputStream = mockk<FileInputStream>() + + @Test + fun `now is a good time for a backup`() { + assertEquals(0, backup.requestFullBackupTime()) + } + + @Test + fun `has no initial state`() { + assertFalse(backup.hasState()) + } + + @Test + fun `checkFullBackupSize exceeds quota`() { + every { plugin.getQuota() } returns quota + + assertEquals(TRANSPORT_QUOTA_EXCEEDED, backup.checkFullBackupSize(quota + 1)) + } + + @Test + fun `checkFullBackupSize for no data`() { + assertEquals(TRANSPORT_PACKAGE_REJECTED, backup.checkFullBackupSize(0)) + } + + @Test + fun `checkFullBackupSize for negative data`() { + assertEquals(TRANSPORT_PACKAGE_REJECTED, backup.checkFullBackupSize(-1)) + } + + @Test + fun `checkFullBackupSize accepts min data`() { + every { plugin.getQuota() } returns quota + + assertEquals(TRANSPORT_OK, backup.checkFullBackupSize(1)) + } + + @Test + fun `checkFullBackupSize accepts max data`() { + every { plugin.getQuota() } returns quota + + assertEquals(TRANSPORT_OK, backup.checkFullBackupSize(quota)) + } + + @Test + fun `performFullBackup throws exception when getting outputStream`() { + every { plugin.getOutputStream(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performFullBackup(packageInfo, data)) + assertFalse(backup.hasState()) + } + + @Test + fun `performFullBackup throws exception when writing header`() { + every { plugin.getOutputStream(packageInfo) } returns outputStream + every { inputFactory.getInputStream(data) } returns inputStream + every { headerWriter.writeVersion(outputStream, header) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performFullBackup(packageInfo, data)) + assertFalse(backup.hasState()) + } + + @Test + fun `performFullBackup runs ok`() { + expectPerformFullBackup() + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `sendBackupData first call over quota`() { + expectPerformFullBackup() + val numBytes = (quota + 1).toInt() + expectSendData(numBytes) + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_QUOTA_EXCEEDED, backup.sendBackupData(numBytes)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `sendBackupData second call over quota`() { + expectPerformFullBackup() + val numBytes1 = quota.toInt() + expectSendData(numBytes1) + val numBytes2 = 1 + expectSendData(numBytes2) + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.sendBackupData(numBytes1)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_QUOTA_EXCEEDED, backup.sendBackupData(numBytes2)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `sendBackupData throws exception when reading from InputStream`() { + expectPerformFullBackup() + every { plugin.getQuota() } returns quota + every { inputStream.read(any(), any(), bytes.size) } throws IOException() + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_ERROR, backup.sendBackupData(bytes.size)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `sendBackupData throws exception when writing encrypted data to OutputStream`() { + expectPerformFullBackup() + every { plugin.getQuota() } returns quota + every { inputStream.read(any(), any(), bytes.size) } returns bytes.size + every { crypto.encryptSegment(outputStream, any()) } throws IOException() + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_ERROR, backup.sendBackupData(bytes.size)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `sendBackupData runs ok`() { + expectPerformFullBackup() + val numBytes1 = (quota / 2).toInt() + expectSendData(numBytes1) + val numBytes2 = (quota / 2).toInt() + expectSendData(numBytes2) + expectClearState() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.sendBackupData(numBytes1)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.sendBackupData(numBytes2)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `cancel full backup runs ok`() { + expectPerformFullBackup() + expectClearState() + every { plugin.cancelFullBackup(packageInfo) } just Runs + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + backup.cancelFullBackup() + assertFalse(backup.hasState()) + } + + @Test + fun `cancel full backup ignores exception when calling plugin`() { + expectPerformFullBackup() + expectClearState() + every { plugin.cancelFullBackup(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + backup.cancelFullBackup() + assertFalse(backup.hasState()) + } + + @Test + fun `clearState throws exception when flushing OutputStream`() { + expectPerformFullBackup() + every { outputStream.write(closeBytes) } just Runs + every { outputStream.flush() } throws IOException() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_ERROR, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `clearState ignores exception when closing OutputStream`() { + expectPerformFullBackup() + every { outputStream.flush() } just Runs + every { outputStream.close() } throws IOException() + every { inputStream.close() } just Runs + every { data.close() } just Runs + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `clearState ignores exception when closing InputStream`() { + expectPerformFullBackup() + every { outputStream.flush() } just Runs + every { outputStream.close() } just Runs + every { inputStream.close() } throws IOException() + every { data.close() } just Runs + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `clearState ignores exception when closing ParcelFileDescriptor`() { + expectPerformFullBackup() + every { outputStream.flush() } just Runs + every { outputStream.close() } just Runs + every { inputStream.close() } just Runs + every { data.close() } throws IOException() + + assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + private fun expectPerformFullBackup() { + every { plugin.getOutputStream(packageInfo) } returns outputStream + every { inputFactory.getInputStream(data) } returns inputStream + every { headerWriter.writeVersion(outputStream, header) } just Runs + every { crypto.encryptHeader(outputStream, header) } just Runs + } + + private fun expectSendData(numBytes: Int, readBytes: Int = numBytes) { + every { plugin.getQuota() } returns quota + every { inputStream.read(any(), any(), numBytes) } returns readBytes + every { crypto.encryptSegment(outputStream, any()) } just Runs + } + + private fun expectClearState() { + every { outputStream.write(closeBytes) } just Runs + every { outputStream.flush() } just Runs + every { outputStream.close() } just Runs + every { inputStream.close() } just Runs + every { data.close() } just Runs + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/backup/KVBackupTest.kt new file mode 100644 index 00000000..5362415b --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/backup/KVBackupTest.kt @@ -0,0 +1,216 @@ +package com.stevesoltys.backup.transport.backup + +import android.app.backup.BackupDataInput +import android.app.backup.BackupTransport.* +import com.stevesoltys.backup.getRandomString +import com.stevesoltys.backup.header.MAX_KEY_LENGTH_SIZE +import com.stevesoltys.backup.header.Utf8 +import com.stevesoltys.backup.header.VersionHeader +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.io.IOException +import java.util.* +import kotlin.random.Random + +internal class KVBackupTest : BackupTest() { + + private val plugin = mockk<KVBackupPlugin>() + private val dataInput = mockk<BackupDataInput>() + + private val backup = KVBackup(plugin, inputFactory, headerWriter, crypto) + + private val key = getRandomString(MAX_KEY_LENGTH_SIZE) + private val key64 = Base64.getEncoder().encodeToString(key.toByteArray(Utf8)) + private val value = ByteArray(23).apply { Random.nextBytes(this) } + private val versionHeader = VersionHeader(packageName = packageInfo.packageName, key = key) + + @Test + fun `now is a good time for a backup`() { + assertEquals(0, backup.requestBackupTime()) + } + + @Test + fun `has no initial state`() { + assertFalse(backup.hasState()) + } + + @Test + fun `simple backup with one record`() { + singleRecordBackup() + + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, 0)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `incremental backup with no data gets rejected`() { + every { plugin.hasDataForPackage(packageInfo) } returns false + + assertEquals(TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED, backup.performBackup(packageInfo, data, FLAG_INCREMENTAL)) + assertFalse(backup.hasState()) + } + + @Test + fun `check for existing data throws exception`() { + every { plugin.hasDataForPackage(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `non-incremental backup with data clears old data first`() { + singleRecordBackup(true) + every { plugin.removeDataOfPackage(packageInfo) } just Runs + + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, FLAG_NON_INCREMENTAL)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `ignoring exception when clearing data when non-incremental backup has data`() { + singleRecordBackup(true) + every { plugin.removeDataOfPackage(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, FLAG_NON_INCREMENTAL)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `ensuring storage throws exception`() { + every { plugin.hasDataForPackage(packageInfo) } returns false + every { plugin.ensureRecordStorageForPackage(packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `exception while reading next header`() { + initPlugin(false) + createBackupDataInput() + every { dataInput.readNextHeader() } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `exception while reading value`() { + initPlugin(false) + createBackupDataInput() + every { dataInput.readNextHeader() } returns true + every { dataInput.key } returns key + every { dataInput.dataSize } returns value.size + every { dataInput.readEntityData(any(), 0, value.size) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `no data records`() { + initPlugin(false) + getDataInput(listOf(false)) + + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, 0)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + @Test + fun `exception while writing version header`() { + initPlugin(false) + getDataInput(listOf(true)) + every { plugin.getOutputStreamForRecord(packageInfo, key64) } returns outputStream + every { headerWriter.writeVersion(outputStream, versionHeader) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `exception while writing encrypted value to output stream`() { + initPlugin(false) + getDataInput(listOf(true)) + writeHeaderAndEncrypt() + every { plugin.getOutputStreamForRecord(packageInfo, key64) } returns outputStream + every { headerWriter.writeVersion(outputStream, versionHeader) } just Runs + every { crypto.encryptSegment(outputStream, any()) } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `exception while flushing output stream`() { + initPlugin(false) + getDataInput(listOf(true)) + writeHeaderAndEncrypt() + every { outputStream.write(value) } just Runs + every { outputStream.flush() } throws IOException() + + assertEquals(TRANSPORT_ERROR, backup.performBackup(packageInfo, data, 0)) + assertFalse(backup.hasState()) + } + + @Test + fun `ignoring exception while closing output stream`() { + initPlugin(false) + getDataInput(listOf(true, false)) + writeHeaderAndEncrypt() + every { outputStream.write(value) } just Runs + every { outputStream.flush() } just Runs + every { outputStream.close() } throws IOException() + + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, 0)) + assertTrue(backup.hasState()) + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + + private fun singleRecordBackup(hasDataForPackage: Boolean = false) { + initPlugin(hasDataForPackage) + getDataInput(listOf(true, false)) + writeHeaderAndEncrypt() + every { outputStream.write(value) } just Runs + every { outputStream.flush() } just Runs + every { outputStream.close() } just Runs + } + + private fun initPlugin(hasDataForPackage: Boolean = false) { + every { plugin.hasDataForPackage(packageInfo) } returns hasDataForPackage + every { plugin.ensureRecordStorageForPackage(packageInfo) } just Runs + } + + private fun createBackupDataInput() { + every { inputFactory.getBackupDataInput(data) } returns dataInput + } + + private fun getDataInput(returnValues: List<Boolean>) { + createBackupDataInput() + every { dataInput.readNextHeader() } returnsMany returnValues + every { dataInput.key } returns key + every { dataInput.dataSize } returns value.size + every { dataInput.readEntityData(any(), 0, value.size) } returns value.size + } + + private fun writeHeaderAndEncrypt() { + every { plugin.getOutputStreamForRecord(packageInfo, key64) } returns outputStream + every { headerWriter.writeVersion(outputStream, versionHeader) } just Runs + every { crypto.encryptHeader(outputStream, versionHeader) } just Runs + every { crypto.encryptSegment(outputStream, any()) } just Runs + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/stevesoltys/backup/transport/restore/FullRestoreTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/restore/FullRestoreTest.kt new file mode 100644 index 00000000..fd6ee47d --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/restore/FullRestoreTest.kt @@ -0,0 +1,173 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupTransport.* +import com.stevesoltys.backup.getRandomByteArray +import com.stevesoltys.backup.header.UnsupportedVersionException +import com.stevesoltys.backup.header.VERSION +import com.stevesoltys.backup.header.VersionHeader +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.io.ByteArrayOutputStream +import java.io.EOFException +import java.io.IOException +import kotlin.random.Random + +internal class FullRestoreTest : RestoreTest() { + + private val plugin = mockk<FullRestorePlugin>() + private val restore = FullRestore(plugin, outputFactory, headerReader, crypto) + + private val encrypted = getRandomByteArray() + private val outputStream = ByteArrayOutputStream() + private val versionHeader = VersionHeader(VERSION, packageInfo.packageName) + + @Test + fun `has no initial state`() { + assertFalse(restore.hasState()) + } + + @Test + fun `hasDataForPackage() delegates to plugin`() { + val result = Random.nextBoolean() + every { plugin.hasDataForPackage(token, packageInfo) } returns result + assertEquals(result, restore.hasDataForPackage(token, packageInfo)) + } + + @Test + fun `initializing state leaves a state`() { + assertFalse(restore.hasState()) + restore.initializeState(token, packageInfo) + assertTrue(restore.hasState()) + } + + @Test + fun `getting chunks without initializing state throws`() { + assertFalse(restore.hasState()) + assertThrows(IllegalStateException::class.java) { + restore.getNextFullRestoreDataChunk(fileDescriptor) + } + } + + @Test + fun `getting InputStream for package when getting first chunk throws`() { + restore.initializeState(token, packageInfo) + + every { plugin.getInputStreamForPackage(token, packageInfo) } throws IOException() + + assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `reading version header when getting first chunk throws`() { + restore.initializeState(token, packageInfo) + + every { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { headerReader.readVersion(inputStream) } throws IOException() + + assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `reading unsupported version when getting first chunk`() { + restore.initializeState(token, packageInfo) + + every { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { headerReader.readVersion(inputStream) } throws UnsupportedVersionException(unsupportedVersion) + + assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `decrypting version header when getting first chunk throws`() { + restore.initializeState(token, packageInfo) + + every { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } throws IOException() + + assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `decrypting version header when getting first chunk throws security exception`() { + restore.initializeState(token, packageInfo) + + every { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } throws SecurityException() + + assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `decrypting segment throws IOException`() { + restore.initializeState(token, packageInfo) + + initInputStream() + every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream + every { crypto.decryptSegment(inputStream) } throws IOException() + every { inputStream.close() } just Runs + every { fileDescriptor.close() } just Runs + + assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `decrypting segment throws EOFException`() { + restore.initializeState(token, packageInfo) + + initInputStream() + every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream + every { crypto.decryptSegment(inputStream) } throws EOFException() + every { inputStream.close() } just Runs + every { fileDescriptor.close() } just Runs + + assertEquals(NO_MORE_DATA, restore.getNextFullRestoreDataChunk(fileDescriptor)) + } + + @Test + fun `full chunk gets encrypted`() { + restore.initializeState(token, packageInfo) + + initInputStream() + readAndEncryptInputStream(encrypted) + every { inputStream.close() } just Runs + + assertEquals(encrypted.size, restore.getNextFullRestoreDataChunk(fileDescriptor)) + assertArrayEquals(encrypted, outputStream.toByteArray()) + restore.finishRestore() + assertFalse(restore.hasState()) + } + + @Test + fun `aborting full restore closes stream, resets state`() { + restore.initializeState(token, packageInfo) + + initInputStream() + readAndEncryptInputStream(encrypted) + + restore.getNextFullRestoreDataChunk(fileDescriptor) + + every { inputStream.close() } just Runs + + assertEquals(TRANSPORT_OK, restore.abortFullRestore()) + assertFalse(restore.hasState()) + } + + private fun initInputStream() { + every { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } returns versionHeader + } + + private fun readAndEncryptInputStream(encryptedBytes: ByteArray) { + every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream + every { crypto.decryptSegment(inputStream) } returns encryptedBytes + every { fileDescriptor.close() } just Runs + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/restore/KVRestoreTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/restore/KVRestoreTest.kt new file mode 100644 index 00000000..f0237ce8 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/restore/KVRestoreTest.kt @@ -0,0 +1,221 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupDataOutput +import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_OK +import com.stevesoltys.backup.getRandomByteArray +import com.stevesoltys.backup.header.UnsupportedVersionException +import com.stevesoltys.backup.header.Utf8 +import com.stevesoltys.backup.header.VERSION +import com.stevesoltys.backup.header.VersionHeader +import io.mockk.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import java.io.IOException +import java.io.InputStream +import java.util.Base64.getUrlEncoder +import kotlin.random.Random + +internal class KVRestoreTest : RestoreTest() { + + private val plugin = mockk<KVRestorePlugin>() + private val output = mockk<BackupDataOutput>() + private val restore = KVRestore(plugin, outputFactory, headerReader, crypto) + + private val key = "Restore Key" + private val key64 = getUrlEncoder().withoutPadding().encodeToString(key.toByteArray(Utf8)) + private val versionHeader = VersionHeader(VERSION, packageInfo.packageName, key) + private val key2 = "Restore Key2" + private val key264 = getUrlEncoder().withoutPadding().encodeToString(key2.toByteArray(Utf8)) + private val versionHeader2 = VersionHeader(VERSION, packageInfo.packageName, key2) + + @Test + fun `hasDataForPackage() delegates to plugin`() { + val result = Random.nextBoolean() + + every { plugin.hasDataForPackage(token, packageInfo) } returns result + + assertEquals(result, restore.hasDataForPackage(token, packageInfo)) + } + + @Test + fun `getRestoreData() throws without initializing state`() { + assertThrows(IllegalStateException::class.java) { + restore.getRestoreData(fileDescriptor) + } + } + + @Test + fun `listing records throws`() { + restore.initializeState(token, packageInfo) + + every { plugin.listRecords(token, packageInfo) } throws IOException() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + } + + @Test + fun `reading VersionHeader with unsupported version throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } throws UnsupportedVersionException(unsupportedVersion) + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `error reading VersionHeader throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } throws IOException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `decrypting segment throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader + every { crypto.decryptSegment(inputStream) } throws IOException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `decrypting header throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } throws IOException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `decrypting header throws security exception`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } throws SecurityException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `writing header throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader + every { crypto.decryptSegment(inputStream) } returns data + every { output.writeEntityHeader(key, data.size) } throws IOException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `writing value throws`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader + every { crypto.decryptSegment(inputStream) } returns data + every { output.writeEntityHeader(key, data.size) } returns 42 + every { output.writeEntityData(data, data.size) } throws IOException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `writing value succeeds`() { + restore.initializeState(token, packageInfo) + + getRecordsAndOutput() + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader + every { crypto.decryptSegment(inputStream) } returns data + every { output.writeEntityHeader(key, data.size) } returns 42 + every { output.writeEntityData(data, data.size) } returns data.size + streamsGetClosed() + + assertEquals(TRANSPORT_OK, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + + @Test + fun `writing two values succeeds`() { + val data2 = getRandomByteArray() + val inputStream2 = mockk<InputStream>() + restore.initializeState(token, packageInfo) + + getRecordsAndOutput(listOf(key64, key264)) + // first key/value + every { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { headerReader.readVersion(inputStream) } returns VERSION + every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader + every { crypto.decryptSegment(inputStream) } returns data + every { output.writeEntityHeader(key, data.size) } returns 42 + every { output.writeEntityData(data, data.size) } returns data.size + // second key/value + every { plugin.getInputStreamForRecord(token, packageInfo, key264) } returns inputStream2 + every { headerReader.readVersion(inputStream2) } returns VERSION + every { crypto.decryptHeader(inputStream2, VERSION, packageInfo.packageName, key2) } returns versionHeader2 + every { crypto.decryptSegment(inputStream2) } returns data2 + every { output.writeEntityHeader(key2, data2.size) } returns 42 + every { output.writeEntityData(data2, data2.size) } returns data2.size + every { inputStream2.close() } just Runs + streamsGetClosed() + + assertEquals(TRANSPORT_OK, restore.getRestoreData(fileDescriptor)) + } + + private fun getRecordsAndOutput(recordKeys: List<String> = listOf(key64)) { + every { plugin.listRecords(token, packageInfo) } returns recordKeys + every { outputFactory.getBackupDataOutput(fileDescriptor) } returns output + } + + private fun streamsGetClosed() { + every { inputStream.close() } just Runs + every { fileDescriptor.close() } just Runs + } + + private fun verifyStreamWasClosed() { + verifyAll { + inputStream.close() + fileDescriptor.close() + } + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreCoordinatorTest.kt new file mode 100644 index 00000000..10aa6cb0 --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreCoordinatorTest.kt @@ -0,0 +1,189 @@ +package com.stevesoltys.backup.transport.restore + +import android.app.backup.BackupTransport.TRANSPORT_OK +import android.app.backup.RestoreDescription +import android.app.backup.RestoreDescription.* +import android.app.backup.RestoreSet +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import com.stevesoltys.backup.transport.TransportTest +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.io.IOException +import kotlin.random.Random + +internal class RestoreCoordinatorTest : TransportTest() { + + private val plugin = mockk<RestorePlugin>() + private val kv = mockk<KVRestore>() + private val full = mockk<FullRestore>() + + private val restore = RestoreCoordinator(plugin, kv, full) + + private val token = Random.nextLong() + private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" } + private val packageInfoArray = arrayOf(packageInfo) + private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2) + + @Test + fun `getAvailableRestoreSets() delegates to plugin`() { + val restoreSets = Array(1) { RestoreSet() } + + every { plugin.getAvailableRestoreSets() } returns restoreSets + + assertEquals(restoreSets, restore.getAvailableRestoreSets()) + } + + @Test + fun `getCurrentRestoreSet() delegates to plugin`() { + val currentRestoreSet = Random.nextLong() + + every { plugin.getCurrentRestoreSet() } returns currentRestoreSet + + assertEquals(currentRestoreSet, restore.getCurrentRestoreSet()) + } + + @Test + fun `startRestore() returns OK`() { + assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) + } + + @Test + fun `startRestore() can not be called twice`() { + assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) + assertThrows(IllegalStateException::class.javaObjectType) { + restore.startRestore(token, packageInfoArray) + } + } + + @Test + fun `nextRestorePackage() throws without startRestore()`() { + assertThrows(IllegalStateException::class.javaObjectType) { + restore.nextRestorePackage() + } + } + + @Test + fun `nextRestorePackage() returns KV description and takes precedence`() { + restore.startRestore(token, packageInfoArray) + + every { kv.hasDataForPackage(token, packageInfo) } returns true + every { kv.initializeState(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`() { + restore.startRestore(token, packageInfoArray) + + every { kv.hasDataForPackage(token, packageInfo) } returns false + every { full.hasDataForPackage(token, packageInfo) } returns true + every { full.initializeState(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`() { + restore.startRestore(token, packageInfoArray) + + every { kv.hasDataForPackage(token, packageInfo) } returns false + every { full.hasDataForPackage(token, packageInfo) } returns false + + assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage()) + } + + @Test + fun `nextRestorePackage() returns all packages from startRestore()`() { + restore.startRestore(token, packageInfoArray2) + + every { kv.hasDataForPackage(token, packageInfo) } returns true + every { kv.initializeState(token, packageInfo) } just Runs + + val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE) + assertEquals(expected, restore.nextRestorePackage()) + + every { kv.hasDataForPackage(token, packageInfo2) } returns false + every { full.hasDataForPackage(token, packageInfo2) } returns true + every { full.initializeState(token, packageInfo2) } just Runs + + val expected2 = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM) + assertEquals(expected2, restore.nextRestorePackage()) + + assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage()) + } + + @Test + fun `when kv#hasDataForPackage() throws return null`() { + restore.startRestore(token, packageInfoArray) + + every { kv.hasDataForPackage(token, packageInfo) } throws IOException() + + assertNull(restore.nextRestorePackage()) + } + + @Test + fun `when full#hasDataForPackage() throws return null`() { + restore.startRestore(token, packageInfoArray) + + every { kv.hasDataForPackage(token, packageInfo) } returns false + every { full.hasDataForPackage(token, packageInfo) } throws IOException() + + assertNull(restore.nextRestorePackage()) + } + + @Test + fun `getRestoreData() delegates to KV`() { + val data = mockk<ParcelFileDescriptor>() + val result = Random.nextInt() + + every { kv.getRestoreData(data) } returns result + + assertEquals(result, restore.getRestoreData(data)) + } + + @Test + fun `getNextFullRestoreDataChunk() delegates to Full`() { + val data = mockk<ParcelFileDescriptor>() + val result = Random.nextInt() + + every { full.getNextFullRestoreDataChunk(data) } returns result + + assertEquals(result, restore.getNextFullRestoreDataChunk(data)) + } + + @Test + fun `abortFullRestore() delegates to Full`() { + val result = Random.nextInt() + + every { full.abortFullRestore() } returns result + + assertEquals(result, restore.abortFullRestore()) + } + + @Test + fun `finishRestore() delegates to Full if it has state`() { + val hasState = Random.nextBoolean() + + every { full.hasState() } returns hasState + if (hasState) { + every { full.finishRestore() } just Runs + } + + restore.finishRestore() + } + + private fun assertEquals(expected: RestoreDescription, actual: RestoreDescription?) { + assertNotNull(actual) + assertEquals(expected.packageName, actual?.packageName) + assertEquals(expected.dataType, actual?.dataType) + } + +} diff --git a/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreTest.kt b/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreTest.kt new file mode 100644 index 00000000..577f3a6c --- /dev/null +++ b/app/src/test/java/com/stevesoltys/backup/transport/restore/RestoreTest.kt @@ -0,0 +1,24 @@ +package com.stevesoltys.backup.transport.restore + +import android.os.ParcelFileDescriptor +import com.stevesoltys.backup.getRandomByteArray +import com.stevesoltys.backup.transport.TransportTest +import com.stevesoltys.backup.header.HeaderReader +import com.stevesoltys.backup.header.VERSION +import io.mockk.mockk +import java.io.InputStream +import kotlin.random.Random + +internal abstract class RestoreTest : TransportTest() { + + protected val outputFactory = mockk<OutputFactory>() + protected val headerReader = mockk<HeaderReader>() + protected val fileDescriptor = mockk<ParcelFileDescriptor>() + + protected val token = Random.nextLong() + protected val data = getRandomByteArray() + protected val inputStream = mockk<InputStream>() + + protected val unsupportedVersion = (VERSION + 1).toByte() + +} diff --git a/build.gradle b/build.gradle index aee7f914..26dd7758 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ allprojects { mavenCentral() jcenter() google() + maven { url 'https://jitpack.io' } } }