Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea5c362d authored by Al Sutton's avatar Al Sutton Committed by Android (Google) Code Review
Browse files

Merge "Import FullRestoreToFileTask"

parents 8873b682 8611f147
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+87 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+128 −0
Original line number Diff line number Diff line
/*
 * 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.
        }
    }
}
+31 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}