summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-09-20 16:02:41 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-09-20 16:02:41 +0000
commitf30d3536d081effad5e23e887e9be2345b0c7ae5 (patch)
tree209b9ca9ac74543f3f5ba3516b45413120bd2b92
parent0daaaaf3e954d7d318b53042136deebdbaf947e0 (diff)
parent9ce011908ab752b4c9b01b63138a6e7fd6c3713b (diff)
Merge "Import ChunkListingMap"
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java94
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java114
2 files changed, 208 insertions, 0 deletions
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
new file mode 100644
index 000000000000..51d7d532c920
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
@@ -0,0 +1,94 @@
+/*
+ * 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 com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+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 {
+
+ private final Map<ChunkHash, Entry> mChunksByHash;
+
+ /** Construct a map from a {@link ChunksMetadataProto.ChunkListing} protobuf */
+ public static ChunkListingMap fromProto(ChunksMetadataProto.ChunkListing chunkListingProto) {
+ Map<ChunkHash, Entry> entries = new HashMap<>();
+
+ long start = 0;
+
+ for (ChunksMetadataProto.Chunk chunk : chunkListingProto.chunks) {
+ entries.put(new ChunkHash(chunk.hash), new Entry(start, chunk.length));
+ start += chunk.length;
+ }
+
+ return new ChunkListingMap(entries);
+ }
+
+ private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
+ // This is only called from the {@link #fromProto} method, so we don't
+ // need to take a copy.
+ this.mChunksByHash = 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);
+ }
+
+ /** 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) {
+ mLength = length;
+ mStart = start;
+ }
+
+ /** 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;
+ }
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
new file mode 100644
index 000000000000..c5f78c254cd1
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+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.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkListingMapTest {
+ private static final ChunkHash CHUNK_A_HASH = getHash("CHUNK_A");
+ private static final ChunkHash CHUNK_B_HASH = getHash("CHUNK_B");
+ private static final ChunkHash CHUNK_C_HASH = getHash("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 static final int CHUNK_A_START = 0;
+ private static final int CHUNK_B_START = CHUNK_A_START + CHUNK_A_LENGTH;
+ private static final int CHUNK_C_START = CHUNK_B_START + CHUNK_B_LENGTH;
+
+ private ChunkListingMap mChunkListingMap;
+
+ @Before
+ public void setUp() {
+ mChunkListingMap = createFromFixture();
+ }
+
+ @Test
+ public void hasChunk_isTrueForExistingChunks() {
+ assertThat(mChunkListingMap.hasChunk(CHUNK_A_HASH)).isTrue();
+ assertThat(mChunkListingMap.hasChunk(CHUNK_B_HASH)).isTrue();
+ assertThat(mChunkListingMap.hasChunk(CHUNK_C_HASH)).isTrue();
+ }
+
+ @Test
+ public void hasChunk_isFalseForNonexistentChunks() {
+ assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse();
+ assertThat(mChunkListingMap.hasChunk(getHash(""))).isFalse();
+ }
+
+ @Test
+ public void getChunkListing_hasCorrectLengths() {
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getLength())
+ .isEqualTo(CHUNK_A_LENGTH);
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getLength())
+ .isEqualTo(CHUNK_B_LENGTH);
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getLength())
+ .isEqualTo(CHUNK_C_LENGTH);
+ }
+
+ @Test
+ public void getChunkListing_hasCorrectStarts() {
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getStart())
+ .isEqualTo(CHUNK_A_START);
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getStart())
+ .isEqualTo(CHUNK_B_START);
+ assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getStart())
+ .isEqualTo(CHUNK_C_START);
+ }
+
+ @Test
+ public void getChunkListing_isNullForNonExistentChunks() {
+ assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).isNull();
+ }
+
+ private static ChunkListingMap createFromFixture() {
+ ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+ chunkListing.chunks = new ChunksMetadataProto.Chunk[3];
+ chunkListing.chunks[0] = newChunk(CHUNK_A_HASH.getHash(), CHUNK_A_LENGTH);
+ chunkListing.chunks[1] = newChunk(CHUNK_B_HASH.getHash(), CHUNK_B_LENGTH);
+ chunkListing.chunks[2] = newChunk(CHUNK_C_HASH.getHash(), CHUNK_C_LENGTH);
+ return ChunkListingMap.fromProto(chunkListing);
+ }
+
+ private static ChunkHash getHash(String name) {
+ return new ChunkHash(
+ Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
+ }
+
+ public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) {
+ ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk();
+ newChunk.hash = Arrays.copyOf(hash, hash.length);
+ newChunk.length = length;
+ return newChunk;
+ }
+}