summaryrefslogtreecommitdiff
path: root/src/ot_vector.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_vector.c')
-rw-r--r--src/ot_vector.c287
1 files changed, 287 insertions, 0 deletions
diff --git a/src/ot_vector.c b/src/ot_vector.c
new file mode 100644
index 0000000..ab0566c
--- /dev/null
+++ b/src/ot_vector.c
@@ -0,0 +1,287 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stddef.h>
6#include <stdint.h>
7#include <stdlib.h>
8#include <string.h>
9
10/* Opentracker */
11#include "trackerlogic.h"
12
13static int vector_compare_peer6(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE6); }
14static int vector_compare_peer4(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE4); }
15
16/* This function gives us a binary search that returns a pointer, even if
17 no exact match is found. In that case it sets exactmatch 0 and gives
18 calling functions the chance to insert data
19*/
20void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch) {
21 size_t interval = member_count;
22
23 while (interval) {
24 uint8_t *lookat = ((uint8_t *)base) + member_size * (interval / 2);
25 int cmp = memcmp(lookat, key, compare_size);
26 if (cmp == 0) {
27 base = lookat;
28 break;
29 }
30 if (cmp < 0) {
31 base = lookat + member_size;
32 interval--;
33 }
34 interval /= 2;
35 }
36
37 *exactmatch = interval;
38 return (void *)base;
39}
40
41static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t bucket_count) {
42 unsigned int hash = 5381;
43 uint8_t *p = (uint8_t *)peer;
44 while (compare_size--)
45 hash += (hash << 5) + *(p++);
46 return hash % bucket_count;
47}
48
49/* This is the generic insert operation for our vector type.
50 It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with
51 those of objects in vector. Our special "vector_binary_search" function does that and either returns the match or a
52 pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it,
53 if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert
54 took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector.
55*/
56void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) {
57 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch);
58 size_t match_offset, bytes_after;
59
60 if (*exactmatch)
61 return match;
62
63 /* Hold the insertion point as an offset rather than a pointer: it survives
64 the realloc below, and an empty vector has no data pointer to offset from
65 in the first place */
66 match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0;
67 bytes_after = member_size * vector->size - match_offset;
68
69 if (vector->size + 1 > vector->space) {
70 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
71 uint8_t *new_data = realloc(vector->data, new_space * member_size);
72
73 if (!new_data)
74 return NULL;
75
76 vector->data = new_data;
77 vector->space = new_space;
78 }
79
80 match = (uint8_t *)vector->data + match_offset;
81 memmove(match + member_size, match, bytes_after);
82
83 vector->size++;
84 return match;
85}
86
87ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) {
88 ot_peer *match;
89 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
90 size_t match_offset, bytes_after;
91
92 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
93 if (vector->space < vector->size)
94 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
95 match = vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, exactmatch);
96
97 if (*exactmatch)
98 return match;
99
100 /* Hold the insertion point as an offset rather than a pointer: it survives
101 the realloc below, and an empty vector has no data pointer to offset from
102 in the first place. bytes_after is what has to move up by peer_size to
103 make room for the new peer */
104 match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0;
105 bytes_after = vector->size * peer_size - match_offset;
106
107 if (vector->size + 1 > vector->space) {
108 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
109 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
110
111 if (!new_data)
112 return NULL;
113
114 vector->data = new_data;
115 vector->space = new_space;
116 }
117
118 match = (ot_peer *)vector->data + match_offset;
119 memmove(match + peer_size, match, bytes_after);
120
121 vector->size++;
122 return match;
123}
124
125/* This is the non-generic delete from vector-operation specialized for peers in pools.
126 It returns 0 if no peer was found (and thus not removed)
127 1 if a non-seeding peer was removed
128 2 if a seeding peer was removed
129*/
130int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size) {
131 int exactmatch, was_seeder;
132 ot_peer *match, *end;
133 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
134
135 if (!vector->size)
136 return 0;
137
138 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
139 if (vector->space < vector->size)
140 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
141
142 end = ((ot_peer *)vector->data) + peer_size * vector->size;
143 match = (ot_peer *)vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, &exactmatch);
144 if (!exactmatch)
145 return 0;
146
147 was_seeder = (OT_PEERFLAG_D(match, peer_size) & PEER_FLAG_SEEDING) ? 2 : 1;
148 memmove(match, match + peer_size, end - match - peer_size);
149
150 vector->size--;
151 vector_fixup_peers(vector, peer_size);
152 return was_seeder;
153}
154
155void vector_remove_torrent(ot_vector *vector, ot_torrent *match) {
156 ot_torrent *end;
157
158 if (!vector->size)
159 return;
160
161 end = ((ot_torrent *)vector->data) + vector->size;
162
163 free_peerlist(match->peer_list6);
164 free_peerlist(match->peer_list4);
165
166 memmove(match, match + 1, sizeof(ot_torrent) * (end - match - 1));
167 if ((--vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) {
168 vector->space /= OT_VECTOR_SHRINK_RATIO;
169 vector->data = realloc(vector->data, vector->space * sizeof(ot_torrent));
170 }
171}
172
173void vector_clean_list(ot_vector *vector, size_t num_buckets) {
174 while (num_buckets--)
175 free(vector[num_buckets].data);
176 free(vector);
177 return;
178}
179
180void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) {
181 size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1;
182 ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers;
183 int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4;
184
185 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
186 num_buckets_old = peer_list->peers.size;
187 bucket_list_old = peer_list->peers.data;
188 }
189
190 if (peer_list->peer_count < 255)
191 num_buckets_new = 1;
192 else if (peer_list->peer_count > 8192)
193 num_buckets_new = 64;
194 else if (peer_list->peer_count >= 512 && peer_list->peer_count < 4096)
195 num_buckets_new = 16;
196 else if (peer_list->peer_count < 512 && num_buckets_old <= 16)
197 num_buckets_new = num_buckets_old;
198 else if (peer_list->peer_count < 512)
199 num_buckets_new = 1;
200 else if (peer_list->peer_count < 8192 && num_buckets_old > 1)
201 num_buckets_new = num_buckets_old;
202 else
203 num_buckets_new = 16;
204
205 if (num_buckets_new == num_buckets_old)
206 return;
207
208 /* Assume near perfect distribution */
209 bucket_list_new = calloc(1, num_buckets_new * sizeof(ot_vector));
210 if (!bucket_list_new)
211 return;
212
213 bucket_size_new = OT_VECTOR_MIN_MEMBERS;
214 while (bucket_size_new * num_buckets_new < peer_list->peer_count)
215 bucket_size_new *= OT_VECTOR_GROW_RATIO;
216
217 /* preallocate vectors to hold all peers */
218 for (bucket = 0; bucket < num_buckets_new; ++bucket) {
219 bucket_list_new[bucket].space = bucket_size_new;
220 bucket_list_new[bucket].data = malloc(bucket_size_new * peer_size);
221 if (!bucket_list_new[bucket].data) {
222 vector_clean_list(bucket_list_new, num_buckets_new);
223 return;
224 }
225 }
226
227 /* Now sort them into the correct bucket */
228 for (bucket = 0; bucket < num_buckets_old; ++bucket) {
229 ot_peer *peers_old = bucket_list_old[bucket].data;
230 size_t peer_count_old = bucket_list_old[bucket].size;
231 while (peer_count_old--) {
232 ot_vector *bucket_dest = bucket_list_new;
233 if (num_buckets_new > 1)
234 bucket_dest += vector_hash_peer(peers_old, OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size), num_buckets_new);
235 if (bucket_dest->size + 1 > bucket_dest->space) {
236 void *tmp = realloc(bucket_dest->data, peer_size * OT_VECTOR_GROW_RATIO * bucket_dest->space);
237 if (!tmp) {
238 vector_clean_list(bucket_list_new, num_buckets_new);
239 return;
240 }
241 bucket_dest->data = tmp;
242 bucket_dest->space *= OT_VECTOR_GROW_RATIO;
243 }
244 memcpy((ot_peer *)bucket_dest->data + peer_size * bucket_dest->size++, peers_old, peer_size);
245 peers_old += peer_size;
246 }
247 }
248
249 /* Now sort each bucket to later allow bsearch */
250 for (bucket = 0; bucket < num_buckets_new; ++bucket)
251 qsort(bucket_list_new[bucket].data, bucket_list_new[bucket].size, peer_size, sort_func);
252
253 /* Everything worked fine. Now link new bucket_list to peer_list */
254 if (OT_PEERLIST_HASBUCKETS(peer_list))
255 vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size);
256 else
257 free(peer_list->peers.data);
258
259 if (num_buckets_new > 1) {
260 peer_list->peers.data = bucket_list_new;
261 peer_list->peers.size = num_buckets_new;
262 peer_list->peers.space = 0; /* Magic marker for "is list of buckets" */
263 } else {
264 peer_list->peers.data = bucket_list_new->data;
265 peer_list->peers.size = bucket_list_new->size;
266 peer_list->peers.space = bucket_list_new->space;
267 free(bucket_list_new);
268 }
269}
270
271void vector_fixup_peers(ot_vector *vector, size_t peer_size) {
272 int need_fix = 0;
273
274 if (!vector->size) {
275 free(vector->data);
276 vector->data = NULL;
277 vector->space = 0;
278 return;
279 }
280
281 while ((vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) {
282 vector->space /= OT_VECTOR_SHRINK_RATIO;
283 need_fix++;
284 }
285 if (need_fix)
286 vector->data = realloc(vector->data, vector->space * peer_size);
287}