summaryrefslogtreecommitdiff
path: root/toxcore/assoc.c
diff options
context:
space:
mode:
Diffstat (limited to 'toxcore/assoc.c')
-rw-r--r--toxcore/assoc.c886
1 files changed, 886 insertions, 0 deletions
diff --git a/toxcore/assoc.c b/toxcore/assoc.c
new file mode 100644
index 00000000..fa44d0e3
--- /dev/null
+++ b/toxcore/assoc.c
@@ -0,0 +1,886 @@
1
2#include "DHT.h"
3#include "assoc.h"
4#include "ping.h"
5
6#include "LAN_discovery.h"
7
8#include <assert.h>
9#include "util.h"
10
11/*
12 * BASIC OVERVIEW:
13 *
14 * Hash: The client_id is hashed with a local hash function.
15 * Hashes are used in multiple places for searching.
16 * Bucket: The first n bits of the client_id are used to
17 * select a bucket. This speeds up sorting, but the more
18 * important reason is to enforce a spread in the space of
19 * client_ids available.
20 *
21 *
22 * Candidates:
23 *
24 * Candidates are kept in buckets of hash tables. The hash
25 * function is calculated from the client_id. Up to
26 * HASH_COLLIDE_COUNT alternative positions are tried if
27 * the inital position is already used by a different entry.
28 * The collision function is multiplicative, not additive.
29 *
30 * A new candidate can bump an existing candidate, if it is
31 * more "desirable": Seen beats Heard.
32 */
33
34/* candidates: alternative places for the same hash value */
35#define HASH_COLLIDE_COUNT 5
36
37/* bucket size shall be co-prime to this */
38#define HASH_COLLIDE_PRIME 101
39
40/* candidates: bump entries: timeout values for seen/heard to be considered of value */
41#define CANDIDATES_SEEN_TIMEOUT 1800
42#define CANDIDATES_HEARD_TIMEOUT 600
43
44/* distance/index: index size & access mask */
45#define DISTANCE_INDEX_INDEX_BITS (64 - DISTANCE_INDEX_DISTANCE_BITS)
46#define DISTANCE_INDEX_INDEX_MASK ((1 << DISTANCE_INDEX_INDEX_BITS) - 1)
47
48/* types to stay consistent */
49typedef uint16_t bucket_t;
50typedef uint32_t hash_t;
51typedef uint16_t usecnt_t;
52
53/* abbreviations ... */
54typedef Assoc_distance_relative_callback dist_rel_cb;
55typedef Assoc_distance_absolute_callback dist_abs_cb;
56
57/*
58 * Client_data wrapped with additional data
59 */
60typedef struct Client_entry {
61 hash_t hash;
62
63 /* shortcuts & rumors: timers and data */
64 uint64_t used_at;
65 uint64_t seen_at;
66 uint64_t heard_at;
67
68 uint16_t seen_family;
69 uint16_t heard_family;
70
71 IP_Port assoc_heard4;
72 IP_Port assoc_heard6;
73
74 Client_data client;
75} Client_entry;
76
77typedef struct candidates_bucket {
78 Client_entry *list; /* hashed list */
79} candidates_bucket;
80typedef struct Assoc {
81 hash_t self_hash; /* hash of self_client_id */
82 uint8_t self_client_id[CLIENT_ID_SIZE]; /* don't store entries for this */
83
84 /* association centralization: clients not in use */
85 size_t candidates_bucket_bits;
86 size_t candidates_bucket_count;
87 size_t candidates_bucket_size;
88 candidates_bucket *candidates;
89} Assoc;
90
91/*****************************************************************************/
92/* HELPER FUNCTIONS */
93/*****************************************************************************/
94
95/* the complete distance would be CLIENT_ID_SIZE long...
96 * returns DISTANCE_INDEX_DISTANCE_BITS valid bits */
97static uint64_t id_distance(Assoc *assoc, void *callback_data, uint8_t *id_ref, uint8_t *id_test)
98{
99 /* with BIG_ENDIAN, this would be a one-liner... */
100 uint64_t retval = 0;
101
102 uint8_t pos = 0, bits = DISTANCE_INDEX_DISTANCE_BITS;
103
104 while (bits > 8) {
105 uint8_t distance = abs((int8_t)id_ref[pos] ^ (int8_t)id_test[pos]);
106 retval = (retval << 8) | distance;
107 bits -= 8;
108 pos++;
109 }
110
111 return (retval << bits) | ((id_ref[pos] ^ id_test[pos]) >> (8 - bits));
112}
113
114/* qsort() callback for a sorting by id_distance() values */
115static int dist_index_comp(const void *a, const void *b)
116{
117 const uint64_t *_a = a;
118 const uint64_t *_b = b;
119
120 if (*_a < *_b)
121 return -1;
122
123 if (*_a > *_b)
124 return 1;
125
126 return 0;
127}
128
129/* get actual entry to a distance_index */
130static Client_entry *dist_index_entry(Assoc *assoc, uint64_t dist_ind)
131{
132 if ((dist_ind & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK)
133 return NULL;
134
135 size_t total = assoc->candidates_bucket_count * assoc->candidates_bucket_size;
136 uint32_t index = dist_ind & DISTANCE_INDEX_INDEX_MASK;
137
138 if (index < total) {
139 bucket_t b_id = index / assoc->candidates_bucket_size;
140 candidates_bucket *cnd_bckt = &assoc->candidates[b_id];
141 size_t b_ix = index % assoc->candidates_bucket_size;
142 Client_entry *entry = &cnd_bckt->list[b_ix];
143
144 if (entry->hash)
145 return entry;
146 }
147
148 return NULL;
149}
150
151/* get actual entry's client_id to a distance_index */
152static uint8_t *dist_index_id(Assoc *assoc, uint64_t dist_ind)
153{
154 Client_entry *entry = dist_index_entry(assoc, dist_ind);
155
156 if (entry)
157 return entry->client.client_id;
158
159 return NULL;
160}
161
162/* sorts first .. last, i.e. last is included */
163static void dist_index_bubble(Assoc *assoc, uint64_t *dist_list, size_t first, size_t last, uint8_t *id,
164 void *custom_data, Assoc_distance_relative_callback dist_rel_func)
165{
166 size_t i, k;
167
168 for (i = first; i <= last; i++) {
169 uint8_t *id1 = dist_index_id(assoc, dist_list[i]);
170
171 for (k = i + 1; k <= last; k++) {
172 uint8_t *id2 = dist_index_id(assoc, dist_list[k]);
173
174 if (id1 && id2)
175 if (dist_rel_func(assoc, custom_data, id, id1, id2) == 2) {
176 uint64_t swap = dist_list[i];
177 dist_list[i] = dist_list[k];
178 dist_list[k] = swap;
179 }
180 }
181 }
182}
183
184/* TODO: Check that there isn't a function like this elsewhere hidden.
185 * E.g. the one which creates a handshake_id isn't usable for this, it must
186 * always map the same ID to the same hash.
187 *
188 * Result is NOT MAPPED to CANDIDATES_TO_KEEP range, i.e. map before using
189 * it for list access. */
190static hash_t id_hash(Assoc *assoc, uint8_t *id)
191{
192 uint32_t i, res = 0x19a64e82;
193
194 for (i = 0; i < CLIENT_ID_SIZE; i++)
195 res = ((res << 1) ^ id[i]) + (res >> 31);
196
197 /* can't have zero as hash, a) marks an unused spot,
198 * b) collision function is multiplicative */
199 if (!(res % assoc->candidates_bucket_size))
200 res++;
201
202 return res;
203}
204
205/* up to HASH_COLLIDE_COUNT calls to different spots,
206 * result IS mapped to CANDIDATES_TO_KEEP range */
207static hash_t hash_collide(Assoc *assoc, hash_t hash)
208{
209 uint64_t hash64 = hash % assoc->candidates_bucket_size;
210 hash64 = (hash64 * HASH_COLLIDE_PRIME) % assoc->candidates_bucket_size;
211
212 hash_t retval = hash64;
213
214 /* this should never happen when CANDIDATES_TO_KEEP is prime and hash not a multiple
215 * (id_hash() checks for a multiple and returns a different hash in that case)
216 *
217 * ( 1 .. (prime - 1) is a group over multiplication and every number has its inverse
218 * in the group, so no multiplication should ever end on zero as long neither
219 * of the two factors was zero-equivalent )
220 *
221 * BUT: because the usage of the word "never" invokes Murphy's law, catch it */
222 if (!retval) {
223#ifdef DEBUG
224 fprintf(stderr, "assoc::hash_collide: hash %u, bucket size %u => %u!", hash, (uint)assoc->candidates_bucket_size,
225 retval);
226 assert(retval != 0);
227#endif
228 retval = 1;
229 }
230
231 return retval;
232}
233
234/* returns the "seen" assoc related to the ipp */
235static IPPTsPng *entry_assoc(Client_entry *cl_entry, IP_Port *ipp)
236{
237 if (!cl_entry)
238 return NULL;
239
240 if (ipp->ip.family == AF_INET)
241 return &cl_entry->client.assoc4;
242
243 if (ipp->ip.family == AF_INET6)
244 return &cl_entry->client.assoc6;
245
246 return NULL;
247}
248
249/* returns the "heard" assoc related to the ipp */
250static IP_Port *entry_heard_get(Client_entry *entry, IP_Port *ipp)
251{
252 if (ipp->ip.family == AF_INET)
253 return &entry->assoc_heard4;
254 else if (ipp->ip.family == AF_INET6)
255 return &entry->assoc_heard6;
256 else
257 return NULL;
258}
259
260/* store a "heard" entry
261 * overwrites empty entry, does NOT overwrite non-LAN ip with
262 * LAN ip
263 *
264 * returns 1 if the entry did change */
265static int entry_heard_store(Client_entry *entry, IPPTs *ippts)
266{
267 if (!entry || !ippts)
268 return 0;
269
270 if (!ipport_isset(&ippts->ip_port))
271 return 0;
272
273 IP_Port *heard, *ipp = &ippts->ip_port;
274
275 if (ipp->ip.family == AF_INET)
276 heard = &entry->assoc_heard4;
277 else if (ipp->ip.family == AF_INET6)
278 heard = &entry->assoc_heard6;
279 else
280 return 0;
281
282 if (ipport_equal(ipp, heard))
283 return 0;
284
285 if (!ipport_isset(heard)) {
286 *heard = *ipp;
287 entry->heard_at = ippts->timestamp;
288 entry->heard_family = ipp->ip.family;
289 return 1;
290 }
291
292 /* don't destroy a good address with a crappy one
293 * (unless we're very timed out) */
294 uint8_t LAN_ipp = LAN_ip(ipp->ip) == 0;
295 uint8_t LAN_entry = LAN_ip(heard->ip) == 0;
296
297 if (LAN_ipp && !LAN_entry && !is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT))
298 return 0;
299
300 *heard = *ipp;
301 entry->heard_at = ippts->timestamp;
302 entry->heard_family = ipp->ip.family;
303
304 return 1;
305}
306
307/* maps Assoc callback signature to id_closest() */
308static int assoc_id_closest(Assoc *assoc, void *callback_data, uint8_t *client_id, uint8_t *client_id1,
309 uint8_t *client_id2)
310{
311 return id_closest(client_id, client_id1, client_id2);
312}
313
314static bucket_t id_bucket(uint8_t *id, uint8_t bits)
315{
316 /* return the first "bits" bits of id */
317 bucket_t retval = 0;
318
319 uint8_t pos = 0;
320
321 while (bits > 8) {
322 retval = (retval << 8) | id[pos++];
323 bits -= 8;
324 }
325
326 return (retval << bits) | (id[pos] >> (8 - bits));
327}
328
329/*****************************************************************************/
330/* CANDIDATES FUNCTIONS */
331/*****************************************************************************/
332
333
334static bucket_t candidates_id_bucket(Assoc *assoc, uint8_t *id)
335{
336 return id_bucket(id, assoc->candidates_bucket_bits);
337}
338
339static uint8_t candidates_search(Assoc *assoc, uint8_t *id, hash_t hash, Client_entry **entryptr)
340{
341 bucket_t bucket = candidates_id_bucket(assoc, id);
342 candidates_bucket *cnd_bckt = &assoc->candidates[bucket];
343 size_t coll, pos = hash % assoc->candidates_bucket_size;
344
345 for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) {
346 Client_entry *entry = &cnd_bckt->list[pos];
347
348 if (entry->hash == hash)
349 if (id_equal(entry->client.client_id, id)) {
350 *entryptr = entry;
351 return 1;
352 }
353 }
354
355 *entryptr = NULL;
356 return 0;
357}
358
359static void candidates_update_assoc(Assoc *assoc, Client_entry *entry, uint8_t used, IPPTs *ippts_send,
360 IP_Port *ipp_recv)
361{
362 if (!assoc || !entry || !ippts_send)
363 return;
364
365 IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port);
366
367 if (!ipptsp)
368 return;
369
370 if (used)
371 entry->used_at = unix_time();
372
373 /* do NOT do anything related to wanted, that's handled outside,
374 * just update the assoc (in the most sensible way)
375 */
376 if (ipp_recv) {
377 ipptsp->ip_port = ippts_send->ip_port;
378 ipptsp->timestamp = ippts_send->timestamp;
379 ipptsp->ret_ip_port = *ipp_recv;
380 ipptsp->ret_timestamp = unix_time();
381
382 entry->seen_at = unix_time();
383 entry->seen_family = ippts_send->ip_port.ip.family;
384
385 return;
386 }
387
388 entry_heard_store(entry, ippts_send);
389}
390
391static uint8_t candidates_create_internal(Assoc *assoc, hash_t hash, uint8_t *id, uint8_t seen,
392 uint8_t used, bucket_t *bucketptr, size_t *posptr)
393{
394 if (!assoc || !id || !bucketptr || !posptr)
395 return 0;
396
397 bucket_t bucket = candidates_id_bucket(assoc, id);
398 candidates_bucket *cnd_bckt = &assoc->candidates[bucket];
399
400 size_t coll, pos = hash % assoc->candidates_bucket_size, check;
401 size_t pos_check[6];
402
403 memset(pos_check, 0, sizeof(pos_check));
404
405 for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) {
406 Client_entry *entry = &cnd_bckt->list[pos];
407
408 /* unset */
409 if (!entry->hash) {
410 *bucketptr = bucket;
411 *posptr = pos;
412
413 return 1;
414 }
415
416 /* 0. bad
417 * 1. seen bad, heard good
418 * 2. seen good
419 * 3. used */
420 if (!is_timeout(entry->used_at, BAD_NODE_TIMEOUT))
421 check = 3;
422
423 if (!is_timeout(entry->seen_at, CANDIDATES_SEEN_TIMEOUT))
424 check = 2;
425 else if (!is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT))
426 check = 1;
427 else
428 check = 0;
429
430 if (!pos_check[check])
431 pos_check[check] = pos + 1;
432 }
433
434 /* used > seen > heard > bad */
435 size_t i, pos_max = used ? 3 : (seen ? 2 : 1);
436
437 for (i = 0; i < pos_max; i++)
438 if (pos_check[i]) {
439 *bucketptr = bucket;
440 *posptr = pos_check[i] - 1;
441
442 return 1;
443 }
444
445 return 0;
446}
447
448static uint8_t candidates_create_new(Assoc *assoc, hash_t hash, uint8_t *id, uint8_t used,
449 IPPTs *ippts_send, IP_Port *ipp_recv)
450{
451 if (!assoc || !id || !ippts_send)
452 return 0;
453
454 bucket_t bucket;
455 size_t pos;
456
457 if (!candidates_create_internal(assoc, hash, id, ipp_recv != NULL, used, &bucket, &pos))
458 return 0;
459
460 candidates_bucket *cnd_bckt = &assoc->candidates[bucket];
461 Client_entry *entry = &cnd_bckt->list[pos];
462 memset(entry, 0, sizeof(*entry));
463 IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port);
464
465 if (!ipptsp)
466 return 0;
467
468 entry->hash = hash;
469 id_copy(entry->client.client_id, id);
470
471 if (used)
472 entry->used_at = unix_time();
473
474 if (ipp_recv && !ipport_isset(ipp_recv))
475 ipp_recv = NULL;
476
477 if (ipp_recv) {
478 entry->seen_at = unix_time();
479 entry->seen_family = ippts_send->ip_port.ip.family;
480
481 ipptsp->ip_port = ippts_send->ip_port;
482 ipptsp->timestamp = ippts_send->timestamp;
483 ipptsp->ret_ip_port = *ipp_recv;
484 ipptsp->ret_timestamp = unix_time();
485 } else {
486 IP_Port *heard = entry_heard_get(entry, &ippts_send->ip_port);
487
488 if (heard) {
489 entry->heard_at = ippts_send->timestamp;
490 entry->heard_family = ippts_send->ip_port.ip.family;
491
492 *heard = ippts_send->ip_port;
493 }
494 }
495
496 return 1;
497}
498
499/*****************************************************************************/
500
501static void client_id_self_update(Assoc *assoc)
502{
503 if (assoc->self_hash || !assoc->self_client_id)
504 return;
505
506 if (!assoc->self_hash) {
507 size_t i, sum = 0;
508
509 for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++)
510 sum |= assoc->self_client_id[i];
511
512 if (!sum)
513 return;
514
515 assoc->self_hash = id_hash(assoc, assoc->self_client_id);
516 }
517
518#ifdef LOGGING
519 loglog("assoc: id is now set, purging cache of self-references...\n");
520#endif
521
522 /* if we already added some (or loaded some) entries,
523 * look and remove if we find a match
524 */
525 bucket_t b_id = candidates_id_bucket(assoc, assoc->self_client_id);
526 candidates_bucket *cnd_bckt = &assoc->candidates[b_id];
527 size_t i, pos = assoc->self_hash % assoc->candidates_bucket_size;
528
529 for (i = 0; i < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos), i++) {
530 Client_entry *entry = &cnd_bckt->list[pos];
531
532 if (entry->hash == assoc->self_hash)
533 if (id_equal(entry->client.client_id, assoc->self_client_id))
534 entry->hash = 0;
535 }
536}
537
538/*****************************************************************************/
539/* TRIGGER FUNCTIONS */
540/*****************************************************************************/
541
542/* Central entry point for new associations: add a new candidate to the cache
543 * seen should be 0 (zero), if the candidate was announced by someone else,
544 * seen should be 1 (one), if there is confirmed connectivity (a definite response)
545 */
546uint8_t Assoc_add_entry(Assoc *assoc, uint8_t *id, IPPTs *ippts_send, IP_Port *ipp_recv, uint8_t used)
547{
548 if (!assoc || !id || !ippts_send)
549 return 0;
550
551 if (!assoc->self_hash) {
552 client_id_self_update(assoc);
553
554 if (!assoc->self_hash)
555 return 0;
556 }
557
558 if (!ipport_isset(&ippts_send->ip_port))
559 return 0;
560
561 if (ipp_recv && !ipport_isset(ipp_recv))
562 ipp_recv = NULL;
563
564 hash_t hash = id_hash(assoc, id);
565
566 if (hash == assoc->self_hash)
567 if (id_equal(id, assoc->self_client_id))
568 return 0;
569
570 /* if it's new:
571 * callback, if there's desire, add to clients, else to candidates
572 *
573 * if it's "old":
574 * if it's client: refresh
575 * if it's candidate:
576 * if !ipp_recv, refresh
577 * if ipp_recv: callback, if there's desire, move to candidates
578 */
579 Client_entry *cnd_entry;
580
581 if (!candidates_search(assoc, id, hash, &cnd_entry)) {
582 if (candidates_create_new(assoc, hash, id, used, ippts_send, ipp_recv))
583 return 1;
584 else
585 return 0;
586 } else {
587 candidates_update_assoc(assoc, cnd_entry, used, ippts_send, ipp_recv);
588 return 2;
589 }
590}
591
592/*****************************************************************************/
593/* MAIN USE */
594/*****************************************************************************/
595
596uint8_t Assoc_get_close_entries(Assoc *assoc, Assoc_close_entries *state)
597{
598 if (!assoc || !state || !state->wanted_id || !state->result)
599 return 0;
600
601 if (!assoc->self_hash) {
602 client_id_self_update(assoc);
603
604 if (!assoc->self_hash)
605 return 0;
606 }
607
608 if (!state->distance_relative_func)
609 state->distance_relative_func = assoc_id_closest;
610
611 if (!state->distance_absolute_func)
612 state->distance_absolute_func = id_distance;
613
614 size_t dist_list_len = assoc->candidates_bucket_count * assoc->candidates_bucket_size;
615 uint64_t dist_list[dist_list_len];
616 memset(dist_list, ~0, dist_list_len * sizeof(dist_list[0]));
617 bucket_t b;
618 size_t i;
619
620 for (b = 0; b < assoc->candidates_bucket_count; b++) {
621 candidates_bucket *cnd_bckt = &assoc->candidates[b];
622
623 for (i = 0; i < assoc->candidates_bucket_size; i++) {
624 Client_entry *entry = &cnd_bckt->list[i];
625
626 if (entry->hash) {
627 uint64_t dist = state->distance_absolute_func(assoc, state->custom_data, state->wanted_id, entry->client.client_id);
628 uint32_t index = b * assoc->candidates_bucket_size + i;
629 dist_list[index] = (dist << DISTANCE_INDEX_INDEX_BITS) | index;
630 }
631 }
632 }
633
634 qsort(dist_list, dist_list_len, sizeof(dist_list[0]), dist_index_comp);
635
636 /* ok, ok, it's not *perfectly* sorted, because we used an absolute distance
637 * go over the result and see if we need to "smoothen things out"
638 * because those should be only very few and short streaks, the worst regularly
639 * used sorting function aka bubble sort is used */
640 uint64_t dist_prev = ~0;
641 size_t ind_prev = ~0, ind_curr;
642 size_t len = 1;
643
644 for (ind_curr = 0; ind_curr < dist_list_len; ind_curr++) {
645 /* sorted increasingly, so an invalid entry marks the end */
646 if ((dist_list[ind_curr] & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK)
647 break;
648
649 uint64_t dist_curr = dist_list[ind_curr] >> DISTANCE_INDEX_INDEX_BITS;
650
651 if (dist_prev == dist_curr)
652 len++;
653 else {
654 if (len > 1)
655 dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data,
656 state->distance_relative_func);
657
658 dist_prev = dist_curr;
659 ind_prev = ind_curr;
660 len = 1;
661 }
662 }
663
664 if (len > 1)
665 dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data,
666 state->distance_relative_func);
667
668 /* ok, now dist_list is a strictly ascending sorted list of nodes
669 * a) extract CLOSE_QUOTA_USED clients, not timed out
670 * b) extract (1 - QUOTA) (better!) clients & candidates, not timed out
671 * c) save candidates which would be better, if contact can be established */
672 size_t client_quota_good = 0, pos = 0;
673 size_t client_quota_max = state->count_good;
674
675 ssize_t taken_last = - 1;
676
677 for (i = 0; (i < dist_list_len) && (pos < state->count); i++) {
678 Client_entry *entry = dist_index_entry(assoc, dist_list[i]);
679
680 if (entry && entry->hash) {
681 if (client_quota_good >= client_quota_max) {
682 state->result[pos++] = &entry->client;
683 taken_last = i;
684 } else if (!is_timeout(entry->seen_at, BAD_NODE_TIMEOUT)) {
685 state->result[pos++] = &entry->client;
686 client_quota_good++;
687 taken_last = i;
688 }
689 }
690 }
691
692 /* if we had not enough valid entries the list might still not be filled.
693 *
694 * start again from last taken client, but leave out any requirement
695 */
696 if (pos < state->count) {
697 for (i = taken_last + 1; (i < dist_list_len) && (pos < state->count); i++) {
698 Client_entry *entry = dist_index_entry(assoc, dist_list[i]);
699
700 if (entry && entry->hash)
701 state->result[pos++] = &entry->client;
702 }
703 }
704
705 return pos;
706}
707
708/*****************************************************************************/
709/* GLOBAL STRUCTURE FUNCTIONS */
710/*****************************************************************************/
711
712static uint8_t odd_min9_is_prime(size_t value)
713{
714 size_t i = 3;
715
716 while (i * i <= value) {
717 if (!(value % i))
718 return 0;
719
720 i += 2;
721 }
722
723 return 1;
724}
725
726static size_t prime_upto_min9(size_t limit)
727{
728 /* primes besides 2 are odd */
729 if (!(limit % 2))
730 limit--;
731
732 while (!odd_min9_is_prime(limit))
733 limit -= 2;
734
735 return limit;
736}
737
738/* create */
739Assoc *new_Assoc(size_t bits, size_t entries, uint8_t *public_id)
740{
741 if (!public_id)
742 return NULL;
743
744 Assoc *assoc = calloc(1, sizeof(*assoc));
745
746 if (!assoc)
747 return NULL;
748
749 /*
750 * bits must be in [ 2 .. 15 ]
751 * entries must be a prime
752 */
753 if (bits < 2)
754 bits = 2;
755 else if (bits > 15)
756 bits = 15;
757
758 assoc->candidates_bucket_bits = bits;
759 assoc->candidates_bucket_count = 1U << bits;
760
761 if (entries <= 8) {
762 if (entries <= 4)
763 entries = 3;
764 else if (!(entries % 2)) /* 6, 8 => 5, 7 */
765 entries--;
766 } else if (entries > ((1 << 17) - 1)) /* 130k+ */
767 entries = (1 << 17) - 1;
768 else {
769 /* 9+: test and find a prime less or equal */
770 size_t entries_test = prime_upto_min9(entries);
771
772 if (entries_test == HASH_COLLIDE_PRIME) /* disallowed */
773 entries_test = prime_upto_min9(entries_test - 1);
774
775 if (entries_test != entries) {
776#ifdef LOGGING
777 sprintf(logbuffer, "new_Assoc(): trimmed %i to %i.\n", (int)entries, (int)entries_test);
778 loglog(logbuffer);
779#endif
780 entries = (size_t)entries_test;
781 }
782 }
783
784 assoc->candidates_bucket_size = entries;
785
786 /* allocation: preferably few blobs */
787 size_t bckt, cix;
788 Client_entry *clients = malloc(sizeof(*clients) * assoc->candidates_bucket_count * assoc->candidates_bucket_size);
789 candidates_bucket *lists = malloc(sizeof(*lists) * assoc->candidates_bucket_count);
790
791 for (bckt = 0; bckt < assoc->candidates_bucket_count; bckt++) {
792 candidates_bucket *list = &lists[bckt];
793
794 list->list = &clients[bckt * assoc->candidates_bucket_size];
795
796 for (cix = 0; cix < assoc->candidates_bucket_size; cix++)
797 list->list[cix].hash = 0;
798 }
799
800 assoc->candidates = lists;
801
802 id_copy(assoc->self_client_id, public_id);
803 client_id_self_update(assoc);
804
805 return assoc;
806}
807
808Assoc *new_Assoc_default(uint8_t *public_id)
809{
810 /* original 8, 251 averages to ~32k entries... probably the whole DHT :D
811 * 320 entries is fine, hopefully */
812 return new_Assoc(6, 5, public_id);
813}
814
815/* own client_id, assocs for this have to be ignored */
816void Assoc_self_client_id_changed(Assoc *assoc, uint8_t *id)
817{
818 if (assoc && id) {
819 assoc->self_hash = 0;
820 id_copy(assoc->self_client_id, id);
821 client_id_self_update(assoc);
822 }
823}
824
825/* destroy */
826void kill_Assoc(Assoc *assoc)
827{
828 if (assoc) {
829 free(assoc->candidates->list);
830 free(assoc->candidates);
831 free(assoc);
832 }
833}
834
835#ifdef LOGGING
836
837static char buffer[CLIENT_ID_SIZE * 2 + 1];
838static char *idpart2str(uint8_t *id, size_t len)
839{
840 if (len > CLIENT_ID_SIZE)
841 len = CLIENT_ID_SIZE;
842
843 size_t i;
844
845 for (i = 0; i < len; i++)
846 sprintf(buffer + i * 2, "%02hhx", id[i]);
847
848 buffer[len * 2] = 0;
849 return buffer;
850}
851
852void Assoc_status(Assoc *assoc)
853{
854 if (!assoc) {
855 loglog("Assoc status: no assoc\n");
856 return;
857 }
858
859 size_t bid, cid, total = 0;
860
861 for (bid = 0; bid < assoc->candidates_bucket_count; bid++) {
862 candidates_bucket *bucket = &assoc->candidates[bid];
863
864 for (cid = 0; cid < assoc->candidates_bucket_size; cid++) {
865 Client_entry *entry = &bucket->list[cid];
866
867 if (entry->hash) {
868 sprintf(logbuffer, "[%3i:%3i] %x => [%s...] %i, %i, %i\n",
869 (int)bid, (int)cid, entry->hash, idpart2str(entry->client.client_id, 8),
870 entry->used_at ? (int)(unix_time() - entry->used_at) : 0,
871 entry->seen_at ? (int)(unix_time() - entry->seen_at) : 0,
872 entry->heard_at ? (int)(unix_time() - entry->heard_at) : 0);
873 loglog(logbuffer);
874 total++;
875 }
876 }
877 }
878
879 if (total) {
880 sprintf(logbuffer, "Total: %i entries, table usage %i%%.\n", (int)total,
881 (int)(total * 100 / (assoc->candidates_bucket_count * assoc->candidates_bucket_size)));
882 loglog(logbuffer);
883 }
884}
885
886#endif