• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.backup.encryption.kv;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.Mockito.when;
23 import static org.testng.Assert.assertThrows;
24 
25 import android.os.Debug;
26 import android.platform.test.annotations.Presubmit;
27 
28 import com.android.server.backup.encryption.chunk.ChunkHash;
29 import com.android.server.backup.encryption.chunking.ChunkHasher;
30 import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
31 
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.Mock;
36 import org.mockito.MockitoAnnotations;
37 import org.robolectric.RobolectricTestRunner;
38 
39 import java.security.MessageDigest;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.stream.Stream;
46 
47 @RunWith(RobolectricTestRunner.class)
48 @Presubmit
49 public class DecryptedChunkKvOutputTest {
50     private static final String TEST_KEY_1 = "key_1";
51     private static final String TEST_KEY_2 = "key_2";
52     private static final byte[] TEST_VALUE_1 = {1, 2, 3};
53     private static final byte[] TEST_VALUE_2 = {10, 11, 12, 13};
54     private static final byte[] TEST_PAIR_1 = toByteArray(createPair(TEST_KEY_1, TEST_VALUE_1));
55     private static final byte[] TEST_PAIR_2 = toByteArray(createPair(TEST_KEY_2, TEST_VALUE_2));
56     private static final int TEST_BUFFER_SIZE = Math.max(TEST_PAIR_1.length, TEST_PAIR_2.length);
57 
58     @Mock private ChunkHasher mChunkHasher;
59     private DecryptedChunkKvOutput mOutput;
60 
61     @Before
setUp()62     public void setUp() throws Exception {
63         MockitoAnnotations.initMocks(this);
64 
65         when(mChunkHasher.computeHash(any()))
66                 .thenAnswer(invocation -> fakeHash(invocation.getArgument(0)));
67         mOutput = new DecryptedChunkKvOutput(mChunkHasher);
68     }
69 
70     @Test
open_returnsInstance()71     public void open_returnsInstance() throws Exception {
72         assertThat(mOutput.open()).isEqualTo(mOutput);
73     }
74 
75     @Test
processChunk_alreadyClosed_throws()76     public void processChunk_alreadyClosed_throws() throws Exception {
77         mOutput.open();
78         mOutput.close();
79 
80         assertThrows(
81                 IllegalStateException.class,
82                 () -> mOutput.processChunk(TEST_PAIR_1, TEST_PAIR_1.length));
83     }
84 
85     @Test
getDigest_beforeClose_throws()86     public void getDigest_beforeClose_throws() throws Exception {
87         // TODO: b/141356823 We should add a test which calls .open() here
88         assertThrows(IllegalStateException.class, () -> mOutput.getDigest());
89     }
90 
91     @Test
getDigest_returnsDigestOfSortedHashes()92     public void getDigest_returnsDigestOfSortedHashes() throws Exception {
93         mOutput.open();
94         Debug.waitForDebugger();
95         mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
96         mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
97         mOutput.close();
98 
99         byte[] actualDigest = mOutput.getDigest();
100 
101         MessageDigest digest = MessageDigest.getInstance(DecryptedChunkKvOutput.DIGEST_ALGORITHM);
102         Stream.of(TEST_PAIR_1, TEST_PAIR_2)
103                 .map(DecryptedChunkKvOutputTest::fakeHash)
104                 .sorted(Comparator.naturalOrder())
105                 .forEachOrdered(hash -> digest.update(hash.getHash()));
106         assertThat(actualDigest).isEqualTo(digest.digest());
107     }
108 
109     @Test
getPairs_beforeClose_throws()110     public void getPairs_beforeClose_throws() throws Exception {
111         // TODO: b/141356823 We should add a test which calls .open() here
112         assertThrows(IllegalStateException.class, () -> mOutput.getPairs());
113     }
114 
115     @Test
getPairs_returnsPairsSortedByKey()116     public void getPairs_returnsPairsSortedByKey() throws Exception {
117         mOutput.open();
118         // Write out of order to check that it sorts the chunks.
119         mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
120         mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
121         mOutput.close();
122 
123         List<KeyValuePairProto.KeyValuePair> pairs = mOutput.getPairs();
124 
125         assertThat(
126                         isInOrder(
127                                 pairs,
128                                 Comparator.comparing(
129                                         (KeyValuePairProto.KeyValuePair pair) -> pair.key)))
130                 .isTrue();
131         assertThat(pairs).hasSize(2);
132         assertThat(pairs.get(0).key).isEqualTo(TEST_KEY_1);
133         assertThat(pairs.get(0).value).isEqualTo(TEST_VALUE_1);
134         assertThat(pairs.get(1).key).isEqualTo(TEST_KEY_2);
135         assertThat(pairs.get(1).value).isEqualTo(TEST_VALUE_2);
136     }
137 
createPair(String key, byte[] value)138     private static KeyValuePairProto.KeyValuePair createPair(String key, byte[] value) {
139         KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair();
140         pair.key = key;
141         pair.value = value;
142         return pair;
143     }
144 
isInOrder( List<KeyValuePairProto.KeyValuePair> list, Comparator<KeyValuePairProto.KeyValuePair> comparator)145     private boolean isInOrder(
146             List<KeyValuePairProto.KeyValuePair> list,
147             Comparator<KeyValuePairProto.KeyValuePair> comparator) {
148         if (list.size() < 2) {
149             return true;
150         }
151 
152         List<KeyValuePairProto.KeyValuePair> sortedList = new ArrayList<>(list);
153         Collections.sort(sortedList, comparator);
154         return list.equals(sortedList);
155     }
156 
toByteArray(KeyValuePairProto.KeyValuePair nano)157     private static byte[] toByteArray(KeyValuePairProto.KeyValuePair nano) {
158         return KeyValuePairProto.KeyValuePair.toByteArray(nano);
159     }
160 
fakeHash(byte[] data)161     private static ChunkHash fakeHash(byte[] data) {
162         return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES));
163     }
164 }
165