diff options
author | Al Sutton <alsutton@google.com> | 2019-09-27 07:58:40 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-09-27 07:58:40 +0000 |
commit | ea5c362d631dcd67748b54f4a5c1dbe908cee4ef (patch) | |
tree | b48b0826bd1501b4f061cacfca6f3f3dbd3db0f7 | |
parent | 8873b682ea8b52ec27f1209e711ed8745573bb4e (diff) | |
parent | 8611f1471aee351cace10892b1f8f47927ed0ae7 (diff) |
Merge "Import FullRestoreToFileTask"
4 files changed, 293 insertions, 0 deletions
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java new file mode 100644 index 000000000000..afcca79a0027 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption; + +import java.io.IOException; + +/** Interface for classes which will provide backup data */ +public abstract class FullRestoreDownloader { + /** Enum to provide information on why a download finished */ + public enum FinishType { + UNKNOWN_FINISH(0), + // Finish the downloading and successfully write data to Android OS. + FINISHED(1), + // Download failed with any kind of exception. + TRANSFER_FAILURE(2), + // Download failed due to auth failure on the device. + AUTH_FAILURE(3), + // Aborted by Android Framework. + FRAMEWORK_ABORTED(4); + + private int mValue; + + FinishType(int value) { + mValue = value; + } + } + + /** Get the next data chunk from the backing store */ + public abstract int readNextChunk(byte[] buffer) throws IOException; + + /** Called when we've finished restoring the data */ + public abstract void finish(FinishType finishType); +} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java new file mode 100644 index 000000000000..82f83f9b7494 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.tasks; + +import static com.android.internal.util.Preconditions.checkArgument; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.backup.encryption.FullRestoreDownloader; +import com.android.server.backup.encryption.FullRestoreDownloader.FinishType; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Reads a stream from a {@link FullRestoreDownloader} and writes it to a file for consumption by + * {@link BackupFileDecryptorTask}. + */ +public class FullRestoreToFileTask { + /** + * Maximum number of bytes which the framework can request from the full restore data stream in + * one call to {@link BackupTransport#getNextFullRestoreDataChunk}. + */ + public static final int MAX_BYTES_FULL_RESTORE_CHUNK = 1024 * 32; + + /** Returned when the end of a backup stream has been reached. */ + private static final int END_OF_STREAM = -1; + + private final FullRestoreDownloader mFullRestoreDownloader; + private final int mBufferSize; + + /** + * Constructs a new instance which reads from the given package wrapper, using a buffer of size + * {@link #MAX_BYTES_FULL_RESTORE_CHUNK}. + */ + public FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader) { + this(fullRestoreDownloader, MAX_BYTES_FULL_RESTORE_CHUNK); + } + + @VisibleForTesting + FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader, int bufferSize) { + checkArgument(bufferSize > 0, "Buffer must have positive size"); + + this.mFullRestoreDownloader = fullRestoreDownloader; + this.mBufferSize = bufferSize; + } + + /** + * Downloads the backup file from the server and writes it to the given file. + * + * <p>At the end of the download (success or failure), closes the connection and sends a + * Clearcut log. + */ + public void restoreToFile(File targetFile) throws IOException { + try (BufferedOutputStream outputStream = + new BufferedOutputStream(new FileOutputStream(targetFile))) { + byte[] buffer = new byte[mBufferSize]; + int bytesRead = mFullRestoreDownloader.readNextChunk(buffer); + while (bytesRead != END_OF_STREAM) { + outputStream.write(buffer, /* off=*/ 0, bytesRead); + bytesRead = mFullRestoreDownloader.readNextChunk(buffer); + } + + outputStream.flush(); + + mFullRestoreDownloader.finish(FinishType.FINISHED); + } catch (IOException e) { + mFullRestoreDownloader.finish(FinishType.TRANSFER_FAILURE); + throw e; + } + } +} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java new file mode 100644 index 000000000000..de8b7340ebce --- /dev/null +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.encryption.tasks; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; + +import android.platform.test.annotations.Presubmit; + +import com.android.server.backup.encryption.FullRestoreDownloader; +import com.android.server.backup.encryption.FullRestoreDownloader.FinishType; + +import com.google.common.io.Files; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Random; + +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class FullRestoreToFileTaskTest { + private static final int TEST_RANDOM_SEED = 34; + private static final int TEST_MAX_CHUNK_SIZE_BYTES = 5; + private static final int TEST_DATA_LENGTH_BYTES = TEST_MAX_CHUNK_SIZE_BYTES * 20; + + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + + private byte[] mTestData; + private File mTargetFile; + private FakeFullRestoreDownloader mFakeFullRestoreDownloader; + @Mock private FullRestoreDownloader mMockFullRestoreDownloader; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTargetFile = mTemporaryFolder.newFile(); + + mTestData = new byte[TEST_DATA_LENGTH_BYTES]; + new Random(TEST_RANDOM_SEED).nextBytes(mTestData); + mFakeFullRestoreDownloader = new FakeFullRestoreDownloader(mTestData); + } + + private FullRestoreToFileTask createTaskWithFakeDownloader() { + return new FullRestoreToFileTask(mFakeFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES); + } + + private FullRestoreToFileTask createTaskWithMockDownloader() { + return new FullRestoreToFileTask(mMockFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES); + } + + @Test + public void restoreToFile_readsDataAndWritesToFile() throws Exception { + FullRestoreToFileTask task = createTaskWithFakeDownloader(); + task.restoreToFile(mTargetFile); + assertThat(Files.toByteArray(mTargetFile)).isEqualTo(mTestData); + } + + @Test + public void restoreToFile_noErrors_closesDownloaderWithFinished() throws Exception { + FullRestoreToFileTask task = createTaskWithMockDownloader(); + when(mMockFullRestoreDownloader.readNextChunk(any())).thenReturn(-1); + + task.restoreToFile(mTargetFile); + + verify(mMockFullRestoreDownloader).finish(FinishType.FINISHED); + } + + @Test + public void restoreToFile_ioException_closesDownloaderWithTransferFailure() throws Exception { + FullRestoreToFileTask task = createTaskWithMockDownloader(); + when(mMockFullRestoreDownloader.readNextChunk(any())).thenThrow(IOException.class); + + assertThrows(IOException.class, () -> task.restoreToFile(mTargetFile)); + + verify(mMockFullRestoreDownloader).finish(FinishType.TRANSFER_FAILURE); + } + + /** Fake package wrapper which returns data from a byte array. */ + private static class FakeFullRestoreDownloader extends FullRestoreDownloader { + + private final ByteArrayInputStream mData; + + FakeFullRestoreDownloader(byte[] data) { + // We override all methods of the superclass, so it does not require any collaborators. + super(); + this.mData = new ByteArrayInputStream(data); + } + + @Override + public int readNextChunk(byte[] buffer) throws IOException { + return mData.read(buffer); + } + + @Override + public void finish(FinishType finishType) { + // Do nothing. + } + } +} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java new file mode 100644 index 000000000000..e5d73ba41902 --- /dev/null +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.common.io.ByteStreams; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** Utility methods for use in tests */ +public class TestFileUtils { + /** Read the contents of a file into a byte array */ + public static byte[] toByteArray(File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + return ByteStreams.toByteArray(fis); + } + } +} |