diff options
| author | 2018-09-12 10:02:24 +0000 | |
|---|---|---|
| committer | 2018-09-12 10:02:24 +0000 | |
| commit | 629d31c543cd6e28334bc74edf09445a537ee49b (patch) | |
| tree | 117f81c9041ed8af03fa5f40218e27786296e105 | |
| parent | edc46f4239c3f7410468c115d1604e5c871b8809 (diff) | |
| parent | cf2b7c3b39a3eca910be2b0b345c7b88ac934079 (diff) | |
Merge "Moves first parts of backup crypto code (ChunkHash class) to the framework."
3 files changed, 191 insertions, 0 deletions
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java new file mode 100644 index 000000000000..1ae598ec9920 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java @@ -0,0 +1,89 @@ +/* + * 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 com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.Base64; + +/** + * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key. + * + * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link + * #hashCode()}. + */ +public class ChunkHash implements Comparable<ChunkHash> { + /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */ + public static final int HASH_LENGTH_BYTES = 256 / 8; + + private static final int UNSIGNED_MASK = 0xFF; + + private final byte[] mHash; + + /** Constructs a new instance which wraps the given SHA-256 hash bytes. */ + public ChunkHash(byte[] hash) { + Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits"); + mHash = hash; + } + + public byte[] getHash() { + return mHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ChunkHash)) { + return false; + } + + ChunkHash chunkHash = (ChunkHash) o; + return Arrays.equals(mHash, chunkHash.mHash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mHash); + } + + @Override + public int compareTo(ChunkHash other) { + return lexicographicalCompareUnsignedBytes(getHash(), other.getHash()); + } + + @Override + public String toString() { + return Base64.getEncoder().encodeToString(mHash); + } + + private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = toInt(left[i]) - toInt(right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + private static int toInt(byte value) { + return value & UNSIGNED_MASK; + } +} diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk index 2691701f79af..8b5977144db1 100644 --- a/services/robotests/Android.mk +++ b/services/robotests/Android.mk @@ -75,6 +75,7 @@ LOCAL_AIDL_INCLUDES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ platform-robolectric-android-all-stubs \ android-support-test \ + guava \ mockito-robolectric-prebuilt \ platform-test-annotations \ truth-prebuilt \ diff --git a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java new file mode 100644 index 000000000000..3b6e038ec8cb --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java @@ -0,0 +1,101 @@ +/* + * 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 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 ChunkHashTest { + private static final int HASH_LENGTH_BYTES = 256 / 8; + private static final byte[] TEST_HASH_1 = Arrays.copyOf(new byte[] {1}, HASH_LENGTH_BYTES); + private static final byte[] TEST_HASH_2 = Arrays.copyOf(new byte[] {2}, HASH_LENGTH_BYTES); + + @Test + public void testGetHash_returnsHash() { + ChunkHash chunkHash = new ChunkHash(TEST_HASH_1); + + byte[] hash = chunkHash.getHash(); + + assertThat(hash).asList().containsExactlyElementsIn(Bytes.asList(TEST_HASH_1)).inOrder(); + } + + @Test + public void testEquals() { + ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); + + assertThat(chunkHash1).isEqualTo(equalChunkHash1); + assertThat(chunkHash1).isNotEqualTo(chunkHash2); + } + + @Test + public void testHashCode() { + ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); + + int hash1 = chunkHash1.hashCode(); + int equalHash1 = equalChunkHash1.hashCode(); + int hash2 = chunkHash2.hashCode(); + + assertThat(hash1).isEqualTo(equalHash1); + assertThat(hash1).isNotEqualTo(hash2); + } + + @Test + public void testCompareTo_whenEqual_returnsZero() { + ChunkHash chunkHash = new ChunkHash(TEST_HASH_1); + ChunkHash equalChunkHash = new ChunkHash(TEST_HASH_1); + + int result = chunkHash.compareTo(equalChunkHash); + + assertThat(result).isEqualTo(0); + } + + @Test + public void testCompareTo_whenArgumentGreater_returnsNegative() { + ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); + + int result = chunkHash1.compareTo(chunkHash2); + + assertThat(result).isLessThan(0); + } + + @Test + public void testCompareTo_whenArgumentSmaller_returnsPositive() { + ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); + ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); + + int result = chunkHash2.compareTo(chunkHash1); + + assertThat(result).isGreaterThan(0); + } +} |