• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Guava Authors
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.google.common.hash;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static java.nio.charset.StandardCharsets.ISO_8859_1;
21 import static java.nio.charset.StandardCharsets.US_ASCII;
22 import static java.nio.charset.StandardCharsets.UTF_16;
23 import static java.nio.charset.StandardCharsets.UTF_16BE;
24 import static java.nio.charset.StandardCharsets.UTF_16LE;
25 import static java.nio.charset.StandardCharsets.UTF_8;
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertFalse;
28 
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.Sets;
31 import com.google.common.primitives.Ints;
32 import com.google.common.testing.EqualsTester;
33 import java.nio.ByteBuffer;
34 import java.nio.ByteOrder;
35 import java.nio.charset.Charset;
36 import java.util.Arrays;
37 import java.util.Random;
38 import java.util.Set;
39 import org.junit.Assert;
40 
41 /**
42  * Various utilities for testing {@link HashFunction}s.
43  *
44  * @author Dimitris Andreou
45  * @author Kurt Alfred Kluever
46  */
47 final class HashTestUtils {
HashTestUtils()48   private HashTestUtils() {}
49 
50   /** Converts a string, which should contain only ascii-representable characters, to a byte[]. */
ascii(String string)51   static byte[] ascii(String string) {
52     byte[] bytes = new byte[string.length()];
53     for (int i = 0; i < string.length(); i++) {
54       bytes[i] = (byte) string.charAt(i);
55     }
56     return bytes;
57   }
58 
59   interface HashFn {
hash(byte[] input, int seed)60     byte[] hash(byte[] input, int seed);
61   }
62 
verifyHashFunction(HashFn hashFunction, int hashbits, int expected)63   static void verifyHashFunction(HashFn hashFunction, int hashbits, int expected) {
64     int hashBytes = hashbits / 8;
65 
66     byte[] key = new byte[256];
67     byte[] hashes = new byte[hashBytes * 256];
68 
69     // Hash keys of the form {}, {0}, {0,1}, {0,1,2}... up to N=255,using 256-N as the seed
70     for (int i = 0; i < 256; i++) {
71       key[i] = (byte) i;
72       int seed = 256 - i;
73       byte[] hash = hashFunction.hash(Arrays.copyOf(key, i), seed);
74       System.arraycopy(hash, 0, hashes, i * hashBytes, hash.length);
75     }
76 
77     // Then hash the result array
78     byte[] result = hashFunction.hash(hashes, 0);
79 
80     // interpreted in little-endian order.
81     int verification = Integer.reverseBytes(Ints.fromByteArray(result));
82 
83     if (expected != verification) {
84       throw new AssertionError(
85           "Expected: "
86               + Integer.toHexString(expected)
87               + " got: "
88               + Integer.toHexString(verification));
89     }
90   }
91 
92   static final Funnel<Object> BAD_FUNNEL =
93       new Funnel<Object>() {
94         @Override
95         public void funnel(Object object, PrimitiveSink bytePrimitiveSink) {
96           bytePrimitiveSink.putInt(object.hashCode());
97         }
98       };
99 
100   enum RandomHasherAction {
PUT_BOOLEAN()101     PUT_BOOLEAN() {
102       @Override
103       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
104         boolean value = random.nextBoolean();
105         for (PrimitiveSink sink : sinks) {
106           sink.putBoolean(value);
107         }
108       }
109     },
PUT_BYTE()110     PUT_BYTE() {
111       @Override
112       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
113         int value = random.nextInt();
114         for (PrimitiveSink sink : sinks) {
115           sink.putByte((byte) value);
116         }
117       }
118     },
PUT_SHORT()119     PUT_SHORT() {
120       @Override
121       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
122         short value = (short) random.nextInt();
123         for (PrimitiveSink sink : sinks) {
124           sink.putShort(value);
125         }
126       }
127     },
PUT_CHAR()128     PUT_CHAR() {
129       @Override
130       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
131         char value = (char) random.nextInt();
132         for (PrimitiveSink sink : sinks) {
133           sink.putChar(value);
134         }
135       }
136     },
PUT_INT()137     PUT_INT() {
138       @Override
139       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
140         int value = random.nextInt();
141         for (PrimitiveSink sink : sinks) {
142           sink.putInt(value);
143         }
144       }
145     },
PUT_LONG()146     PUT_LONG() {
147       @Override
148       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
149         long value = random.nextLong();
150         for (PrimitiveSink sink : sinks) {
151           sink.putLong(value);
152         }
153       }
154     },
PUT_FLOAT()155     PUT_FLOAT() {
156       @Override
157       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
158         float value = random.nextFloat();
159         for (PrimitiveSink sink : sinks) {
160           sink.putFloat(value);
161         }
162       }
163     },
PUT_DOUBLE()164     PUT_DOUBLE() {
165       @Override
166       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
167         double value = random.nextDouble();
168         for (PrimitiveSink sink : sinks) {
169           sink.putDouble(value);
170         }
171       }
172     },
PUT_BYTES()173     PUT_BYTES() {
174       @Override
175       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
176         byte[] value = new byte[random.nextInt(128)];
177         random.nextBytes(value);
178         for (PrimitiveSink sink : sinks) {
179           sink.putBytes(value);
180         }
181       }
182     },
PUT_BYTES_INT_INT()183     PUT_BYTES_INT_INT() {
184       @Override
185       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
186         byte[] value = new byte[random.nextInt(128)];
187         random.nextBytes(value);
188         int off = random.nextInt(value.length + 1);
189         int len = random.nextInt(value.length - off + 1);
190         for (PrimitiveSink sink : sinks) {
191           sink.putBytes(value, off, len);
192         }
193       }
194     },
PUT_BYTE_BUFFER()195     PUT_BYTE_BUFFER() {
196       @Override
197       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
198         byte[] value = new byte[random.nextInt(128)];
199         random.nextBytes(value);
200         int pos = random.nextInt(value.length + 1);
201         int limit = pos + random.nextInt(value.length - pos + 1);
202         for (PrimitiveSink sink : sinks) {
203           ByteBuffer buffer = ByteBuffer.wrap(value);
204           Java8Compatibility.position(buffer, pos);
205           Java8Compatibility.limit(buffer, limit);
206           sink.putBytes(buffer);
207           assertEquals(limit, buffer.limit());
208           assertEquals(limit, buffer.position());
209         }
210       }
211     },
PUT_STRING()212     PUT_STRING() {
213       @Override
214       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
215         char[] value = new char[random.nextInt(128)];
216         for (int i = 0; i < value.length; i++) {
217           value[i] = (char) random.nextInt();
218         }
219         String s = new String(value);
220         for (PrimitiveSink sink : sinks) {
221           sink.putUnencodedChars(s);
222         }
223       }
224     },
PUT_STRING_LOW_SURROGATE()225     PUT_STRING_LOW_SURROGATE() {
226       @Override
227       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
228         String s = new String(new char[] {randomLowSurrogate(random)});
229         for (PrimitiveSink sink : sinks) {
230           sink.putUnencodedChars(s);
231         }
232       }
233     },
PUT_STRING_HIGH_SURROGATE()234     PUT_STRING_HIGH_SURROGATE() {
235       @Override
236       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
237         String s = new String(new char[] {randomHighSurrogate(random)});
238         for (PrimitiveSink sink : sinks) {
239           sink.putUnencodedChars(s);
240         }
241       }
242     },
PUT_STRING_LOW_HIGH_SURROGATE()243     PUT_STRING_LOW_HIGH_SURROGATE() {
244       @Override
245       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
246         String s = new String(new char[] {randomLowSurrogate(random), randomHighSurrogate(random)});
247         for (PrimitiveSink sink : sinks) {
248           sink.putUnencodedChars(s);
249         }
250       }
251     },
PUT_STRING_HIGH_LOW_SURROGATE()252     PUT_STRING_HIGH_LOW_SURROGATE() {
253       @Override
254       void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) {
255         String s = new String(new char[] {randomHighSurrogate(random), randomLowSurrogate(random)});
256         for (PrimitiveSink sink : sinks) {
257           sink.putUnencodedChars(s);
258         }
259       }
260     };
261 
performAction(Random random, Iterable<? extends PrimitiveSink> sinks)262     abstract void performAction(Random random, Iterable<? extends PrimitiveSink> sinks);
263 
264     private static final RandomHasherAction[] actions = values();
265 
pickAtRandom(Random random)266     static RandomHasherAction pickAtRandom(Random random) {
267       return actions[random.nextInt(actions.length)];
268     }
269   }
270 
271   /**
272    * Test that the hash function contains no funnels. A funnel is a situation where a set of input
273    * (key) bits 'affects' a strictly smaller set of output bits. Funneling is bad because it can
274    * result in more-than-ideal collisions for a non-uniformly distributed key space. In practice,
275    * most key spaces are ANYTHING BUT uniformly distributed. A bit(i) in the input is said to
276    * 'affect' a bit(j) in the output if two inputs, identical but for bit(i), will differ at output
277    * bit(j) about half the time
278    *
279    * <p>Funneling is pretty simple to detect. The key idea is to find example keys which
280    * unequivocally demonstrate that funneling cannot be occurring. This is done bit-by-bit. For each
281    * input bit(i) and output bit(j), two pairs of keys must be found with all bits identical except
282    * bit(i). One pair must differ in output bit(j), and one pair must not. This proves that input
283    * bit(i) can alter output bit(j).
284    */
checkNoFunnels(HashFunction function)285   static void checkNoFunnels(HashFunction function) {
286     Random rand = new Random(0);
287     int keyBits = 32;
288     int hashBits = function.bits();
289 
290     // output loop tests input bit
291     for (int i = 0; i < keyBits; i++) {
292       int same = 0x0; // bitset for output bits with same values
293       int diff = 0x0; // bitset for output bits with different values
294       int count = 0;
295       // originally was 2 * Math.log(...), making it try more times to avoid flakiness issues
296       int maxCount = (int) (4 * Math.log(2 * keyBits * hashBits) + 1);
297       while (same != 0xffffffff || diff != 0xffffffff) {
298         int key1 = rand.nextInt();
299         // flip input bit for key2
300         int key2 = key1 ^ (1 << i);
301         // get hashes
302         int hash1 = function.hashInt(key1).asInt();
303         int hash2 = function.hashInt(key2).asInt();
304         // test whether the hash values have same output bits
305         same |= ~(hash1 ^ hash2);
306         // test whether the hash values have different output bits
307         diff |= (hash1 ^ hash2);
308 
309         count++;
310         // check whether we've exceeded the probabilistically
311         // likely number of trials to have proven no funneling
312         if (count > maxCount) {
313           Assert.fail(
314               "input bit("
315                   + i
316                   + ") was found not to affect all "
317                   + hashBits
318                   + " output bits; The unaffected bits are "
319                   + "as follows: "
320                   + ~(same & diff)
321                   + ". This was "
322                   + "determined after "
323                   + count
324                   + " trials.");
325         }
326       }
327     }
328   }
329 
330   /**
331    * Test for avalanche. Avalanche means that output bits differ with roughly 1/2 probability on
332    * different input keys. This test verifies that each possible 1-bit key delta achieves avalanche.
333    *
334    * <p>For more information: http://burtleburtle.net/bob/hash/avalanche.html
335    */
checkAvalanche(HashFunction function, int trials, double epsilon)336   static void checkAvalanche(HashFunction function, int trials, double epsilon) {
337     Random rand = new Random(0);
338     int keyBits = 32;
339     int hashBits = function.bits();
340     for (int i = 0; i < keyBits; i++) {
341       int[] same = new int[hashBits];
342       int[] diff = new int[hashBits];
343       // go through trials to compute probability
344       for (int j = 0; j < trials; j++) {
345         int key1 = rand.nextInt();
346         // flip input bit for key2
347         int key2 = key1 ^ (1 << i);
348         // compute hash values
349         int hash1 = function.hashInt(key1).asInt();
350         int hash2 = function.hashInt(key2).asInt();
351         for (int k = 0; k < hashBits; k++) {
352           if ((hash1 & (1 << k)) == (hash2 & (1 << k))) {
353             same[k] += 1;
354           } else {
355             diff[k] += 1;
356           }
357         }
358       }
359       // measure probability and assert it's within margin of error
360       for (int j = 0; j < hashBits; j++) {
361         double prob = (double) diff[j] / (double) (diff[j] + same[j]);
362         assertThat(prob).isWithin(epsilon).of(0.50d);
363       }
364     }
365   }
366 
367   /**
368    * Test for 2-bit characteristics. A characteristic is a delta in the input which is repeated in
369    * the output. For example, if f() is a block cipher and c is a characteristic, then f(x^c) =
370    * f(x)^c with greater than expected probability. The test for funneling is merely a test for
371    * 1-bit characteristics.
372    *
373    * <p>There is more general code provided by Bob Jenkins to test arbitrarily sized characteristics
374    * using the magic of gaussian elimination: http://burtleburtle.net/bob/crypto/findingc.html.
375    */
checkNo2BitCharacteristics(HashFunction function)376   static void checkNo2BitCharacteristics(HashFunction function) {
377     Random rand = new Random(0);
378     int keyBits = 32;
379 
380     // get every one of (keyBits choose 2) deltas:
381     for (int i = 0; i < keyBits; i++) {
382       for (int j = 0; j < keyBits; j++) {
383         if (j <= i) continue;
384         int count = 0;
385         int maxCount = 20; // the probability of error here is minuscule
386         boolean diff = false;
387 
388         while (!diff) {
389           int delta = (1 << i) | (1 << j);
390           int key1 = rand.nextInt();
391           // apply delta
392           int key2 = key1 ^ delta;
393 
394           // get hashes
395           int hash1 = function.hashInt(key1).asInt();
396           int hash2 = function.hashInt(key2).asInt();
397 
398           // this 2-bit candidate delta is not a characteristic
399           // if deltas are different
400           if ((hash1 ^ hash2) != delta) {
401             diff = true;
402             continue;
403           }
404 
405           // check if we've exceeded the probabilistically
406           // likely number of trials to have proven 2-bit candidate
407           // is not a characteristic
408           count++;
409           if (count > maxCount) {
410             Assert.fail(
411                 "2-bit delta ("
412                     + i
413                     + ", "
414                     + j
415                     + ") is likely a "
416                     + "characteristic for this hash. This was "
417                     + "determined after "
418                     + count
419                     + " trials");
420           }
421         }
422       }
423     }
424   }
425 
426   /**
427    * Test for avalanche with 2-bit deltas. Most probabilities of output bit(j) differing are well
428    * within 50%.
429    */
check2BitAvalanche(HashFunction function, int trials, double epsilon)430   static void check2BitAvalanche(HashFunction function, int trials, double epsilon) {
431     Random rand = new Random(0);
432     int keyBits = 32;
433     int hashBits = function.bits();
434     for (int bit1 = 0; bit1 < keyBits; bit1++) {
435       for (int bit2 = 0; bit2 < keyBits; bit2++) {
436         if (bit2 <= bit1) continue;
437         int delta = (1 << bit1) | (1 << bit2);
438         int[] same = new int[hashBits];
439         int[] diff = new int[hashBits];
440         // go through trials to compute probability
441         for (int j = 0; j < trials; j++) {
442           int key1 = rand.nextInt();
443           // flip input bit for key2
444           int key2 = key1 ^ delta;
445           // compute hash values
446           int hash1 = function.hashInt(key1).asInt();
447           int hash2 = function.hashInt(key2).asInt();
448           for (int k = 0; k < hashBits; k++) {
449             if ((hash1 & (1 << k)) == (hash2 & (1 << k))) {
450               same[k] += 1;
451             } else {
452               diff[k] += 1;
453             }
454           }
455         }
456         // measure probability and assert it's within margin of error
457         for (int j = 0; j < hashBits; j++) {
458           double prob = (double) diff[j] / (double) (diff[j] + same[j]);
459           assertThat(prob).isWithin(epsilon).of(0.50d);
460         }
461       }
462     }
463   }
464 
465   /**
466    * Checks that a Hasher returns the same HashCode when given the same input, and also that the
467    * collision rate looks sane.
468    */
assertInvariants(HashFunction hashFunction)469   static void assertInvariants(HashFunction hashFunction) {
470     int objects = 100;
471     Set<HashCode> hashcodes = Sets.newHashSetWithExpectedSize(objects);
472     Random random = new Random(314159);
473     for (int i = 0; i < objects; i++) {
474       int value = random.nextInt();
475       HashCode hashcode1 = hashFunction.hashInt(value);
476       HashCode hashcode2 = hashFunction.hashInt(value);
477       Assert.assertEquals(hashcode1, hashcode2); // idempotent
478       Assert.assertEquals(hashFunction.bits(), hashcode1.bits());
479       Assert.assertEquals(hashFunction.bits(), hashcode1.asBytes().length * 8);
480       hashcodes.add(hashcode1);
481     }
482     Assert.assertTrue(hashcodes.size() > objects * 0.95); // quite relaxed test
483 
484     assertHashBytesThrowsCorrectExceptions(hashFunction);
485     assertIndependentHashers(hashFunction);
486     assertShortcutsAreEquivalent(hashFunction, 512);
487   }
488 
assertHashByteBufferInvariants(HashFunction hashFunction)489   static void assertHashByteBufferInvariants(HashFunction hashFunction) {
490     assertHashByteBufferMatchesBytes(hashFunction);
491     assertHashByteBufferExhaustsBuffer(hashFunction);
492     assertHashByteBufferPreservesByteOrder(hashFunction);
493     assertHasherByteBufferPreservesByteOrder(hashFunction);
494   }
495 
assertHashByteBufferMatchesBytes(HashFunction hashFunction)496   static void assertHashByteBufferMatchesBytes(HashFunction hashFunction) {
497     Random rng = new Random(0L);
498     byte[] bytes = new byte[rng.nextInt(256) + 1];
499     rng.nextBytes(bytes);
500     assertEquals(hashFunction.hashBytes(bytes), hashFunction.hashBytes(ByteBuffer.wrap(bytes)));
501   }
502 
assertHashByteBufferExhaustsBuffer(HashFunction hashFunction)503   static void assertHashByteBufferExhaustsBuffer(HashFunction hashFunction) {
504     Random rng = new Random(0L);
505     byte[] bytes = new byte[rng.nextInt(256) + 1];
506     rng.nextBytes(bytes);
507     ByteBuffer buffer = ByteBuffer.wrap(bytes);
508     HashCode unused = hashFunction.hashBytes(buffer);
509     assertFalse(buffer.hasRemaining());
510   }
511 
assertHashByteBufferPreservesByteOrder(HashFunction hashFunction)512   static void assertHashByteBufferPreservesByteOrder(HashFunction hashFunction) {
513     Random rng = new Random(0L);
514     byte[] bytes = new byte[rng.nextInt(256) + 1];
515     rng.nextBytes(bytes);
516     ByteBuffer littleEndian = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
517     ByteBuffer bigEndian = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
518     assertEquals(hashFunction.hashBytes(littleEndian), hashFunction.hashBytes(bigEndian));
519     assertEquals(ByteOrder.LITTLE_ENDIAN, littleEndian.order());
520     assertEquals(ByteOrder.BIG_ENDIAN, bigEndian.order());
521   }
522 
assertHasherByteBufferPreservesByteOrder(HashFunction hashFunction)523   static void assertHasherByteBufferPreservesByteOrder(HashFunction hashFunction) {
524     Random rng = new Random(0L);
525     byte[] bytes = new byte[rng.nextInt(256) + 1];
526     rng.nextBytes(bytes);
527     ByteBuffer littleEndian = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
528     ByteBuffer bigEndian = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
529     assertEquals(
530         hashFunction.newHasher().putBytes(littleEndian).hash(),
531         hashFunction.newHasher().putBytes(bigEndian).hash());
532     assertEquals(ByteOrder.LITTLE_ENDIAN, littleEndian.order());
533     assertEquals(ByteOrder.BIG_ENDIAN, bigEndian.order());
534   }
535 
assertHashBytesThrowsCorrectExceptions(HashFunction hashFunction)536   static void assertHashBytesThrowsCorrectExceptions(HashFunction hashFunction) {
537     {
538       HashCode unused = hashFunction.hashBytes(new byte[64], 0, 0);
539     }
540 
541     try {
542       hashFunction.hashBytes(new byte[128], -1, 128);
543       Assert.fail();
544     } catch (IndexOutOfBoundsException expected) {
545     }
546     try {
547       hashFunction.hashBytes(new byte[128], 64, 256 /* too long len */);
548       Assert.fail();
549     } catch (IndexOutOfBoundsException expected) {
550     }
551     try {
552       hashFunction.hashBytes(new byte[64], 0, -1);
553       Assert.fail();
554     } catch (IndexOutOfBoundsException expected) {
555     }
556   }
557 
assertIndependentHashers(HashFunction hashFunction)558   static void assertIndependentHashers(HashFunction hashFunction) {
559     int numActions = 100;
560     // hashcodes from non-overlapping hash computations
561     HashCode expected1 = randomHash(hashFunction, new Random(1L), numActions);
562     HashCode expected2 = randomHash(hashFunction, new Random(2L), numActions);
563 
564     // equivalent, but overlapping, computations (should produce the same results as above)
565     Random random1 = new Random(1L);
566     Random random2 = new Random(2L);
567     Hasher hasher1 = hashFunction.newHasher();
568     Hasher hasher2 = hashFunction.newHasher();
569     for (int i = 0; i < numActions; i++) {
570       RandomHasherAction.pickAtRandom(random1).performAction(random1, ImmutableSet.of(hasher1));
571       RandomHasherAction.pickAtRandom(random2).performAction(random2, ImmutableSet.of(hasher2));
572     }
573 
574     Assert.assertEquals(expected1, hasher1.hash());
575     Assert.assertEquals(expected2, hasher2.hash());
576   }
577 
randomHash(HashFunction hashFunction, Random random, int numActions)578   static HashCode randomHash(HashFunction hashFunction, Random random, int numActions) {
579     Hasher hasher = hashFunction.newHasher();
580     for (int i = 0; i < numActions; i++) {
581       RandomHasherAction.pickAtRandom(random).performAction(random, ImmutableSet.of(hasher));
582     }
583     return hasher.hash();
584   }
585 
assertShortcutsAreEquivalent(HashFunction hashFunction, int trials)586   private static void assertShortcutsAreEquivalent(HashFunction hashFunction, int trials) {
587     Random random = new Random(42085L);
588     for (int i = 0; i < trials; i++) {
589       assertHashBytesEquivalence(hashFunction, random);
590       assertHashByteBufferEquivalence(hashFunction, random);
591       assertHashIntEquivalence(hashFunction, random);
592       assertHashLongEquivalence(hashFunction, random);
593       assertHashStringEquivalence(hashFunction, random);
594       assertHashStringWithSurrogatesEquivalence(hashFunction, random);
595     }
596   }
597 
assertHashBytesEquivalence(HashFunction hashFunction, Random random)598   private static void assertHashBytesEquivalence(HashFunction hashFunction, Random random) {
599     int size = random.nextInt(2048);
600     byte[] bytes = new byte[size];
601     random.nextBytes(bytes);
602     assertEquals(
603         hashFunction.hashBytes(bytes), hashFunction.newHasher(size).putBytes(bytes).hash());
604     int off = random.nextInt(size);
605     int len = random.nextInt(size - off);
606     assertEquals(
607         hashFunction.hashBytes(bytes, off, len),
608         hashFunction.newHasher(size).putBytes(bytes, off, len).hash());
609   }
610 
assertHashByteBufferEquivalence(HashFunction hashFunction, Random random)611   private static void assertHashByteBufferEquivalence(HashFunction hashFunction, Random random) {
612     int size = random.nextInt(2048);
613     byte[] bytes = new byte[size];
614     random.nextBytes(bytes);
615     assertEquals(
616         hashFunction.hashBytes(ByteBuffer.wrap(bytes)),
617         hashFunction.newHasher(size).putBytes(ByteBuffer.wrap(bytes)).hash());
618     int off = random.nextInt(size);
619     int len = random.nextInt(size - off);
620     assertEquals(
621         hashFunction.hashBytes(ByteBuffer.wrap(bytes, off, len)),
622         hashFunction.newHasher(size).putBytes(ByteBuffer.wrap(bytes, off, len)).hash());
623   }
624 
assertHashIntEquivalence(HashFunction hashFunction, Random random)625   private static void assertHashIntEquivalence(HashFunction hashFunction, Random random) {
626     int i = random.nextInt();
627     assertEquals(hashFunction.hashInt(i), hashFunction.newHasher().putInt(i).hash());
628   }
629 
assertHashLongEquivalence(HashFunction hashFunction, Random random)630   private static void assertHashLongEquivalence(HashFunction hashFunction, Random random) {
631     long l = random.nextLong();
632     assertEquals(hashFunction.hashLong(l), hashFunction.newHasher().putLong(l).hash());
633   }
634 
635   private static final ImmutableSet<Charset> CHARSETS =
636       ImmutableSet.of(ISO_8859_1, US_ASCII, UTF_16, UTF_16BE, UTF_16LE, UTF_8);
637 
assertHashStringEquivalence(HashFunction hashFunction, Random random)638   private static void assertHashStringEquivalence(HashFunction hashFunction, Random random) {
639     // Test that only data and data-order is important, not the individual operations.
640     new EqualsTester()
641         .addEqualityGroup(
642             hashFunction.hashUnencodedChars("abc"),
643             hashFunction.newHasher().putUnencodedChars("abc").hash(),
644             hashFunction.newHasher().putUnencodedChars("ab").putUnencodedChars("c").hash(),
645             hashFunction.newHasher().putUnencodedChars("a").putUnencodedChars("bc").hash(),
646             hashFunction
647                 .newHasher()
648                 .putUnencodedChars("a")
649                 .putUnencodedChars("b")
650                 .putUnencodedChars("c")
651                 .hash(),
652             hashFunction.newHasher().putChar('a').putUnencodedChars("bc").hash(),
653             hashFunction.newHasher().putUnencodedChars("ab").putChar('c').hash(),
654             hashFunction.newHasher().putChar('a').putChar('b').putChar('c').hash())
655         .testEquals();
656 
657     int size = random.nextInt(2048);
658     byte[] bytes = new byte[size];
659     random.nextBytes(bytes);
660     String string = new String(bytes, US_ASCII);
661     assertEquals(
662         hashFunction.hashUnencodedChars(string),
663         hashFunction.newHasher().putUnencodedChars(string).hash());
664     for (Charset charset : CHARSETS) {
665       assertEquals(
666           hashFunction.hashString(string, charset),
667           hashFunction.newHasher().putString(string, charset).hash());
668     }
669   }
670 
671   /**
672    * This verifies that putUnencodedChars(String) and hashUnencodedChars(String) are equivalent,
673    * even for funny strings composed by (possibly unmatched, and mostly illegal) surrogate
674    * characters. (But doesn't test that they do the right thing - just their consistency).
675    */
assertHashStringWithSurrogatesEquivalence( HashFunction hashFunction, Random random)676   private static void assertHashStringWithSurrogatesEquivalence(
677       HashFunction hashFunction, Random random) {
678     int size = random.nextInt(8) + 1;
679     char[] chars = new char[size];
680     for (int i = 0; i < chars.length; i++) {
681       chars[i] = random.nextBoolean() ? randomLowSurrogate(random) : randomHighSurrogate(random);
682     }
683     String string = new String(chars);
684     assertEquals(
685         hashFunction.hashUnencodedChars(string),
686         hashFunction.newHasher().putUnencodedChars(string).hash());
687   }
688 
randomLowSurrogate(Random random)689   static char randomLowSurrogate(Random random) {
690     return (char)
691         (Character.MIN_LOW_SURROGATE
692             + random.nextInt(Character.MAX_LOW_SURROGATE - Character.MIN_LOW_SURROGATE + 1));
693   }
694 
randomHighSurrogate(Random random)695   static char randomHighSurrogate(Random random) {
696     return (char)
697         (Character.MIN_HIGH_SURROGATE
698             + random.nextInt(Character.MAX_HIGH_SURROGATE - Character.MIN_HIGH_SURROGATE + 1));
699   }
700 }
701