1 /* 2 * Copyright (C) 2017 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 android.privacy; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertTrue; 22 23 import android.privacy.internal.rappor.RapporConfig; 24 import android.privacy.internal.rappor.RapporEncoder; 25 26 import androidx.test.filters.SmallTest; 27 import androidx.test.runner.AndroidJUnit4; 28 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.nio.charset.StandardCharsets; 35 import java.security.MessageDigest; 36 37 /** 38 * Unit test for the {@link RapporEncoder}. 39 * Most of the tests are done in external/rappor/client/javatest/ already. 40 * Tests here are just make sure the {@link RapporEncoder} wrap Rappor correctly. 41 */ 42 @RunWith(AndroidJUnit4.class) 43 @SmallTest 44 public class RapporEncoderTest { 45 46 @Test testRapporEncoder_config()47 public void testRapporEncoder_config() throws Exception { 48 final RapporConfig config = new RapporConfig( 49 "Foo", // encoderId 50 8, // numBits, 51 13.0 / 128.0, // probabilityF 52 0.25, // probabilityP 53 0.75, // probabilityQ 54 1, // numCohorts 55 2); // numBloomHashes) 56 final RapporEncoder encoder = RapporEncoder.createEncoder(config, 57 makeTestingUserSecret("encoder1")); 58 assertEquals("Rappor", encoder.getConfig().getAlgorithm()); 59 assertEquals("EncoderId: Foo, NumBits: 8, ProbabilityF: 0.102, " 60 + "ProbabilityP: 0.250, ProbabilityQ: 0.750, NumCohorts: 1, " 61 + "NumBloomHashes: 2", encoder.getConfig().toString()); 62 } 63 64 @Test testRapporEncoder_basicIRRTest()65 public void testRapporEncoder_basicIRRTest() throws Exception { 66 final RapporConfig config = new RapporConfig( 67 "Foo", // encoderId 68 12, // numBits, 69 0, // probabilityF 70 0, // probabilityP 71 1, // probabilityQ 72 1, // numCohorts (so must be cohort 0) 73 2); // numBloomHashes 74 // Use insecure encoder here as we want to get the exact output. 75 final RapporEncoder encoder = RapporEncoder.createInsecureEncoderForTest(config); 76 assertEquals(768, toLong(encoder.encodeString("Testing"))); 77 } 78 79 @Test testRapporEncoder_IRRWithPRR()80 public void testRapporEncoder_IRRWithPRR() throws Exception { 81 int numBits = 8; 82 final long inputValue = 254L; 83 final long expectedPrrValue = 126L; 84 final long expectedPrrAndIrrValue = 79L; 85 86 final RapporConfig config1 = new RapporConfig( 87 "Foo2", // encoderId 88 numBits, // numBits, 89 0.25, // probabilityF 90 0, // probabilityP 91 1, // probabilityQ 92 1, // numCohorts 93 2); // numBloomHashes 94 // Use insecure encoder here as we want to get the exact output. 95 final RapporEncoder encoder1 = RapporEncoder.createInsecureEncoderForTest(config1); 96 // Verify that PRR is working as expected. 97 assertEquals(expectedPrrValue, toLong(encoder1.encodeBits(toBytes(inputValue)))); 98 assertTrue(encoder1.isInsecureEncoderForTest()); 99 100 // Verify that IRR is working as expected. 101 final RapporConfig config2 = new RapporConfig( 102 "Foo2", // encoderId 103 numBits, // numBits, 104 0, // probabilityF 105 0.3, // probabilityP 106 0.7, // probabilityQ 107 1, // numCohorts 108 2); // numBloomHashes 109 // Use insecure encoder here as we want to get the exact output. 110 final RapporEncoder encoder2 = RapporEncoder.createInsecureEncoderForTest(config2); 111 assertEquals(expectedPrrAndIrrValue, 112 toLong(encoder2.encodeBits(toBytes(expectedPrrValue)))); 113 114 // Test that end-to-end is the result of PRR + IRR. 115 final RapporConfig config3 = new RapporConfig( 116 "Foo2", // encoderId 117 numBits, // numBits, 118 0.25, // probabilityF 119 0.3, // probabilityP 120 0.7, // probabilityQ 121 1, // numCohorts 122 2); // numBloomHashes 123 final RapporEncoder encoder3 = RapporEncoder.createInsecureEncoderForTest(config3); 124 // Verify that PRR is working as expected. 125 assertEquals(expectedPrrAndIrrValue, toLong(encoder3.encodeBits(toBytes(inputValue)))); 126 } 127 128 @Test testRapporEncoder_ensureSecureEncoderIsSecure()129 public void testRapporEncoder_ensureSecureEncoderIsSecure() throws Exception { 130 int numBits = 8; 131 final long inputValue = 254L; 132 final long prrValue = 250L; 133 final long prrAndIrrValue = 184L; 134 135 final RapporConfig config1 = new RapporConfig( 136 "Foo", // encoderId 137 numBits, // numBits, 138 0.25, // probabilityF 139 0, // probabilityP 140 1, // probabilityQ 141 1, // numCohorts 142 2); // numBloomHashes 143 final RapporEncoder encoder1 = RapporEncoder.createEncoder(config1, 144 makeTestingUserSecret("secret1")); 145 // Verify that PRR is working as expected, not affected by random seed. 146 assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue)))); 147 assertFalse(encoder1.isInsecureEncoderForTest()); 148 149 boolean hasDifferentResult2 = false; 150 for (int i = 0; i < 5; i++) { 151 final RapporConfig config2 = new RapporConfig( 152 "Foo", // encoderId 153 numBits, // numBits, 154 0, // probabilityF 155 0.3, // probabilityP 156 0.7, // probabilityQ 157 1, // numCohorts 158 2); // numBloomHashes 159 final RapporEncoder encoder2 = RapporEncoder.createEncoder(config2, 160 makeTestingUserSecret("secret1")); 161 hasDifferentResult2 |= (prrAndIrrValue != toLong( 162 encoder2.encodeBits(toBytes(prrValue)))); 163 } 164 // Ensure it's not getting same result as it has random seed while encoder id and secret 165 // is the same. 166 assertTrue(hasDifferentResult2); 167 168 boolean hasDifferentResults3 = false; 169 for (int i = 0; i < 5; i++) { 170 final RapporConfig config3 = new RapporConfig( 171 "Foo", // encoderId 172 numBits, // numBits, 173 0.25, // probabilityF 174 0.3, // probabilityP 175 0.7, // probabilityQ 176 1, // numCohorts 177 2); // numBloomHashes 178 final RapporEncoder encoder3 = RapporEncoder.createEncoder(config3, 179 makeTestingUserSecret("secret1")); 180 hasDifferentResults3 |= (prrAndIrrValue != toLong( 181 encoder3.encodeBits(toBytes(inputValue)))); 182 } 183 // Ensure it's not getting same result as it has random seed while encoder id and secret 184 // is the same. 185 assertTrue(hasDifferentResults3); 186 } 187 toBytes(long value)188 private static byte[] toBytes(long value) { 189 return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array(); 190 } 191 toLong(byte[] bytes)192 private static long toLong(byte[] bytes) { 193 ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes); 194 buffer.rewind(); 195 return buffer.getLong(); 196 } 197 makeTestingUserSecret(String testingSecret)198 private static byte[] makeTestingUserSecret(String testingSecret) throws Exception { 199 // We generate the fake user secret by concatenating three copies of the 200 // 16 byte MD5 hash of the testingSecret string encoded in UTF 8. 201 MessageDigest md5 = MessageDigest.getInstance("MD5"); 202 byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8)); 203 assertEquals(16, digest.length); 204 return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array(); 205 } 206 } 207