Move chunk proto definitions into the BackupEncrypter APK
Bug: 111386661 Test: make RunBackupEncryptionRoboTests Change-Id: I3032210e6eec52b0c925baab770a4578ac7ecbf7
This commit is contained in:
136
packages/BackupEncryption/proto/backup_chunks_metadata.proto
Normal file
136
packages/BackupEncryption/proto/backup_chunks_metadata.proto
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package android_backup_crypto;
|
||||
|
||||
option java_package = "com.android.server.backup.encryption.protos";
|
||||
option java_outer_classname = "ChunksMetadataProto";
|
||||
|
||||
// Cipher type with which the chunks are encrypted. For now we only support AES/GCM/NoPadding, but
|
||||
// this is for backwards-compatibility in case we need to change the default Cipher in the future.
|
||||
enum CipherType {
|
||||
UNKNOWN_CIPHER_TYPE = 0;
|
||||
// Chunk is prefixed with a 12-byte nonce. The tag length is 16 bytes.
|
||||
AES_256_GCM = 1;
|
||||
}
|
||||
|
||||
// Checksum type with which the plaintext is verified.
|
||||
enum ChecksumType {
|
||||
UNKNOWN_CHECKSUM_TYPE = 0;
|
||||
SHA_256 = 1;
|
||||
}
|
||||
|
||||
enum ChunkOrderingType {
|
||||
CHUNK_ORDERING_TYPE_UNSPECIFIED = 0;
|
||||
// The chunk ordering contains a list of the start position of each chunk in the encrypted file,
|
||||
// ordered as in the plaintext file. This allows us to recreate the original plaintext file
|
||||
// during decryption. We use this mode for full backups where the order of the data in the file
|
||||
// is important.
|
||||
EXPLICIT_STARTS = 1;
|
||||
// The chunk ordering does not contain any start positions, and instead each encrypted chunk in
|
||||
// the backup file is prefixed with its length. This allows us to decrypt each chunk but does
|
||||
// not give any information about the order. However, we use this mode for key value backups
|
||||
// where the order does not matter.
|
||||
INLINE_LENGTHS = 2;
|
||||
}
|
||||
|
||||
// Chunk entry (for local state)
|
||||
message Chunk {
|
||||
// SHA-256 MAC of the plaintext of the chunk
|
||||
optional bytes hash = 1;
|
||||
// Number of bytes in encrypted chunk
|
||||
optional int32 length = 2;
|
||||
}
|
||||
|
||||
// List of the chunks in the blob, along with the length of each chunk. From this is it possible to
|
||||
// extract individual chunks. (i.e., start position is equal to the sum of the lengths of all
|
||||
// preceding chunks.)
|
||||
//
|
||||
// This is local state stored on the device. It is never sent to the backup server. See
|
||||
// ChunkOrdering for how the device restores the chunks in the correct order.
|
||||
// Next tag : 6
|
||||
message ChunkListing {
|
||||
repeated Chunk chunks = 1;
|
||||
|
||||
// Cipher algorithm with which the chunks are encrypted.
|
||||
optional CipherType cipher_type = 2;
|
||||
|
||||
// Defines the type of chunk order used to encode the backup file on the server, so that we can
|
||||
// consistently use the same type between backups. If unspecified this backup file was created
|
||||
// before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
|
||||
optional ChunkOrderingType chunk_ordering_type = 5;
|
||||
|
||||
// The document ID returned from Scotty server after uploading the blob associated with this
|
||||
// listing. This needs to be sent when uploading new diff scripts.
|
||||
optional string document_id = 3;
|
||||
|
||||
// Fingerprint mixer salt used for content defined chunking. This is randomly generated for each
|
||||
// package during the initial non-incremental backup and reused for incremental backups.
|
||||
optional bytes fingerprint_mixer_salt = 4;
|
||||
}
|
||||
|
||||
// Ordering information about plaintext and checksum. This is used on restore to reconstruct the
|
||||
// blob in its correct order. (The chunk order is randomized so as to give the server less
|
||||
// information about which parts of the backup are changing over time.) This proto is encrypted
|
||||
// before being uploaded to the server, with a key unknown to the server.
|
||||
message ChunkOrdering {
|
||||
// For backups where ChunksMetadata#chunk_ordering_type = EXPLICIT STARTS:
|
||||
// Ordered start positions of chunks. i.e., the file is the chunk starting at this position,
|
||||
// followed by the chunk starting at this position, followed by ... etc. You can compute the
|
||||
// lengths of the chunks by sorting this list then looking at the start position of the next
|
||||
// chunk after the chunk you care about. This is guaranteed to work as all chunks are
|
||||
// represented in this list.
|
||||
//
|
||||
// For backups where ChunksMetadata#chunk_ordering_type = INLINE_LENGTHS:
|
||||
// This field is unused. See ChunkOrderingType#INLINE_LENGTHS.
|
||||
repeated int32 starts = 1 [packed = true];
|
||||
|
||||
// Checksum of plaintext content. (i.e., in correct order.)
|
||||
//
|
||||
// Each chunk also has a MAC, as generated by GCM, so this is NOT Mac-then-Encrypt, which has
|
||||
// security implications. This is an additional checksum to verify that once the chunks have
|
||||
// been reordered, that the file matches the expected plaintext. This prevents the device
|
||||
// restoring garbage data in case of a mismatch between the ChunkOrdering and the backup blob.
|
||||
optional bytes checksum = 2;
|
||||
}
|
||||
|
||||
// Additional metadata about a backup blob that needs to be synced to the server. This is used on
|
||||
// restore to reconstruct the blob in its correct order. (The chunk order is randomized so as to
|
||||
// give the server less information about which parts of the backup are changing over time.) This
|
||||
// data structure is only ever uploaded to the server encrypted with a key unknown to the server.
|
||||
// Next tag : 6
|
||||
message ChunksMetadata {
|
||||
// Cipher algorithm with which the chunk listing and chunks are encrypted.
|
||||
optional CipherType cipher_type = 1;
|
||||
|
||||
// Defines the type of chunk order this metadata contains. If unspecified this backup file was
|
||||
// created before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
|
||||
optional ChunkOrderingType chunk_ordering_type = 5
|
||||
[default = CHUNK_ORDERING_TYPE_UNSPECIFIED];
|
||||
|
||||
// Encrypted bytes of ChunkOrdering
|
||||
optional bytes chunk_ordering = 2;
|
||||
|
||||
// The type of algorithm used for the checksum of the plaintext. (See ChunkOrdering.) This is
|
||||
// for forwards compatibility in case we change the algorithm in the future. For now, always
|
||||
// SHA-256.
|
||||
optional ChecksumType checksum_type = 3;
|
||||
|
||||
// This used to be the plaintext tertiary key. No longer used.
|
||||
reserved 4;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.proto.ProtoInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Information about a chunk entry in a protobuf. Only used for reading from a {@link
|
||||
* ProtoInputStream}.
|
||||
*/
|
||||
public class Chunk {
|
||||
/**
|
||||
* Reads a Chunk from a {@link ProtoInputStream}. Expects the message to be of format {@link
|
||||
* ChunksMetadataProto.Chunk}.
|
||||
*
|
||||
* @param inputStream currently at a {@link ChunksMetadataProto.Chunk} message.
|
||||
* @throws IOException when the message is not structured as expected or a field can not be
|
||||
* read.
|
||||
*/
|
||||
static Chunk readFromProto(ProtoInputStream inputStream) throws IOException {
|
||||
Chunk result = new Chunk();
|
||||
|
||||
while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (inputStream.getFieldNumber()) {
|
||||
case (int) ChunksMetadataProto.Chunk.HASH:
|
||||
result.mHash = inputStream.readBytes(ChunksMetadataProto.Chunk.HASH);
|
||||
break;
|
||||
case (int) ChunksMetadataProto.Chunk.LENGTH:
|
||||
result.mLength = inputStream.readInt(ChunksMetadataProto.Chunk.LENGTH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int mLength;
|
||||
private byte[] mHash;
|
||||
|
||||
/** Private constructor. This class should only be instantiated by calling readFromProto. */
|
||||
private Chunk() {
|
||||
// Set default values for fields in case they are not available in the proto.
|
||||
mHash = new byte[]{};
|
||||
mLength = 0;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return mHash;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.chunk;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Chunk listing in a format optimized for quick look-up of chunks via their hash keys. This is
|
||||
* useful when building an incremental backup. After a chunk has been produced, the algorithm can
|
||||
* quickly look up whether the chunk existed in the previous backup by checking this chunk listing.
|
||||
* It can then tell the server to use that chunk, through telling it the position and length of the
|
||||
* chunk in the previous backup's blob.
|
||||
*/
|
||||
public class ChunkListingMap {
|
||||
/**
|
||||
* Reads a ChunkListingMap from a {@link ProtoInputStream}. Expects the message to be of format
|
||||
* {@link ChunksMetadataProto.ChunkListing}.
|
||||
*
|
||||
* @param inputStream Currently at a {@link ChunksMetadataProto.ChunkListing} message.
|
||||
* @throws IOException when the message is not structured as expected or a field can not be
|
||||
* read.
|
||||
*/
|
||||
public static ChunkListingMap readFromProto(ProtoInputStream inputStream) throws IOException {
|
||||
Map<ChunkHash, Entry> entries = new HashMap();
|
||||
|
||||
long start = 0;
|
||||
|
||||
while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
if (inputStream.getFieldNumber() == (int) ChunksMetadataProto.ChunkListing.CHUNKS) {
|
||||
long chunkToken = inputStream.start(ChunksMetadataProto.ChunkListing.CHUNKS);
|
||||
Chunk chunk = Chunk.readFromProto(inputStream);
|
||||
entries.put(new ChunkHash(chunk.getHash()), new Entry(start, chunk.getLength()));
|
||||
start += chunk.getLength();
|
||||
inputStream.end(chunkToken);
|
||||
}
|
||||
}
|
||||
|
||||
return new ChunkListingMap(entries);
|
||||
}
|
||||
|
||||
private final Map<ChunkHash, Entry> mChunksByHash;
|
||||
|
||||
private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
|
||||
mChunksByHash = Collections.unmodifiableMap(new HashMap<>(chunksByHash));
|
||||
}
|
||||
|
||||
/** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */
|
||||
public boolean hasChunk(ChunkHash hash) {
|
||||
return mChunksByHash.containsKey(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry for the chunk with the given hash.
|
||||
*
|
||||
* @param hash The SHA-256 MAC of the plaintext of the chunk.
|
||||
* @return The entry, containing position and length of the chunk in the backup blob, or null if
|
||||
* it does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
public Entry getChunkEntry(ChunkHash hash) {
|
||||
return mChunksByHash.get(hash);
|
||||
}
|
||||
|
||||
/** Returns the number of chunks in this listing. */
|
||||
public int getChunkCount() {
|
||||
return mChunksByHash.size();
|
||||
}
|
||||
|
||||
/** Information about a chunk entry in a backup blob - i.e., its position and length. */
|
||||
public static final class Entry {
|
||||
private final int mLength;
|
||||
private final long mStart;
|
||||
|
||||
private Entry(long start, int length) {
|
||||
mStart = start;
|
||||
mLength = length;
|
||||
}
|
||||
|
||||
/** Returns the length of the chunk in bytes. */
|
||||
public int getLength() {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
/** Returns the start position of the chunk in the backup blob, in bytes. */
|
||||
public long getStart() {
|
||||
return mStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
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 static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
|
||||
import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.EXPLICIT_STARTS;
|
||||
import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.INLINE_LENGTHS;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.server.backup.encryption.chunking;
|
||||
|
||||
import com.android.server.backup.encryption.chunk.ChunkOrderingType;
|
||||
import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
|
||||
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.server.backup.encryption.chunking;
|
||||
|
||||
import com.android.server.backup.encryption.chunk.ChunkOrderingType;
|
||||
import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
|
||||
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* 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.chunk;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Presubmit
|
||||
public class ChunkListingMapTest {
|
||||
private static final String CHUNK_A = "CHUNK_A";
|
||||
private static final String CHUNK_B = "CHUNK_B";
|
||||
private static final String CHUNK_C = "CHUNK_C";
|
||||
|
||||
private static final int CHUNK_A_LENGTH = 256;
|
||||
private static final int CHUNK_B_LENGTH = 1024;
|
||||
private static final int CHUNK_C_LENGTH = 4055;
|
||||
|
||||
private ChunkHash mChunkHashA;
|
||||
private ChunkHash mChunkHashB;
|
||||
private ChunkHash mChunkHashC;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mChunkHashA = getHash(CHUNK_A);
|
||||
mChunkHashB = getHash(CHUNK_B);
|
||||
mChunkHashC = getHash(CHUNK_C);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasChunk_whenChunkInListing_returnsTrue() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
|
||||
boolean chunkAInList = chunkListingMap.hasChunk(mChunkHashA);
|
||||
boolean chunkBInList = chunkListingMap.hasChunk(mChunkHashB);
|
||||
boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
|
||||
|
||||
assertThat(chunkAInList).isTrue();
|
||||
assertThat(chunkBInList).isTrue();
|
||||
assertThat(chunkCInList).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasChunk_whenChunkNotInListing_returnsFalse() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
ChunkHash chunkHashEmpty = getHash("");
|
||||
|
||||
boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
|
||||
boolean emptyChunkInList = chunkListingMap.hasChunk(chunkHashEmpty);
|
||||
|
||||
assertThat(chunkCInList).isFalse();
|
||||
assertThat(emptyChunkInList).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetChunkEntry_returnsEntryWithCorrectLength() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
|
||||
ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
|
||||
ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
|
||||
ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
|
||||
|
||||
assertThat(entryA.getLength()).isEqualTo(CHUNK_A_LENGTH);
|
||||
assertThat(entryB.getLength()).isEqualTo(CHUNK_B_LENGTH);
|
||||
assertThat(entryC.getLength()).isEqualTo(CHUNK_C_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetChunkEntry_returnsEntryWithCorrectStart() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
|
||||
ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
|
||||
ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
|
||||
ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
|
||||
|
||||
assertThat(entryA.getStart()).isEqualTo(0);
|
||||
assertThat(entryB.getStart()).isEqualTo(CHUNK_A_LENGTH);
|
||||
assertThat(entryC.getStart()).isEqualTo(CHUNK_A_LENGTH + CHUNK_B_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetChunkEntry_returnsNullForNonExistentChunk() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
|
||||
ChunkListingMap.Entry chunkEntryNonexistentChunk =
|
||||
chunkListingMap.getChunkEntry(mChunkHashC);
|
||||
|
||||
assertThat(chunkEntryNonexistentChunk).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_whenEmptyProto_returnsChunkListingMapWith0Chunks()
|
||||
throws Exception {
|
||||
ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
|
||||
|
||||
ChunkListingMap chunkListingMap = ChunkListingMap.readFromProto(emptyProto);
|
||||
|
||||
assertThat(chunkListingMap.getChunkCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_returnsChunkListingWithCorrectSize() throws Exception {
|
||||
byte[] chunkListingProto =
|
||||
createChunkListingProto(
|
||||
new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
|
||||
new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
|
||||
|
||||
ChunkListingMap chunkListingMap =
|
||||
ChunkListingMap.readFromProto(
|
||||
new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
|
||||
|
||||
assertThat(chunkListingMap.getChunkCount()).isEqualTo(3);
|
||||
}
|
||||
|
||||
private byte[] createChunkListingProto(ChunkHash[] hashes, int[] lengths) {
|
||||
Preconditions.checkArgument(hashes.length == lengths.length);
|
||||
ProtoOutputStream outputStream = new ProtoOutputStream();
|
||||
|
||||
for (int i = 0; i < hashes.length; ++i) {
|
||||
writeToProtoOutputStream(outputStream, hashes[i], lengths[i]);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
return outputStream.getBytes();
|
||||
}
|
||||
|
||||
private void writeToProtoOutputStream(ProtoOutputStream out, ChunkHash chunkHash, int length) {
|
||||
long token = out.start(ChunksMetadataProto.ChunkListing.CHUNKS);
|
||||
out.write(ChunksMetadataProto.Chunk.HASH, chunkHash.getHash());
|
||||
out.write(ChunksMetadataProto.Chunk.LENGTH, length);
|
||||
out.end(token);
|
||||
}
|
||||
|
||||
private ChunkHash getHash(String name) {
|
||||
return new ChunkHash(
|
||||
Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Presubmit
|
||||
public class ChunkTest {
|
||||
private static final String CHUNK_A = "CHUNK_A";
|
||||
private static final int CHUNK_A_LENGTH = 256;
|
||||
|
||||
private ChunkHash mChunkHashA;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mChunkHashA = getHash(CHUNK_A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_readsCorrectly() throws Exception {
|
||||
ProtoOutputStream out = new ProtoOutputStream();
|
||||
out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
|
||||
out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
|
||||
out.flush();
|
||||
byte[] protoBytes = out.getBytes();
|
||||
|
||||
Chunk chunk =
|
||||
Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
|
||||
|
||||
assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
|
||||
assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_whenFieldsWrittenInReversedOrder_readsCorrectly()
|
||||
throws Exception {
|
||||
ProtoOutputStream out = new ProtoOutputStream();
|
||||
// Write fields of Chunk proto in reverse order.
|
||||
out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
|
||||
out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
|
||||
out.flush();
|
||||
byte[] protoBytes = out.getBytes();
|
||||
|
||||
Chunk chunk =
|
||||
Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
|
||||
|
||||
assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
|
||||
assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_whenEmptyProto_returnsEmptyHash() throws Exception {
|
||||
ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
|
||||
|
||||
Chunk chunk = Chunk.readFromProto(emptyProto);
|
||||
|
||||
assertThat(chunk.getHash()).asList().hasSize(0);
|
||||
assertThat(chunk.getLength()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_whenOnlyHashSet_returnsChunkWithOnlyHash() throws Exception {
|
||||
ProtoOutputStream out = new ProtoOutputStream();
|
||||
out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
|
||||
out.flush();
|
||||
byte[] protoBytes = out.getBytes();
|
||||
|
||||
Chunk chunk =
|
||||
Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
|
||||
|
||||
assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
|
||||
assertThat(chunk.getLength()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProto_whenOnlyLengthSet_returnsChunkWithOnlyLength() throws Exception {
|
||||
ProtoOutputStream out = new ProtoOutputStream();
|
||||
out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
|
||||
out.flush();
|
||||
byte[] protoBytes = out.getBytes();
|
||||
|
||||
Chunk chunk =
|
||||
Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
|
||||
|
||||
assertThat(chunk.getHash()).isEqualTo(new byte[] {});
|
||||
assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
|
||||
}
|
||||
|
||||
private ChunkHash getHash(String name) {
|
||||
return new ChunkHash(
|
||||
Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ 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.backup.encryption.protos.nano.ChunksMetadataProto;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -24,7 +24,7 @@ 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.backup.encryption.protos.nano.ChunksMetadataProto;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
Reference in New Issue
Block a user