Merge "Ports part of the backup/encryption/chunking code from gmscore."
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
new file mode 100644
index 0000000..df36c94
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.chunk;
+
+import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.EXPLICIT_STARTS;
+import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.INLINE_LENGTHS;
+
+import android.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** IntDef corresponding to the ChunkOrderingType enum in the ChunksMetadataProto protobuf. */
+@IntDef({CHUNK_ORDERING_TYPE_UNSPECIFIED, EXPLICIT_STARTS, INLINE_LENGTHS})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ChunkOrderingType {}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
new file mode 100644
index 0000000..68d9d14
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import java.io.IOException;
+
+/** Writes backup data either as a diff script or as raw data, determined by the implementation. */
+public interface BackupWriter {
+ /** Writes the given bytes to the output. */
+ void writeBytes(byte[] bytes) throws IOException;
+
+ /**
+ * Writes an existing chunk from the previous backup to the output.
+ *
+ * <p>Note: not all implementations support this method.
+ */
+ void writeChunk(long start, int length) throws IOException;
+
+ /** Returns the number of bytes written, included bytes copied from the old file. */
+ long getBytesWritten();
+
+ /**
+ * Indicates that no more bytes or chunks will be written.
+ *
+ * <p>After calling this, you may not call {@link #writeBytes(byte[])} or {@link
+ * #writeChunk(long, int)}
+ */
+ void flush() throws IOException;
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
new file mode 100644
index 0000000..1f936eb
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A chunk of a file encrypted using AES/GCM.
+ *
+ * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
+ * encryptedBytes(), key() and nonce().
+ */
+public class EncryptedChunk {
+ public static final int KEY_LENGTH_BYTES = ChunkHash.HASH_LENGTH_BYTES;
+ public static final int NONCE_LENGTH_BYTES = 12;
+
+ /**
+ * Constructs a new instance with the given key, nonce, and encrypted bytes.
+ *
+ * @param key SHA-256 Hmac of the chunk plaintext.
+ * @param nonce Nonce with which the bytes of the chunk were encrypted.
+ * @param encryptedBytes Encrypted bytes of the chunk.
+ */
+ public static EncryptedChunk create(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
+ Preconditions.checkArgument(
+ nonce.length == NONCE_LENGTH_BYTES, "Nonce does not have the correct length.");
+ return new EncryptedChunk(key, nonce, encryptedBytes);
+ }
+
+ private ChunkHash mKey;
+ private byte[] mNonce;
+ private byte[] mEncryptedBytes;
+
+ private EncryptedChunk(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
+ mKey = key;
+ mNonce = nonce;
+ mEncryptedBytes = encryptedBytes;
+ }
+
+ /** The SHA-256 Hmac of the plaintext bytes of the chunk. */
+ public ChunkHash key() {
+ return mKey;
+ }
+
+ /** The nonce with which the chunk was encrypted. */
+ public byte[] nonce() {
+ return mNonce;
+ }
+
+ /** The encrypted bytes of the chunk. */
+ public byte[] encryptedBytes() {
+ return mEncryptedBytes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof EncryptedChunk)) {
+ return false;
+ }
+
+ EncryptedChunk encryptedChunkOrdering = (EncryptedChunk) o;
+ return Arrays.equals(mEncryptedBytes, encryptedChunkOrdering.mEncryptedBytes)
+ && Arrays.equals(mNonce, encryptedChunkOrdering.mNonce)
+ && mKey.equals(encryptedChunkOrdering.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, Arrays.hashCode(mNonce), Arrays.hashCode(mEncryptedBytes));
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
new file mode 100644
index 0000000..eaf701c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+import java.io.IOException;
+
+/** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */
+public interface EncryptedChunkEncoder {
+ /**
+ * Encodes the given chunk and asks the writer to write it.
+ *
+ * <p>The chunk will be encoded in the format [nonce]+[encrypted data].
+ *
+ * <p>TODO(b/116575321): Choose a more descriptive method name after the code move is done.
+ */
+ void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException;
+
+ /**
+ * Returns the length in bytes that this chunk would be if encoded with {@link
+ * #writeChunkToWriter}.
+ */
+ int getEncodedLengthOfChunk(EncryptedChunk chunk);
+
+ /**
+ * Returns the {@link ChunkOrderingType} that must be included in the backup file, when using
+ * this decoder, so that the file may be correctly decoded.
+ */
+ @ChunkOrderingType
+ int getChunkOrderingType();
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
new file mode 100644
index 0000000..5c902ca
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import java.io.IOException;
+
+/**
+ * Encodes an {@link EncryptedChunk} as bytes, prepending the length of the chunk.
+ *
+ * <p>This allows us to decode the backup file during restore without any extra information about
+ * the boundaries of the chunks. The backup file should contain a chunk ordering in mode {@link
+ * ChunksMetadataProto#INLINE_LENGTHS}.
+ *
+ * <p>We use this implementation during key value backup.
+ */
+public class InlineLengthsEncryptedChunkEncoder implements EncryptedChunkEncoder {
+ public static final int BYTES_LENGTH = Integer.SIZE / Byte.SIZE;
+
+ private final LengthlessEncryptedChunkEncoder mLengthlessEncryptedChunkEncoder =
+ new LengthlessEncryptedChunkEncoder();
+
+ @Override
+ public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
+ int length = mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
+ writer.writeBytes(toByteArray(length));
+ mLengthlessEncryptedChunkEncoder.writeChunkToWriter(writer, chunk);
+ }
+
+ @Override
+ public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
+ return BYTES_LENGTH + mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
+ }
+
+ @Override
+ @ChunkOrderingType
+ public int getChunkOrderingType() {
+ return ChunksMetadataProto.INLINE_LENGTHS;
+ }
+
+ /**
+ * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to
+ * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code
+ * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}.
+ *
+ * <p>Equivalent to guava's Ints.toByteArray.
+ */
+ static byte[] toByteArray(int value) {
+ return new byte[] {
+ (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value
+ };
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
new file mode 100644
index 0000000..4b84981
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import java.io.IOException;
+
+/**
+ * Encodes an {@link EncryptedChunk} as bytes without including any information about the length of
+ * the chunk.
+ *
+ * <p>In order for us to decode the backup file during restore it must include a chunk ordering in
+ * mode {@link ChunksMetadataProto#EXPLICIT_STARTS}, which contains the boundaries of the chunks in
+ * the encrypted file. This information allows us to decode the backup file and divide it into
+ * chunks without including the length of each chunk inline.
+ *
+ * <p>We use this implementation during full backup.
+ */
+public class LengthlessEncryptedChunkEncoder implements EncryptedChunkEncoder {
+ @Override
+ public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
+ writer.writeBytes(chunk.nonce());
+ writer.writeBytes(chunk.encryptedBytes());
+ }
+
+ @Override
+ public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
+ return chunk.nonce().length + chunk.encryptedBytes().length;
+ }
+
+ @Override
+ @ChunkOrderingType
+ public int getChunkOrderingType() {
+ return ChunksMetadataProto.EXPLICIT_STARTS;
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
new file mode 100644
index 0000000..c5f9b10
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import com.google.common.primitives.Bytes;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class EncryptedChunkTest {
+ private static final byte[] CHUNK_HASH_1_BYTES =
+ Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES);
+ private static final byte[] NONCE_1 =
+ Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES);
+ private static final byte[] ENCRYPTED_BYTES_1 =
+ Arrays.copyOf(new byte[] {3}, EncryptedChunk.KEY_LENGTH_BYTES);
+
+ private static final byte[] CHUNK_HASH_2_BYTES =
+ Arrays.copyOf(new byte[] {4}, ChunkHash.HASH_LENGTH_BYTES);
+ private static final byte[] NONCE_2 =
+ Arrays.copyOf(new byte[] {5}, EncryptedChunk.NONCE_LENGTH_BYTES);
+ private static final byte[] ENCRYPTED_BYTES_2 =
+ Arrays.copyOf(new byte[] {6}, EncryptedChunk.KEY_LENGTH_BYTES);
+
+ @Test
+ public void testCreate_withIncorrectLength_throwsException() {
+ ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+ byte[] shortNonce = Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES - 1);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> EncryptedChunk.create(chunkHash, shortNonce, ENCRYPTED_BYTES_1));
+ }
+
+ @Test
+ public void testEncryptedBytes_forNewlyCreatedObject_returnsCorrectValue() {
+ ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+ EncryptedChunk encryptedChunk =
+ EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+ byte[] returnedBytes = encryptedChunk.encryptedBytes();
+
+ assertThat(returnedBytes)
+ .asList()
+ .containsExactlyElementsIn(Bytes.asList(ENCRYPTED_BYTES_1))
+ .inOrder();
+ }
+
+ @Test
+ public void testKey_forNewlyCreatedObject_returnsCorrectValue() {
+ ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+ EncryptedChunk encryptedChunk =
+ EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+ ChunkHash returnedKey = encryptedChunk.key();
+
+ assertThat(returnedKey).isEqualTo(chunkHash);
+ }
+
+ @Test
+ public void testNonce_forNewlycreatedObject_returnCorrectValue() {
+ ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+ EncryptedChunk encryptedChunk =
+ EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+ byte[] returnedNonce = encryptedChunk.nonce();
+
+ assertThat(returnedNonce).asList().containsExactlyElementsIn(Bytes.asList(NONCE_1));
+ }
+
+ @Test
+ public void testEquals() {
+ ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+ ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+ ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
+ EncryptedChunk encryptedChunk1 =
+ EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+ EncryptedChunk equalEncryptedChunk1 =
+ EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+ EncryptedChunk encryptedChunk2 =
+ EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
+
+ assertThat(encryptedChunk1).isEqualTo(equalEncryptedChunk1);
+ assertThat(encryptedChunk1).isNotEqualTo(encryptedChunk2);
+ }
+
+ @Test
+ public void testHashCode() {
+ ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+ ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+ ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
+ EncryptedChunk encryptedChunk1 =
+ EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+ EncryptedChunk equalEncryptedChunk1 =
+ EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+ EncryptedChunk encryptedChunk2 =
+ EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
+
+ int hash1 = encryptedChunk1.hashCode();
+ int equalHash1 = equalEncryptedChunk1.hashCode();
+ int hash2 = encryptedChunk2.hashCode();
+
+ assertThat(hash1).isEqualTo(equalHash1);
+ assertThat(hash1).isNotEqualTo(hash2);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
new file mode 100644
index 0000000..b162557
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class InlineLengthsEncryptedChunkEncoderTest {
+
+ private static final byte[] TEST_NONCE =
+ Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
+ private static final byte[] TEST_KEY_DATA =
+ Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
+ private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
+
+ @Mock private BackupWriter mMockBackupWriter;
+ private ChunkHash mTestKey;
+ private EncryptedChunk mTestChunk;
+ private EncryptedChunkEncoder mEncoder;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockBackupWriter = mock(BackupWriter.class);
+ mTestKey = new ChunkHash(TEST_KEY_DATA);
+ mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
+ mEncoder = new InlineLengthsEncryptedChunkEncoder();
+ }
+
+ @Test
+ public void writeChunkToWriter_writesLengthThenNonceThenData() throws Exception {
+ mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
+
+ InOrder inOrder = inOrder(mMockBackupWriter);
+ inOrder.verify(mMockBackupWriter)
+ .writeBytes(
+ InlineLengthsEncryptedChunkEncoder.toByteArray(
+ TEST_NONCE.length + TEST_DATA.length));
+ inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
+ inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
+ }
+
+ @Test
+ public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
+ int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
+
+ assertThat(encodedLength).isEqualTo(Integer.BYTES + TEST_NONCE.length + TEST_DATA.length);
+ }
+
+ @Test
+ public void getChunkOrderingType_returnsExplicitStartsType() {
+ assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.INLINE_LENGTHS);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
new file mode 100644
index 0000000..b61dbe9
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class LengthlessEncryptedChunkEncoderTest {
+ private static final byte[] TEST_NONCE =
+ Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
+ private static final byte[] TEST_KEY_DATA =
+ Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
+ private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
+
+ @Mock private BackupWriter mMockBackupWriter;
+ private ChunkHash mTestKey;
+ private EncryptedChunk mTestChunk;
+ private EncryptedChunkEncoder mEncoder;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockBackupWriter = mock(BackupWriter.class);
+ mTestKey = new ChunkHash(TEST_KEY_DATA);
+ mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
+ mEncoder = new LengthlessEncryptedChunkEncoder();
+ }
+
+ @Test
+ public void writeChunkToWriter_writesNonceThenData() throws Exception {
+ mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
+
+ InOrder inOrder = inOrder(mMockBackupWriter);
+ inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
+ inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
+ }
+
+ @Test
+ public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
+ int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
+
+ assertThat(encodedLength).isEqualTo(TEST_NONCE.length + TEST_DATA.length);
+ }
+
+ @Test
+ public void getChunkOrderingType_returnsExplicitStartsType() throws Exception {
+ assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.EXPLICIT_STARTS);
+ }
+}