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