aboutsummaryrefslogtreecommitdiff
path: root/client/javatest/com/google/rappor/EncoderTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'client/javatest/com/google/rappor/EncoderTest.java')
-rw-r--r--client/javatest/com/google/rappor/EncoderTest.java1044
1 files changed, 1044 insertions, 0 deletions
diff --git a/client/javatest/com/google/rappor/EncoderTest.java b/client/javatest/com/google/rappor/EncoderTest.java
new file mode 100644
index 0000000..896322f
--- /dev/null
+++ b/client/javatest/com/google/rappor/EncoderTest.java
@@ -0,0 +1,1044 @@
+package com.google.android.rappor;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+// BEGIN android-changed
+// import java.security.SecureRandom;
+import java.util.Random;
+// END android-changed
+
+/**
+ * Unit tests for {@link Encoder}.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class EncoderTest {
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * Convert a human readable string to a 32 byte userSecret for testing.
+ *
+ * <p>Do not use this in a production environment! For security, userSecret
+ * must be at least 32 bytes of high-quality entropy.
+ */
+ private static byte[] makeTestingUserSecret(String testingSecret) throws Exception {
+ // We generate the fake user secret by concatenating three copies of the
+ // 16 byte MD5 hash of the testingSecret string encoded in UTF 8.
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8));
+ assertEquals(16, digest.length);
+ return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array();
+ }
+
+ private static long toLong(byte[] bytes) {
+ assertThat(bytes.length, is(lessThanOrEqualTo(8)));
+ ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes);
+ buffer.rewind(); // can't chain rewind() because it returns Buffer, not ByteBuffer.
+ return buffer.getLong();
+ }
+
+ private static byte[] toBytes(long value) {
+ return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array();
+ }
+
+ @Test
+ public void testEncoderConstruction_goodArguments() throws Exception {
+ // Full RAPPOR
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+
+ // IRR-only (no PRR)
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+
+ // PRR-only (no IRR)
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.0, // probabilityP
+ 1.0, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_userSecretTooShort() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ byte[] tooShortSecret = new byte[47];
+ new Encoder(tooShortSecret, // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_userSecretMayBeLong() throws Exception {
+ byte[] tooLongSecret = new byte[49];
+ new Encoder(tooLongSecret, // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numBitsTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 0, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numBitsTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 4097, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityFTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ -0.01, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityFTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 1.01, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityPTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ -0.01, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityPTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 1.01, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityQTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ -0.01, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_probabilityQTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.75, // probabilityP
+ 1.01, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numCohortsTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 0, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numCohortsTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ Encoder.MAX_COHORTS + 1, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numCohortsNotPowerOf2() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 3, // numCohorts
+ 2); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numBloomHashesTooLow() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 0); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderConstruction_numBloomHashesTooHigh() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 9); // numBloomHashes
+ }
+
+ @Test
+ public void testEncoderGetCohort() throws Exception {
+ // This is a stable, random cohort assignment.
+ assertEquals(
+ 3,
+ new Encoder(makeTestingUserSecret("Blotto"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ // With numCohorts == 1, the only possible cohort assigment is 0.
+ assertEquals(
+ 0,
+ new Encoder(makeTestingUserSecret("Blotto"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ // Changing the user secret changes the cohort.
+ assertEquals(
+ 3,
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ assertEquals(
+ 0,
+ new Encoder(
+ makeTestingUserSecret("Bar2"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ // Changing the encoder id does not changes the cohort.
+ assertEquals(
+ 3,
+ new Encoder(makeTestingUserSecret("Blotto"), // userSecret
+ "Foo1", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ assertEquals(
+ 3,
+ new Encoder(makeTestingUserSecret("Blotto"), // userSecret
+ "Foo2", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ assertEquals(
+ 3,
+ new Encoder(makeTestingUserSecret("Blotto"), // userSecret
+ "Foo3", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 4, // numCohorts
+ 2) // numBloomHashes
+ .getCohort());
+
+ // Cohort assignments are bit-wise subsets
+ int cohortAssignmentBig =
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ Encoder.MAX_COHORTS, // numCohorts
+ 2) // numBloomHashes
+ .getCohort();
+
+ int numCohortsSmall = Encoder.MAX_COHORTS / 2;
+ // Verify that numCohortsSmall is a power of 2.
+ assertEquals(0, numCohortsSmall & (numCohortsSmall - 1));
+ int cohortAssignmentSmall =
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ numCohortsSmall, // numCohorts
+ 2) // numBloomHashes
+ .getCohort();
+
+ // This validates that the test case is well chosen. If it fails, select a different userSecret
+ // or encoderId.
+ assertNotEquals(cohortAssignmentBig, cohortAssignmentSmall);
+
+ // Test that cohortAssignmentSmall is a suffix of cohortAssignmentBig when represented in
+ // binary.
+ assertEquals(cohortAssignmentBig & (numCohortsSmall - 1), cohortAssignmentSmall);
+ }
+
+ @Test
+ public void testEncoderEncodeBits_identity() throws Exception {
+ assertEquals(
+ 0b11111101L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(0b11111101L))));
+
+ assertEquals(
+ 0xD56B8119L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 32, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(0xD56B8119L))));
+ }
+
+ @Test
+ public void testEncoderEncodeBits_tooHigh() throws Exception {
+ Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ thrown.expect(IllegalArgumentException.class);
+ encoder.encodeBits(toBytes(0x100)); // 9 bits
+ }
+
+ @Test
+ public void testEncoderEncodeBoolean_identity() throws Exception {
+ assertEquals(
+ 0x1L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 1, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1) // numBloomHashes
+ .encodeBoolean(true)));
+
+ assertEquals(
+ 0x0L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 1, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1) // numBloomHashes
+ .encodeBoolean(false)));
+ }
+
+ @Test
+ public void testEncoderEncodeOrdinal_identity() throws Exception {
+ assertEquals(
+ 0b000000000001L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1) // numBloomHashes
+ .encodeOrdinal(0)));
+
+ assertEquals(
+ 0b100000000000L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1) // numBloomHashes
+ .encodeOrdinal(11)));
+ }
+
+ @Test
+ public void testEncoderEncodeOrdinal_tooLow() throws Exception {
+ Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1); // numBloomHashes
+ thrown.expect(IllegalArgumentException.class);
+ encoder.encodeOrdinal(-1);
+ }
+
+ @Test
+ public void testEncoderEncodeOrdinal_tooHigh() throws Exception {
+ Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 1); // numBloomHashes
+ thrown.expect(IllegalArgumentException.class);
+ encoder.encodeOrdinal(12);
+ }
+
+ @Test
+ public void testEncoderEncodeString_identity() throws Exception {
+ assertEquals(
+ 0b000010000100L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts (so must be cohort 0)
+ 2) // numBloomHashes
+ .encodeString("Whizbang")));
+
+ // Changing the user but keeping the cohort the same (both cohort 0)
+ // results in the same encoding.
+ assertEquals(
+ 0b000010000100L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Blotto"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts (so must be cohort 0)
+ 2) // numBloomHashes
+ .encodeString("Whizbang")));
+
+ // When the user is in a different cohort, she gets a different encoding.
+ Encoder cohortProbeEncoder =
+ new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 4, // numCohorts
+ 2); // numBloomHashes
+ assertEquals(3, cohortProbeEncoder.getCohort());
+ assertEquals(0b000011000000L, toLong(cohortProbeEncoder.encodeString("Whizbang")));
+
+ // Changing the string gets a different encoding.
+ assertEquals(
+ 0b001001000000L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts (so must be cohort 0)
+ 2) // numBloomHashes
+ .encodeString("Xyzzy")));
+
+ assertEquals(
+ 0b000000110000L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 12, // numBits,
+ 0, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts (so must be cohort 0)
+ 2) // numBloomHashes
+ .encodeString("Thud")));
+ }
+
+
+ @Test
+ public void testEncoderEncodeBits_prrMemoizes() throws Exception {
+ assertEquals(
+ 0b01110101L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 0.25, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(0b11111101L))));
+
+ assertEquals(
+ 0b11111101L,
+ toLong(
+ new Encoder(
+ makeTestingUserSecret("Baz"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 0.25, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(0b11111101L))));
+ }
+
+ @Test
+ public void testEncoderEncodeBits_prrFlipProbability() throws Exception {
+ int numSamples = 10000;
+ int numBits = 8;
+ double probabilityF = 1.0 / 32.0;
+ long inputValue = 0b11111101L;
+
+ int counts[] = new int[64];
+ for (int iSample = 0; iSample < numSamples; iSample++) {
+ Encoder encoder =
+ new Encoder(makeTestingUserSecret("User" + iSample), // userSecret
+ "Foo", // encoderId
+ numBits, // numBits,
+ probabilityF, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ long encoded = toLong(encoder.encodeBits(toBytes(inputValue)));
+ assertEquals(encoded, toLong(encoder.encodeBits(toBytes(inputValue))));
+
+ for (int iBit = 0; iBit < numBits; iBit++) {
+ if ((encoded & (1L << iBit)) != 0) {
+ counts[iBit]++;
+ }
+ }
+ }
+
+ assertEquals(9843, counts[0]); // input = 1, expectation = 9843.75
+ assertEquals(173, counts[1]); // input = 0, expectation = 156.25
+ assertEquals(9839, counts[2]); // input = 1, expectation = 9843.75
+ assertEquals(9831, counts[3]); // input = 1, expectation = 9843.75
+ assertEquals(9848, counts[4]); // input = 1, expectation = 9843.75
+ assertEquals(9828, counts[5]); // input = 1, expectation = 9843.75
+ assertEquals(9834, counts[6]); // input = 1, expectation = 9843.75
+ assertEquals(9837, counts[7]); // input = 1, expectation = 9843.75
+
+ // Check that no high-order bit past numBits ever got set.
+ for (int iBit = numBits; iBit < 64; iBit++) {
+ assertEquals(0, counts[iBit]);
+ }
+ }
+
+ @Test
+ public void testEncoderEncodeBits_irrFlipProbability() throws Exception {
+ int numBits = 8;
+ double probabilityP = 0.25;
+ double probabilityQ = 0.85;
+ long inputValue = 0b11111101L;
+
+// BEGIN android-changed
+// SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ Random random = new Random();
+// END android-changed
+ random.setSeed(0x12345678L);
+
+ int counts[] = new int[64];
+ for (int iSample = 0; iSample < 10000; iSample++) {
+ Encoder encoder =
+ new Encoder(
+ random,
+ null, // md5
+ null, // sha256
+ makeTestingUserSecret("User" + iSample), // userSecret
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0, // probabilityF
+ probabilityP, // probabilityP
+ probabilityQ, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ long encoded = toLong(encoder.encodeBits(toBytes(inputValue)));
+
+ for (int iBit = 0; iBit < numBits; iBit++) {
+ if ((encoded & (1L << iBit)) != 0) {
+ counts[iBit]++;
+ }
+ }
+ }
+
+// BEGIN android-changed
+// assertEquals(8481, counts[0]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(2477, counts[1]); // input = 0, 99.99% CI = [2332, 2669]
+// assertEquals(8486, counts[2]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(8495, counts[3]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(8563, counts[4]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(8560, counts[5]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(8481, counts[6]); // input = 1, 99.99% CI = [8358, 8636]
+// assertEquals(8491, counts[7]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8492, counts[0]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(2510, counts[1]); // input = 0, 99.99% CI = [2332, 2669]
+ assertEquals(8476, counts[2]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8509, counts[3]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8406, counts[4]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8482, counts[5]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8498, counts[6]); // input = 1, 99.99% CI = [8358, 8636]
+ assertEquals(8533, counts[7]); // input = 1, 99.99% CI = [8358, 8636]
+// END android-changed
+
+ // Check that no high-order bit past numBits ever got set.
+ for (int iBit = numBits; iBit < 64; iBit++) {
+ assertEquals(0, counts[iBit]);
+ }
+ }
+
+ @Test
+ public void testEncoderEncodeBits_endToEnd() throws Exception {
+ int numBits = 8;
+
+ long inputValue = 0b11111101L;
+ long prrValue = 0b01110101L;
+// BEGIN android-changed
+// long prrAndIrrValue = 0b01110110L;
+ long prrAndIrrValue = 0b00111101L;
+// END android-changed
+
+ // Verify that PRR is working as expected.
+ assertEquals(
+ prrValue,
+ toLong(
+ new Encoder(
+ null,
+ null, // md5
+ null, // sha256
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0, // probabilityP
+ 1, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue))));
+
+ // Verify that IRR is working as expected.
+// BEGIN android-changed
+// SecureRandom random1 = SecureRandom.getInstance("SHA1PRNG");
+ Random random1 = new Random();
+// END android-changed
+ random1.setSeed(0x12345678L);
+ assertEquals(
+ prrAndIrrValue,
+ toLong(
+ new Encoder(
+ random1,
+ null, // md5
+ null, // sha256
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(prrValue))));
+
+ // Test that end-to-end is the result of PRR + IRR.
+// BEGIN android-changed
+// SecureRandom random2 = SecureRandom.getInstance("SHA1PRNG");
+ Random random2 = new Random();
+// END android-changed
+ random2.setSeed(0x12345678L);
+ assertEquals(
+ prrAndIrrValue,
+ toLong(
+ new Encoder(
+ random2,
+ null, // md5
+ null, // sha256
+ makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue))));
+ }
+
+ @Test
+ public void testEncoderEncodeBits_32BitValuesEncodeSuccessfully() throws Exception {
+ // Regression test for b/22035650.
+ int numBits = 32;
+ byte[] userSecret = makeTestingUserSecret("Bar");
+
+ // Explicitly spot-check the output for 2^0 and 2^31.
+ long inputValue0 = 1L;
+// BEGIN android-changed
+// long outputValue0 = 590349342L;
+// SecureRandom random0 = SecureRandom.getInstance("SHA1PRNG");
+ long outputValue0 = 1117977L;
+ Random random0 = new Random();
+// END android-changed
+ random0.setSeed(0x12345678L);
+ assertEquals(
+ outputValue0,
+ toLong(
+ new Encoder(
+ random0,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue0))));
+
+ long inputValue31 = 1L << 31;
+// BEGIN android-changed
+// long outputValue31 = 2746482838L;
+// SecureRandom random31 = SecureRandom.getInstance("SHA1PRNG");
+ long outputValue31 = 9505692L;
+ Random random31 = new Random();
+// END android-changed
+ random31.setSeed(0x12345678L);
+ assertEquals(
+ outputValue31,
+ toLong(
+ new Encoder(
+ random31,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue31))));
+
+ // Check the range 2^1 to 2^30, making sure no values produce exceptions.
+// BEGIN android-changed
+// SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG");
+ Random randomRange = new Random();
+// END android-changed
+ randomRange.setSeed(0x12345678L);
+ for (int i = 1; i <= 30; i++) {
+ long inputValue = 1L << (i - 1);
+ new Encoder(
+ randomRange,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue));
+ }
+ }
+
+ @Test
+ public void testEncoderEncodeBits_63BitValuesEncodeSuccessfully() throws Exception {
+ int numBits = 63;
+ byte[] userSecret = makeTestingUserSecret("Bar");
+
+ // Explicitly spot-check the output for 2^0 and 2^63.
+ long inputValue0 = 1L;
+// BEGIN android-changed
+// long outputValue0 = 867402030798341150L;
+// SecureRandom random0 = SecureRandom.getInstance("SHA1PRNG");
+ long outputValue0 = 5802301002133606169L;
+ Random random0 = new Random();
+// END android-changed
+ random0.setSeed(0x12345678L);
+ assertEquals(
+ outputValue0,
+ toLong(
+ new Encoder(
+ random0,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue0))));
+
+ long inputValue63 = 1L << 62;
+// BEGIN android-changed
+// long outputValue63 = 5497102447743615126L;
+// SecureRandom random63 = SecureRandom.getInstance("SHA1PRNG");
+ long outputValue63 = 5874921546135456664L;
+ Random random63 = new Random();
+// END android-changed
+ random63.setSeed(0x12345678L);
+ assertEquals(
+ outputValue63,
+ toLong(
+ new Encoder(
+ random63,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue63))));
+
+ // Check the range 2^1 to 2^62, making sure no values produce exceptions.
+// BEGIN android-changed
+// SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG");
+ Random randomRange = new Random();
+// END android-changed
+ randomRange.setSeed(0x12345678L);
+ for (int i = 1; i <= 62; i++) {
+ long inputValue = 1L << (i - 1);
+ new Encoder(
+ randomRange,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeBits(toBytes(inputValue));
+ }
+ }
+
+ @Test
+ public void testEncoderEncodeBits_4096BitValuesEncodeSuccessfully() throws Exception {
+ int numBits = 4096;
+ byte[] userSecret = makeTestingUserSecret("Bar");
+
+ // Check the range 2^1 to 2^4095, making sure no values produce exceptions.
+// BEGIN android-changed
+// SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG");
+ Random randomRange = new Random();
+// END android-changed
+ randomRange.setSeed(0x12345678L);
+ // Stride is arbitrary, but chosen to be large enough to not cause too many probes (~40) and
+ // prime to explore well.
+ int stride = 97;
+ for (int i = 1; i < numBits; i += stride) {
+ new Encoder(
+ randomRange,
+ null, // md5
+ null, // sha256
+ userSecret, // userSecret
+ "MyEncoder", // encoderId
+ numBits, // numBits,
+ 0.25, // probabilityF
+ 0.3, // probabilityP
+ 0.7, // probabilityQ
+ 1, // numCohorts
+ 2) // numBloomHashes
+ .encodeOrdinal(i);
+ }
+ }
+
+ @Test
+ public void testGetEncoderId() throws Exception {
+ Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret
+ "Foo", // encoderId
+ 8, // numBits,
+ 13.0 / 128.0, // probabilityF
+ 0.25, // probabilityP
+ 0.75, // probabilityQ
+ 1, // numCohorts
+ 2); // numBloomHashes
+ assertEquals("Foo", encoder.getEncoderId());
+ }
+}