• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Brute force collision tester for 64-bit hashes
3  * Part of the xxHash project
4  * Copyright (C) 2019-2020 Yann Collet
5  *
6  * GPL v2 License
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * You can contact the author at:
23  *   - xxHash homepage: https://www.xxhash.com
24  *   - xxHash source repository: https://github.com/Cyan4973/xxHash
25  */
26 
27 /*
28  * The collision tester will generate 24 billion hashes (by default),
29  * and count how many collisions were produced by the 64-bit hash algorithm.
30  * The optimal amount of collisions for 64-bit is ~18 collisions.
31  * A good hash should be close to this figure.
32  *
33  * This program requires a lot of memory:
34  * - Either store hash values directly => 192 GB
35  * - Or use a filter:
36  *   -  32 GB (by default) for the filter itself
37  *   -  + ~14 GB for the list of hashes (depending on the filter's outcome)
38  * Due to these memory constraints, it requires a 64-bit system.
39  */
40 
41 
42  /* ===  Dependencies  === */
43 
44 #include <stdint.h>   /* uint64_t */
45 #include <stdlib.h>   /* malloc, free, qsort, exit */
46 #include <string.h>   /* memset */
47 #include <stdio.h>    /* printf, fflush */
48 
49 #undef NDEBUG   /* ensure assert is _not_ disabled */
50 #include <assert.h>
51 
52 #include "hashes.h"   /* UniHash, hashfn, hashfnTable */
53 
54 #include "sort.hh"    /* sort64 */
55 
56 
57 
58 typedef enum { ht32, ht64, ht128 } Htype_e;
59 
60 /* ===  Debug  === */
61 
62 #define EXIT(...) { printf(__VA_ARGS__); printf("\n"); exit(1); }
63 
hexRaw(const void * buffer,size_t size)64 static void hexRaw(const void* buffer, size_t size)
65 {
66     const unsigned char* p = (const unsigned char*)buffer;
67     for (size_t i=0; i<size; i++) {
68         printf("%02X", p[i]);
69     }
70 }
71 
printHash(const void * table,size_t n,Htype_e htype)72 void printHash(const void* table, size_t n, Htype_e htype)
73 {
74     if ((htype == ht64) || (htype == ht32)){
75         uint64_t const h64 = ((const uint64_t*)table)[n];
76         hexRaw(&h64, sizeof(h64));
77     } else {
78         assert(htype == ht128);
79         XXH128_hash_t const h128 = ((const XXH128_hash_t*)table)[n];
80         hexRaw(&h128, sizeof(h128));
81     }
82 }
83 
84 /* ===  Generate Random unique Samples to hash  === */
85 
86 /*
87  * These functions will generate and update a sample to hash.
88  * initSample() will fill a buffer with random bytes,
89  * updateSample() will modify one slab in the input buffer.
90  * updateSample() guarantees it will produce unique samples,
91  * but it needs to know the total number of samples.
92  */
93 
94 
95 static const uint64_t prime64_1 = 11400714785074694791ULL;   /* 0b1001111000110111011110011011000110000101111010111100101010000111 */
96 static const uint64_t prime64_2 = 14029467366897019727ULL;   /* 0b1100001010110010101011100011110100100111110101001110101101001111 */
97 static const uint64_t prime64_3 =  1609587929392839161ULL;   /* 0b0001011001010110011001111011000110011110001101110111100111111001 */
98 
avalanche64(uint64_t h64)99 static uint64_t avalanche64(uint64_t h64)
100 {
101     h64 ^= h64 >> 33;
102     h64 *= prime64_2;
103     h64 ^= h64 >> 29;
104     h64 *= prime64_3;
105     h64 ^= h64 >> 32;
106     return h64;
107 }
108 
randomByte(size_t n)109 static unsigned char randomByte(size_t n)
110 {
111     uint64_t n64 = avalanche64(n+1);
112     n64 *= prime64_1;
113     return (unsigned char)(n64 >> 56);
114 }
115 
116 typedef enum { sf_slab5, sf_sparse } sf_genMode;
117 
118 
119 #ifdef SLAB5
120 
121 /*
122  * Slab5 sample generation.
123  * This algorithm generates unique inputs flipping on average 16 bits per candidate.
124  * It is generally much more friendly for most hash algorithms, especially
125  * weaker ones, as it shuffles more the input.
126  * The algorithm also avoids overfitting the per4 or per8 ingestion patterns.
127  */
128 
129 #define SLAB_SIZE 5
130 
131 typedef struct {
132     void* buffer;
133     size_t size;
134     sf_genMode mode;
135     size_t prngSeed;
136     uint64_t hnb;
137 } sampleFactory;
138 
init_sampleFactory(sampleFactory * sf,uint64_t htotal)139 static void init_sampleFactory(sampleFactory* sf, uint64_t htotal)
140 {
141     uint64_t const minNbSlabs = ((htotal-1) >> 32) + 1;
142     uint64_t const minSize = minNbSlabs * SLAB_SIZE;
143     if (sf->size < minSize)
144         EXIT("sample size must be >= %i bytes for this amount of hashes",
145             (int)minSize);
146 
147     unsigned char* const p = (unsigned char*)sf->buffer;
148     for (size_t n=0; n < sf->size; n++)
149         p[n] = randomByte(n);
150     sf->hnb = 0;
151 }
152 
153 static sampleFactory*
create_sampleFactory(size_t size,uint64_t htotal,uint64_t seed)154 create_sampleFactory(size_t size, uint64_t htotal, uint64_t seed)
155 {
156     sampleFactory* const sf = malloc(sizeof(sampleFactory));
157     if (!sf) EXIT("not enough memory");
158     void* const buffer = malloc(size);
159     if (!buffer) EXIT("not enough memory");
160     sf->buffer = buffer;
161     sf->size = size;
162     sf->mode = sf_slab5;
163     sf->prngSeed = seed;
164     init_sampleFactory(sf, htotal);
165     return sf;
166 }
167 
free_sampleFactory(sampleFactory * sf)168 static void free_sampleFactory(sampleFactory* sf)
169 {
170     if (!sf) return;
171     free(sf->buffer);
172     free(sf);
173 }
174 
update_sampleFactory(sampleFactory * sf)175 static inline void update_sampleFactory(sampleFactory* sf)
176 {
177     size_t const nbSlabs = sf->size / SLAB_SIZE;
178     size_t const SlabNb = sf->hnb % nbSlabs;
179     sf->hnb++;
180 
181     char* const ptr = (char*)sf->buffer;
182     size_t const start = (SlabNb * SLAB_SIZE) + 1;
183     uint32_t val32;
184     memcpy(&val32, ptr+start, sizeof(val32));
185     static const uint32_t prime32_5 = 374761393U;
186     val32 += prime32_5;
187     memcpy(ptr+start, &val32, sizeof(val32));
188 }
189 
190 #else
191 
192 /*
193  * Sparse sample generation.
194  * This is the default pattern generator.
195  * It only flips one bit at a time (mostly).
196  * Low hamming distance scenario is more difficult for weak hash algorithms.
197  * Note that CRC is immune to this scenario, since they are specifically
198  * designed to detect low hamming distances.
199  * Prefer the Slab5 pattern generator for collisions on CRC algorithms.
200  */
201 
202 #define SPARSE_LEVEL_MAX 15
203 
204 /* Nb of combinations of m bits in a register of n bits */
Cnm(int n,int m)205 static double Cnm(int n, int m)
206 {
207     assert(n > 0);
208     assert(m > 0);
209     assert(m <= m);
210     double acc = 1;
211     for (int i=0; i<m; i++) {
212         acc *= n - i;
213         acc /= 1 + i;
214     }
215     return acc;
216 }
217 
enoughCombos(size_t size,uint64_t htotal)218 static int enoughCombos(size_t size, uint64_t htotal)
219 {
220     if (size < 2) return 0;   /* ensure no multiplication by negative */
221     uint64_t acc = 0;
222     uint64_t const srcBits = size * 8; assert(srcBits < INT_MAX);
223     int nbBitsSet = 0;
224     while (acc < htotal) {
225         nbBitsSet++;
226         if (nbBitsSet >= SPARSE_LEVEL_MAX) return 0;
227         acc += (uint64_t)Cnm((int)srcBits, nbBitsSet);
228     }
229     return 1;
230 }
231 
232 typedef struct {
233     void* buffer;
234     size_t size;
235     sf_genMode mode;
236     /* sparse */
237     size_t bitIdx[SPARSE_LEVEL_MAX];
238     int level;
239     size_t maxBitIdx;
240     /* slab5 */
241     size_t nbSlabs;
242     size_t current;
243     size_t prngSeed;
244 } sampleFactory;
245 
init_sampleFactory(sampleFactory * sf,uint64_t htotal)246 static void init_sampleFactory(sampleFactory* sf, uint64_t htotal)
247 {
248     if (!enoughCombos(sf->size, htotal)) {
249         EXIT("sample size must be larger for this amount of hashes");
250     }
251 
252     memset(sf->bitIdx, 0, sizeof(sf->bitIdx));
253     sf->level = 0;
254 
255     unsigned char* const p = (unsigned char*)sf->buffer;
256     for (size_t n=0; n<sf->size; n++)
257         p[n] = randomByte(sf->prngSeed + n);
258 }
259 
260 static sampleFactory*
create_sampleFactory(size_t size,uint64_t htotal,uint64_t seed)261 create_sampleFactory(size_t size, uint64_t htotal, uint64_t seed)
262 {
263     sampleFactory* const sf = malloc(sizeof(sampleFactory));
264     if (!sf) EXIT("not enough memory");
265     void* const buffer = malloc(size);
266     if (!buffer) EXIT("not enough memory");
267     sf->buffer = buffer;
268     sf->size = size;
269     sf->mode = sf_sparse;
270     sf->maxBitIdx = size * 8;
271     sf->prngSeed = seed;
272     init_sampleFactory(sf, htotal);
273     return sf;
274 }
275 
free_sampleFactory(sampleFactory * sf)276 static void free_sampleFactory(sampleFactory* sf)
277 {
278     if (!sf) return;
279     free(sf->buffer);
280     free(sf);
281 }
282 
flipbit(void * buffer,uint64_t bitID)283 static void flipbit(void* buffer, uint64_t bitID)
284 {
285     size_t const pos = bitID >> 3;
286     unsigned char const mask = (unsigned char)(1 << (bitID & 7));
287     unsigned char* const p = (unsigned char*)buffer;
288     p[pos] ^= mask;
289 }
290 
updateBit(void * buffer,size_t * bitIdx,int level,size_t max)291 static int updateBit(void* buffer, size_t* bitIdx, int level, size_t max)
292 {
293     if (level==0) return 0;   /* can't progress further */
294 
295     flipbit(buffer, bitIdx[level]); /* erase previous bits */
296 
297     if (bitIdx[level] < max-1) { /* simple case: go to next bit */
298         bitIdx[level]++;
299         flipbit(buffer, bitIdx[level]); /* set new bit */
300         return 1;
301     }
302 
303     /* reached last bit: need to update a bit from lower level */
304     if (!updateBit(buffer, bitIdx, level-1, max-1)) return 0;
305     bitIdx[level] = bitIdx[level-1] + 1;
306     flipbit(buffer, bitIdx[level]); /* set new bit */
307     return 1;
308 }
309 
update_sampleFactory(sampleFactory * sf)310 static inline void update_sampleFactory(sampleFactory* sf)
311 {
312     if (!updateBit(sf->buffer, sf->bitIdx, sf->level, sf->maxBitIdx)) {
313         /* no more room => move to next level */
314         sf->level++;
315         assert(sf->level < SPARSE_LEVEL_MAX);
316 
317         /* set new bits */
318         for (int i=1; i <= sf->level; i++) {
319             sf->bitIdx[i] = (size_t)(i-1);
320             flipbit(sf->buffer, sf->bitIdx[i]);
321         }
322     }
323 }
324 
325 #endif /* pattern generator selection */
326 
327 
328 /* ===  Candidate Filter  === */
329 
330 typedef unsigned char Filter;
331 
create_Filter(int bflog)332 Filter* create_Filter(int bflog)
333 {
334     assert(bflog < 64 && bflog > 1);
335     size_t bfsize = (size_t)1 << bflog;
336     Filter* bf = malloc(bfsize);
337     assert(((void)"Filter creation failed", bf));
338     memset(bf, 0, bfsize);
339     return bf;
340 }
341 
free_Filter(Filter * bf)342 void free_Filter(Filter* bf)
343 {
344     free(bf);
345 }
346 
347 #ifdef FILTER_1_PROBE
348 
349 /*
350  * Attach hash to a slot
351  * return: Nb of potential collision candidates detected
352  *          0: position not yet occupied
353  *          2: position previously occupied by a single candidate
354  *          1: position already occupied by multiple candidates
355  */
Filter_insert(Filter * bf,int bflog,uint64_t hash)356 inline int Filter_insert(Filter* bf, int bflog, uint64_t hash)
357 {
358     int const slotNb = hash & 3;
359     int const shift = slotNb * 2 ;
360 
361     size_t const bfmask = ((size_t)1 << bflog) - 1;
362     size_t const pos = (hash >> 2) & bfmask;
363 
364     int const existingCandidates = ((((unsigned char*)bf)[pos]) >> shift) & 3;
365 
366     static const int addCandidates[4] = { 0, 2, 1, 1 };
367     static const int nextValue[4] = { 1, 2, 3, 3 };
368 
369     ((unsigned char*)bf)[pos] |= (unsigned char)(nextValue[existingCandidates] << shift);
370     return addCandidates[existingCandidates];
371 }
372 
373 /*
374  * Check if provided 64-bit hash is a collision candidate
375  * Requires the slot to be occupied by at least 2 candidates.
376  * return >0 if hash is a collision candidate
377  *         0 otherwise (slot unoccupied, or only one candidate)
378  * note: unoccupied slots should not happen in this algorithm,
379  *       since all hashes are supposed to have been inserted at least once.
380  */
Filter_check(const Filter * bf,int bflog,uint64_t hash)381 inline int Filter_check(const Filter* bf, int bflog, uint64_t hash)
382 {
383     int const slotNb = hash & 3;
384     int const shift = slotNb * 2;
385 
386     size_t const bfmask = ((size_t)1 << bflog) - 1;
387     size_t const pos = (hash >> 2) & bfmask;
388 
389     return (((const unsigned char*)bf)[pos]) >> (shift+1) & 1;
390 }
391 
392 #else
393 
394 /*
395  * 2-probes strategy,
396  * more efficient at filtering candidates,
397  * requires filter size to be > nb of hashes
398  */
399 
400 #define MIN(a,b)   ((a) < (b) ? (a) : (b))
401 #define MAX(a,b)   ((a) > (b) ? (a) : (b))
402 
403 /*
404  * Attach hash to 2 slots
405  * return: Nb of potential candidates detected
406  *          0: position not yet occupied
407  *          2: position previously occupied by a single candidate (at most)
408  *          1: position already occupied by multiple candidates
409  */
Filter_insert(Filter * bf,int bflog,uint64_t hash)410 static inline int Filter_insert(Filter* bf, int bflog, uint64_t hash)
411  {
412      hash = avalanche64(hash);
413      unsigned const slot1 = hash & 255;
414      hash >>= 8;
415      unsigned const slot2 = hash & 255;
416      hash >>= 8;
417 
418      size_t const fclmask = ((size_t)1 << (bflog-6)) - 1;
419      size_t const cacheLineNb = hash & fclmask;
420 
421      size_t const pos1 = (cacheLineNb << 6) + (slot1 >> 2);
422      unsigned const shift1 = (slot1 & 3) * 2;
423      unsigned const ex1 = (bf[pos1] >> shift1) & 3;
424 
425      size_t const pos2 = (cacheLineNb << 6) + (slot2 >> 2);
426      unsigned const shift2 = (slot2 & 3) * 2;
427      unsigned const ex2 = (bf[pos2] >> shift2) & 3;
428 
429      unsigned const existing = MIN(ex1, ex2);
430 
431      static const int addCandidates[4] = { 0, 2, 1, 1 };
432      static const unsigned nextValue[4] = { 1, 2, 3, 3 };
433 
434      bf[pos1] &= (Filter)(~(3 << shift1)); /* erase previous value */
435      bf[pos1] |= (Filter)(MAX(ex1, nextValue[existing]) << shift1);
436      bf[pos2] |= (Filter)(MAX(ex2, nextValue[existing]) << shift2);
437 
438      return addCandidates[existing];
439  }
440 
441 
442 /*
443  * Check if provided 64-bit hash is a collision candidate
444  * Requires the slot to be occupied by at least 2 candidates.
445  * return >0 if hash is a collision candidate
446  *         0 otherwise (slot unoccupied, or only one candidate)
447  * note: unoccupied slots should not happen in this algorithm,
448  *       since all hashes are supposed to have been inserted at least once.
449  */
Filter_check(const Filter * bf,int bflog,uint64_t hash)450 static inline int Filter_check(const Filter* bf, int bflog, uint64_t hash)
451  {
452      hash = avalanche64(hash);
453      unsigned const slot1 = hash & 255;
454      hash >>= 8;
455      unsigned const slot2 = hash & 255;
456      hash >>= 8;
457 
458      size_t const fclmask = ((size_t)1 << (bflog-6)) - 1;
459      size_t const cacheLineNb = hash & fclmask;
460 
461      size_t const pos1 = (cacheLineNb << 6) + (slot1 >> 2);
462      unsigned const shift1 = (slot1 & 3) * 2;
463      unsigned const ex1 = (bf[pos1] >> shift1) & 3;
464 
465      size_t const pos2 = (cacheLineNb << 6) + (slot2 >> 2);
466      unsigned const shift2 = (slot2 & 3) * 2;
467      unsigned const ex2 = (bf[pos2] >> shift2) & 3;
468 
469      return (ex1 >= 2) && (ex2 >= 2);
470  }
471 
472 #endif // FILTER_1_PROBE
473 
474 
475 /* ===  Display  === */
476 
477 #include <time.h>   /* clock_t, clock, time_t, time, difftime */
478 
update_indicator(uint64_t v,uint64_t total)479 void update_indicator(uint64_t v, uint64_t total)
480 {
481     static clock_t start = 0;
482     if (start==0) start = clock();
483     clock_t const updateRate = CLOCKS_PER_SEC / 2;
484 
485     clock_t const clockSpan = (clock_t)(clock() - start);
486     if (clockSpan > updateRate) {
487         start = clock();
488         assert(v <= total);
489         assert(total > 0);
490         double share = ((double)v / (double)total) * 100;
491         printf("%6.2f%% (%llu)  \r", share, (unsigned long long)v);
492         fflush(NULL);
493     }
494 }
495 
496 /* note: not thread safe */
displayDelay(double delay_s)497 const char* displayDelay(double delay_s)
498 {
499     static char delayString[50];
500     memset(delayString, 0, sizeof(delayString));
501 
502     int const mn = ((int)delay_s / 60) % 60;
503     int const h = (int)delay_s / 3600;
504     int const sec = (int)delay_s % 60;
505 
506     char* p = delayString;
507     if (h) sprintf(p, "%i h ", h);
508     if (mn || h) {
509         p = delayString + strlen(delayString);
510         sprintf(p, "%i mn ", mn);
511     }
512     p = delayString + strlen(delayString);
513     sprintf(p, "%is ", sec);
514 
515     return delayString;
516 }
517 
518 
519 /* ===  Math  === */
520 
power(uint64_t base,int p)521 static double power(uint64_t base, int p)
522 {
523     double value = 1;
524     assert(p>=0);
525     for (int i=0; i<p; i++) {
526         value *= (double)base;
527     }
528     return value;
529 }
530 
estimateNbCollisions(uint64_t nbH,int nbBits)531 static double estimateNbCollisions(uint64_t nbH, int nbBits)
532 {
533     return ((double)nbH * (double)(nbH-1)) / power(2, nbBits+1);
534 }
535 
highestBitSet(uint64_t v)536 static int highestBitSet(uint64_t v)
537 {
538     assert(v!=0);
539     int bitId = 0;
540     while (v >>= 1) bitId++;
541     return bitId;
542 }
543 
544 
545 /* ===  Filter and search collisions  === */
546 
547 #undef NDEBUG   /* ensure assert is not disabled */
548 #include <assert.h>
549 
550 /* will recommend 24 billion samples for 64-bit hashes,
551  * expecting 18 collisions for a good 64-bit hash */
552 #define NB_BITS_MAX 64   /* can't store nor analyze hash wider than 64-bits for the time being */
select_nbh(int nbBits)553 uint64_t select_nbh(int nbBits)
554 {
555     assert(nbBits > 0);
556     if (nbBits > NB_BITS_MAX) nbBits = NB_BITS_MAX;
557     double targetColls = (double)((128 + 17) - (nbBits * 2));
558     uint64_t nbH = 24;
559     while (estimateNbCollisions(nbH, nbBits) < targetColls) nbH *= 2;
560     return nbH;
561 }
562 
563 
564 typedef struct {
565     uint64_t nbCollisions;
566 } searchCollisions_results;
567 
568 typedef struct {
569     uint64_t nbH;
570     uint64_t mask;
571     uint64_t maskSelector;
572     size_t sampleSize;
573     uint64_t prngSeed;
574     int filterLog;      /* <0 = disable filter;  0 = auto-size; */
575     int hashID;
576     int display;
577     int nbThreads;
578     searchCollisions_results* resultPtr;
579 } searchCollisions_parameters;
580 
581 #define DISPLAY(...) { if (display) printf(__VA_ARGS__); }
582 
isEqual(void * hTablePtr,size_t index1,size_t index2,Htype_e htype)583 static int isEqual(void* hTablePtr, size_t index1, size_t index2, Htype_e htype)
584 {
585     if ((htype == ht64) || (htype == ht32)) {
586         uint64_t const h1 = ((const uint64_t*)hTablePtr)[index1];
587         uint64_t const h2 = ((const uint64_t*)hTablePtr)[index2];
588         return (h1 == h2);
589     } else {
590         assert(htype == ht128);
591         XXH128_hash_t const h1 = ((const XXH128_hash_t*)hTablePtr)[index1];
592         XXH128_hash_t const h2 = ((const XXH128_hash_t*)hTablePtr)[index2];
593         return XXH128_isEqual(h1, h2);
594     }
595 }
596 
isHighEqual(void * hTablePtr,size_t index1,size_t index2,Htype_e htype,int rShift)597 static int isHighEqual(void* hTablePtr, size_t index1, size_t index2, Htype_e htype, int rShift)
598 {
599     uint64_t h1, h2;
600     if ((htype == ht64) || (htype == ht32)) {
601         h1 = ((const uint64_t*)hTablePtr)[index1];
602         h2 = ((const uint64_t*)hTablePtr)[index2];
603     } else {
604         assert(htype == ht128);
605         h1 = ((const XXH128_hash_t*)hTablePtr)[index1].high64;
606         h2 = ((const XXH128_hash_t*)hTablePtr)[index2].high64;
607         assert(rShift >= 64);
608         rShift -= 64;
609     }
610     assert(0 <= rShift && rShift < 64);
611     return (h1 >> rShift) == (h2 >> rShift);
612 }
613 
614 /* assumption: (htype*)hTablePtr[index] is valid */
addHashCandidate(void * hTablePtr,UniHash h,Htype_e htype,size_t index)615 static void addHashCandidate(void* hTablePtr, UniHash h, Htype_e htype, size_t index)
616 {
617     if ((htype == ht64) || (htype == ht32)) {
618         ((uint64_t*)hTablePtr)[index] = h.h64;
619     } else {
620         assert(htype == ht128);
621         ((XXH128_hash_t*)hTablePtr)[index] = h.h128;
622     }
623 }
624 
getNbBits_fromHtype(Htype_e htype)625 static int getNbBits_fromHtype(Htype_e htype) {
626     switch(htype) {
627         case ht32: return 32;
628         case ht64: return 64;
629         case ht128:return 128;
630         default: EXIT("hash size not supported");
631     }
632 }
633 
getHtype_fromHbits(int nbBits)634 static Htype_e getHtype_fromHbits(int nbBits) {
635     switch(nbBits) {
636         case 32 : return ht32;
637         case 64 : return ht64;
638         case 128: return ht128;
639         default: EXIT("hash size not supported");
640     }
641 }
642 
search_collisions(searchCollisions_parameters param)643 static size_t search_collisions(
644     searchCollisions_parameters param)
645 {
646     uint64_t totalH = param.nbH;
647     const uint64_t hMask = param.mask;
648     const uint64_t hSelector = param.maskSelector;
649     int bflog = param.filterLog;
650     const int filter = (param.filterLog >= 0);
651     const size_t sampleSize = param.sampleSize;
652     const int hashID = param.hashID;
653     const Htype_e htype = getHtype_fromHbits(hashfnTable[hashID].bits);
654     const int display = param.display;
655     /* init */
656     sampleFactory* const sf = create_sampleFactory(sampleSize, totalH, param.prngSeed);
657     if (!sf) EXIT("not enough memory");
658 
659     //const char* const hname = hashfnTable[hashID].name;
660     hashfn const hfunction = hashfnTable[hashID].fn;
661     int const hwidth = hashfnTable[hashID].bits;
662     if (totalH == 0) totalH = select_nbh(hwidth);
663     if (bflog == 0) bflog = highestBitSet(totalH) + 1;   /* auto-size filter */
664     uint64_t const bfsize = (1ULL << bflog);
665 
666 
667     /* ===  filter hashes (optional)  === */
668 
669     Filter* bf = NULL;
670     uint64_t nbPresents = totalH;
671 
672     if (filter) {
673         time_t const filterTBegin = time(NULL);
674         DISPLAY(" Creating filter (%i GB) \n", (int)(bfsize >> 30));
675         bf = create_Filter(bflog);
676         if (!bf) EXIT("not enough memory for filter");
677 
678 
679         DISPLAY(" Generate %llu hashes from samples of %u bytes \n",
680                 (unsigned long long)totalH, (unsigned)sampleSize);
681         nbPresents = 0;
682 
683         for (uint64_t n=0; n < totalH; n++) {
684             if (display && ((n&0xFFFFF) == 1) )
685                 update_indicator(n, totalH);
686             update_sampleFactory(sf);
687 
688             UniHash const h = hfunction(sf->buffer, sampleSize);
689             if ((h.h64 & hMask) != hSelector) continue;
690 
691             nbPresents += (uint64_t)Filter_insert(bf, bflog, h.h64);
692         }
693 
694         if (nbPresents==0) {
695             DISPLAY(" Analysis completed: No collision detected \n");
696             if (param.resultPtr) param.resultPtr->nbCollisions = 0;
697             free_Filter(bf);
698             free_sampleFactory(sf);
699             return 0;
700         }
701 
702         {   double const filterDelay = difftime(time(NULL), filterTBegin);
703             DISPLAY(" Generation and filter completed in %s, detected up to %llu candidates \n",
704                     displayDelay(filterDelay), (unsigned long long) nbPresents);
705     }   }
706 
707 
708     /* === store hash candidates: duplicates will be present here === */
709 
710     time_t const storeTBegin = time(NULL);
711     size_t const hashByteSize = (htype == ht128) ? 16 : 8;
712     size_t const tableSize = (nbPresents+1) * hashByteSize;
713     assert(tableSize > nbPresents);  /* check tableSize calculation overflow */
714     DISPLAY(" Storing hash candidates (%i MB) \n", (int)(tableSize >> 20));
715 
716     /* Generate and store hashes */
717     void* const hashCandidates = malloc(tableSize);
718     if (!hashCandidates) EXIT("not enough memory to store candidates");
719     init_sampleFactory(sf, totalH);
720     size_t nbCandidates = 0;
721     for (uint64_t n=0; n < totalH; n++) {
722         if (display && ((n&0xFFFFF) == 1) ) update_indicator(n, totalH);
723         update_sampleFactory(sf);
724 
725         UniHash const h = hfunction(sf->buffer, sampleSize);
726         if ((h.h64 & hMask) != hSelector) continue;
727 
728         if (filter) {
729             if (Filter_check(bf, bflog, h.h64)) {
730                 assert(nbCandidates < nbPresents);
731                 addHashCandidate(hashCandidates, h, htype, nbCandidates++);
732             }
733         } else {
734             assert(nbCandidates < nbPresents);
735             addHashCandidate(hashCandidates, h, htype, nbCandidates++);
736         }
737     }
738     if (nbCandidates < nbPresents) {
739         /* Try to mitigate gnuc_quicksort behavior, by reducing allocated memory,
740          * since gnuc_quicksort uses a lot of additional memory for mergesort */
741         void* const checkPtr = realloc(hashCandidates, nbCandidates * hashByteSize);
742         assert(checkPtr != NULL);
743         assert(checkPtr == hashCandidates);  /* simplification: since we are reducing the size,
744                                               * we hope to keep the same ptr position.
745                                               * Otherwise, hashCandidates must be mutable. */
746         DISPLAY(" List of hashes reduced to %u MB from %u MB (saved %u MB) \n",
747                 (unsigned)((nbCandidates * hashByteSize) >> 20),
748                 (unsigned)(tableSize >> 20),
749                 (unsigned)((tableSize - (nbCandidates * hashByteSize)) >> 20) );
750     }
751     double const storeTDelay = difftime(time(NULL), storeTBegin);
752     DISPLAY(" Stored %llu hash candidates in %s \n",
753             (unsigned long long) nbCandidates, displayDelay(storeTDelay));
754     free_Filter(bf);
755     free_sampleFactory(sf);
756 
757 
758     /* === step 3: look for duplicates === */
759     time_t const sortTBegin = time(NULL);
760     DISPLAY(" Sorting candidates... ");
761     fflush(NULL);
762     if ((htype == ht64) || (htype == ht32)) {
763         /*
764          * Use C++'s std::sort, as it's faster than C stdlib's qsort, and
765          * doesn't suffer from gnuc_libsort's memory expansion
766          */
767         sort64(hashCandidates, nbCandidates);
768     } else {
769         assert(htype == ht128);
770         sort128(hashCandidates, nbCandidates); /* sort with custom comparator */
771     }
772     double const sortTDelay = difftime(time(NULL), sortTBegin);
773     DISPLAY(" Completed in %s \n", displayDelay(sortTDelay));
774 
775     /* scan and count duplicates */
776     time_t const countBegin = time(NULL);
777     DISPLAY(" Looking for duplicates: ");
778     fflush(NULL);
779     size_t collisions = 0;
780     for (size_t n=1; n<nbCandidates; n++) {
781         if (isEqual(hashCandidates, n, n-1, htype)) {
782 #if defined(COL_DISPLAY_DUPLICATES)
783             printf("collision: ");
784             printHash(hashCandidates, n, htype);
785             printf(" / ");
786             printHash(hashCandidates, n-1, htype);
787             printf(" \n");
788 #endif
789             collisions++;
790     }   }
791 
792     if (!filter /* all candidates */ && display /*single thead*/ ) {
793         /* check partial bitfields (high bits) */
794         DISPLAY(" \n");
795         int const hashBits = getNbBits_fromHtype(htype);
796         double worstRatio = 0.;
797         int worstNbHBits = 0;
798         for (int nbHBits = 1; nbHBits < hashBits; nbHBits++) {
799             uint64_t const nbSlots = (uint64_t)1 << nbHBits;
800             double const expectedCollisions = estimateNbCollisions(nbCandidates, nbHBits);
801             if ( (nbSlots > nbCandidates * 100)  /* within range for meaningfull collision analysis results */
802               && (expectedCollisions > 18.0) ) {
803                 int const rShift = hashBits - nbHBits;
804                 size_t HBits_collisions = 0;
805                 for (size_t n=1; n<nbCandidates; n++) {
806                     if (isHighEqual(hashCandidates, n, n-1, htype, rShift)) {
807                         HBits_collisions++;
808                 }   }
809                 double const collisionRatio = (double)HBits_collisions / expectedCollisions;
810                 if (collisionRatio > 2.0) DISPLAY("WARNING !!!  ===> ");
811                 DISPLAY(" high %i bits: %zu collision (%.1f expected): x%.2f \n",
812                         nbHBits, HBits_collisions, expectedCollisions, collisionRatio);
813                 if (collisionRatio > worstRatio) {
814                     worstNbHBits = nbHBits;
815                     worstRatio = collisionRatio;
816         }   }   }
817         DISPLAY("Worst collision ratio at %i high bits: x%.2f \n",
818                 worstNbHBits, worstRatio);
819     }
820     double const countDelay = difftime(time(NULL), countBegin);
821     DISPLAY(" Completed in %s \n", displayDelay(countDelay));
822 
823     /* clean and exit */
824     free (hashCandidates);
825 
826 #if 0  /* debug */
827     for (size_t n=0; n<nbCandidates; n++)
828         printf("0x%016llx \n", (unsigned long long)hashCandidates[n]);
829 #endif
830 
831     if (param.resultPtr) param.resultPtr->nbCollisions = collisions;
832     return collisions;
833 }
834 
835 
836 
837 #if defined(__MACH__) || defined(__linux__)
838 #include <sys/resource.h>
getProcessMemUsage(int children)839 static size_t getProcessMemUsage(int children)
840 {
841     struct rusage stats;
842     if (getrusage(children ? RUSAGE_CHILDREN : RUSAGE_SELF, &stats) == 0)
843       return (size_t)stats.ru_maxrss;
844     return 0;
845 }
846 #else
getProcessMemUsage(int ignore)847 static size_t getProcessMemUsage(int ignore) { return 0; }
848 #endif
849 
time_collisions(searchCollisions_parameters param)850 void time_collisions(searchCollisions_parameters param)
851 {
852     uint64_t totalH = param.nbH;
853     int hashID = param.hashID;
854     int display = param.display;
855 
856     /* init */
857     assert(0 <= hashID && hashID < HASH_FN_TOTAL);
858     //const char* const hname = hashfnTable[hashID].name;
859     int const hwidth = hashfnTable[hashID].bits;
860     if (totalH == 0) totalH = select_nbh(hwidth);
861     double const targetColls = estimateNbCollisions(totalH, hwidth);
862 
863     /* Start the timer to measure start/end of hashing + collision detection. */
864     time_t const programTBegin = time(NULL);
865 
866     /* Generate hashes, and count collisions */
867     size_t const collisions = search_collisions(param);
868 
869     /* display results */
870     double const programTDelay = difftime(time(NULL), programTBegin);
871     size_t const programBytesSelf = getProcessMemUsage(0);
872     size_t const programBytesChildren = getProcessMemUsage(1);
873     DISPLAY("\n\n");
874     DISPLAY("===>   Found %llu collisions (x%.2f, %.1f expected) in %s\n",
875             (unsigned long long)collisions,
876             (double)collisions / targetColls,
877             targetColls,
878             displayDelay(programTDelay));
879     if (programBytesSelf)
880       DISPLAY("===>   MaxRSS(self) %zuMB, MaxRSS(children) %zuMB\n",
881               programBytesSelf>>20,
882               programBytesChildren>>20);
883     DISPLAY("------------------------------------------ \n");
884 }
885 
886 // wrapper for pthread interface
MT_searchCollisions(void * payload)887 void MT_searchCollisions(void* payload)
888 {
889     search_collisions(*(searchCollisions_parameters*)payload);
890 }
891 
892 /* ===  Command Line  === */
893 
894 /*!
895  * readU64FromChar():
896  * Allows and interprets K, KB, KiB, M, MB and MiB suffix.
897  * Will also modify `*stringPtr`, advancing it to the position where it stopped reading.
898  */
readU64FromChar(const char ** stringPtr)899 static uint64_t readU64FromChar(const char** stringPtr)
900 {
901     static uint64_t const max = (((uint64_t)(-1)) / 10) - 1;
902     uint64_t result = 0;
903     while ((**stringPtr >='0') && (**stringPtr <='9')) {
904         assert(result < max);
905         result *= 10;
906         result += (unsigned)(**stringPtr - '0');
907         (*stringPtr)++ ;
908     }
909     if ((**stringPtr=='K') || (**stringPtr=='M') || (**stringPtr=='G')) {
910         uint64_t const maxK = ((uint64_t)(-1)) >> 10;
911         assert(result < maxK);
912         result <<= 10;
913         if ((**stringPtr=='M') || (**stringPtr=='G')) {
914             assert(result < maxK);
915             result <<= 10;
916             if (**stringPtr=='G') {
917                 assert(result < maxK);
918                 result <<= 10;
919             }
920         }
921         (*stringPtr)++;  /* skip `K` or `M` */
922         if (**stringPtr=='i') (*stringPtr)++;
923         if (**stringPtr=='B') (*stringPtr)++;
924     }
925     return result;
926 }
927 
928 
929 /**
930  * longCommandWArg():
931  * Checks if *stringPtr is the same as longCommand.
932  * If yes, @return 1 and advances *stringPtr to the position which immediately follows longCommand.
933  * @return 0 and doesn't modify *stringPtr otherwise.
934  */
longCommandWArg(const char ** stringPtr,const char * longCommand)935 static int longCommandWArg(const char** stringPtr, const char* longCommand)
936 {
937     assert(longCommand); assert(stringPtr); assert(*stringPtr);
938     size_t const comSize = strlen(longCommand);
939     int const result = !strncmp(*stringPtr, longCommand, comSize);
940     if (result) *stringPtr += comSize;
941     return result;
942 }
943 
944 
945 #include "pool.h"
946 
947 /*
948  * As some hashes use different algorithms depending on input size,
949  * it can be necessary to test multiple input sizes
950  * to paint an accurate picture of collision performance
951  */
952 #define SAMPLE_SIZE_DEFAULT 256
953 #define HASHFN_ID_DEFAULT 0
954 
help(const char * exeName)955 void help(const char* exeName)
956 {
957     printf("usage: %s [hashName] [opt] \n\n", exeName);
958     printf("list of hashNames:");
959     printf("%s ", hashfnTable[0].name);
960     for (int i=1; i < HASH_FN_TOTAL; i++) {
961         printf(", %s ", hashfnTable[i].name);
962     }
963     printf(" \n");
964     printf("Default hashName is %s\n", hashfnTable[HASHFN_ID_DEFAULT].name);
965 
966     printf(" \n");
967     printf("Optional parameters: \n");
968     printf("  --nbh=NB       Select nb of hashes to generate (%llu by default) \n", (unsigned long long)select_nbh(64));
969     printf("  --filter       Activates the filter. Slower, but reduces memory usage for the same nb of hashes.\n");
970     printf("  --threadlog=NB Use 2^NB threads.\n");
971     printf("  --len=MB       Set length of the input (%i bytes by default) \n", SAMPLE_SIZE_DEFAULT);
972 }
973 
bad_argument(const char * exeName)974 int bad_argument(const char* exeName)
975 {
976     printf("incorrect command: \n");
977     help(exeName);
978     return 1;
979 }
980 
981 
main(int argc,const char ** argv)982 int main(int argc, const char** argv)
983 {
984     if (sizeof(size_t) < 8) return 1;  // cannot work on systems without ability to allocate objects >= 4 GB
985 
986     assert(argc > 0);
987     const char* const exeName = argv[0];
988     uint64_t totalH = 0;  /* auto, based on nbBits */
989     int bflog = 0;    /* auto */
990     int filter = 0;   /* disabled */
991     size_t sampleSize = SAMPLE_SIZE_DEFAULT;
992     int hashID = HASHFN_ID_DEFAULT;
993     int threadlog = 0;
994     uint64_t prngSeed = 0;
995 
996     int arg_nb;
997     for (arg_nb = 1; arg_nb < argc; arg_nb++) {
998         const char** arg = argv + arg_nb;
999 
1000         if (!strcmp(*arg, "-h")) { help(exeName); return 0; }
1001         if (longCommandWArg(arg, "-T")) { threadlog = (int)readU64FromChar(arg); continue; }
1002 
1003         if (!strcmp(*arg, "--filter"))    { filter=1; continue; }
1004         if (!strcmp(*arg, "--no-filter")) { filter=0; continue; }
1005 
1006         if (longCommandWArg(arg, "--seed")) { prngSeed = readU64FromChar(arg); continue; }
1007         if (longCommandWArg(arg, "--nbh=")) { totalH = readU64FromChar(arg); continue; }
1008         if (longCommandWArg(arg, "--filter=")) { filter=1; bflog = (int)readU64FromChar(arg); assert(bflog < 64); continue; }
1009         if (longCommandWArg(arg, "--filterlog=")) { filter=1; bflog = (int)readU64FromChar(arg); assert(bflog < 64); continue; }
1010         if (longCommandWArg(arg, "--size=")) { sampleSize = (size_t)readU64FromChar(arg); continue; }
1011         if (longCommandWArg(arg, "--len=")) { sampleSize = (size_t)readU64FromChar(arg); continue; }
1012         if (longCommandWArg(arg, "--threadlog=")) { threadlog = (int)readU64FromChar(arg); continue; }
1013 
1014         /* argument understood as hash name (must be correct) */
1015         int hnb;
1016         for (hnb=0; hnb < HASH_FN_TOTAL; hnb++) {
1017             if (!strcmp(*arg, hashfnTable[hnb].name)) { hashID = hnb; break; }
1018         }
1019         if (hnb == HASH_FN_TOTAL) return bad_argument(exeName);
1020     }
1021 
1022     /* init */
1023     const char* const hname = hashfnTable[hashID].name;
1024     int const hwidth = hashfnTable[hashID].bits;
1025     if (totalH == 0) totalH = select_nbh(hwidth);
1026     double const targetColls = estimateNbCollisions(totalH, hwidth);
1027     if (bflog == 0) bflog = highestBitSet(totalH) + 1;   /* auto-size filter */
1028     if (!filter) bflog = -1; // disable filter
1029 
1030     if (sizeof(size_t) < 8)
1031       EXIT("This program has not been validated on architectures other than "
1032            "64bit \n");
1033 
1034     printf(" *** Collision tester for 64+ bit hashes ***  \n\n");
1035     printf("Testing %s algorithm (%i-bit) \n", hname, hwidth);
1036     printf("This program will allocate a lot of memory,\n");
1037     printf("generate %llu %i-bit hashes from samples of %u bytes, \n",
1038             (unsigned long long)totalH, hwidth, (unsigned)sampleSize);
1039     printf("and attempt to produce %.0f collisions. \n\n", targetColls);
1040 
1041     int const nbThreads = 1 << threadlog;
1042     if (nbThreads <= 0) EXIT("Invalid --threadlog value.");
1043 
1044     if (nbThreads == 1) {
1045 
1046         searchCollisions_parameters params;
1047         params.nbH = totalH;
1048         params.mask = 0;
1049         params.maskSelector = 0;
1050         params.sampleSize = sampleSize;
1051         params.filterLog = bflog;
1052         params.hashID = hashID;
1053         params.display = 1;
1054         params.resultPtr = NULL;
1055         params.prngSeed = prngSeed;
1056         params.nbThreads = 1;
1057         time_collisions(params);
1058 
1059     } else { /*  nbThreads > 1 */
1060 
1061         /* use multithreading */
1062         if (threadlog >= 30) EXIT("too many threads requested");
1063         if ((uint64_t)nbThreads > (totalH >> 16))
1064             EXIT("too many threads requested");
1065         if (bflog > 0 && threadlog > (bflog-10))
1066             EXIT("too many threads requested");
1067         printf("using %i threads ... \n", nbThreads);
1068 
1069         /* allocation */
1070         time_t const programTBegin = time(NULL);
1071         POOL_ctx* const pt = POOL_create((size_t)nbThreads, 1);
1072         if (!pt) EXIT("not enough memory for threads");
1073         searchCollisions_results* const MTresults = calloc (sizeof(searchCollisions_results), (size_t)nbThreads);
1074         if (!MTresults) EXIT("not enough memory");
1075         searchCollisions_parameters* const MTparams = calloc (sizeof(searchCollisions_parameters), (size_t)nbThreads);
1076         if (!MTparams) EXIT("not enough memory");
1077 
1078         /* distribute jobs */
1079         for (int tnb=0; tnb<nbThreads; tnb++) {
1080             MTparams[tnb].nbH = totalH;
1081             MTparams[tnb].mask = (uint64_t)nbThreads - 1;
1082             MTparams[tnb].sampleSize = sampleSize;
1083             MTparams[tnb].filterLog = bflog ? bflog - threadlog : 0;
1084             MTparams[tnb].hashID = hashID;
1085             MTparams[tnb].display = 0;
1086             MTparams[tnb].resultPtr = MTresults+tnb;
1087             MTparams[tnb].prngSeed = prngSeed;
1088             MTparams[tnb].maskSelector = (uint64_t)tnb;
1089             POOL_add(pt, MT_searchCollisions, MTparams + tnb);
1090         }
1091         POOL_free(pt);  /* actually joins and free */
1092 
1093         /* Gather results */
1094         uint64_t nbCollisions=0;
1095         for (int tnb=0; tnb<nbThreads; tnb++) {
1096             nbCollisions += MTresults[tnb].nbCollisions;
1097         }
1098 
1099         double const programTDelay = difftime(time(NULL), programTBegin);
1100         size_t const programBytesSelf = getProcessMemUsage(0);
1101         size_t const programBytesChildren = getProcessMemUsage(1);
1102         printf("\n\n");
1103         printf("===>   Found %llu collisions (x%.2f, %.1f expected) in %s\n",
1104                 (unsigned long long)nbCollisions,
1105                 (double)nbCollisions / targetColls,
1106                 targetColls,
1107                 displayDelay(programTDelay));
1108         if (programBytesSelf)
1109           printf("===>   MaxRSS(self) %zuMB, MaxRSS(children) %zuMB\n",
1110                  programBytesSelf>>20,
1111                  programBytesChildren>>20);
1112         printf("------------------------------------------ \n");
1113 
1114         /* Clean up */
1115         free(MTparams);
1116         free(MTresults);
1117     }
1118 
1119     return 0;
1120 }
1121