summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-25 13:00:17 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-25 13:00:17 +0100
commit632960a33eb8d23925b2bbef24d5065de7280aef (patch)
tree37805cf0e22bb1c56a4b5eac22727004b0a0d245
parent17dbb4ccbff1259da86ce6a602cbcd8f49ecfbf5 (diff)
Clean up peer re-distribution between single bucket vectors and bucket lists, add tests
-rw-r--r--ot_vector.c8
-rw-r--r--tests/t_vector.c104
2 files changed, 107 insertions, 5 deletions
diff --git a/ot_vector.c b/ot_vector.c
index c4cd171..7f18246 100644
--- a/ot_vector.c
+++ b/ot_vector.c
@@ -8,7 +8,6 @@
8#include <stdint.h> 8#include <stdint.h>
9#include <stdlib.h> 9#include <stdlib.h>
10#include <string.h> 10#include <string.h>
11#include <strings.h>
12 11
13/* Opentracker */ 12/* Opentracker */
14#include "trackerlogic.h" 13#include "trackerlogic.h"
@@ -174,7 +173,7 @@ void vector_clean_list(ot_vector *vector, size_t num_buckets) {
174} 173}
175 174
176void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { 175void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) {
177 size_t tmp, bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; 176 size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1;
178 ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; 177 ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers;
179 int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; 178 int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4;
180 179
@@ -205,11 +204,10 @@ void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) {
205 bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector)); 204 bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector));
206 if (!bucket_list_new) 205 if (!bucket_list_new)
207 return; 206 return;
208 bzero(bucket_list_new, num_buckets_new * sizeof(ot_vector)); 207 memset(bucket_list_new, 0, num_buckets_new * sizeof(ot_vector));
209 208
210 tmp = peer_list->peer_count / num_buckets_new;
211 bucket_size_new = OT_VECTOR_MIN_MEMBERS; 209 bucket_size_new = OT_VECTOR_MIN_MEMBERS;
212 while (bucket_size_new < tmp) 210 while (bucket_size_new * num_buckets_new < peer_list->peer_count)
213 bucket_size_new *= OT_VECTOR_GROW_RATIO; 211 bucket_size_new *= OT_VECTOR_GROW_RATIO;
214 212
215 /* preallocate vectors to hold all peers */ 213 /* preallocate vectors to hold all peers */
diff --git a/tests/t_vector.c b/tests/t_vector.c
index fee336b..a8186b2 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -4,10 +4,12 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <stdint.h>
7#include <stdlib.h> 8#include <stdlib.h>
8#include <string.h> 9#include <string.h>
9 10
10/* Opentracker */ 11/* Opentracker */
12#include "ot_vector.h"
11#include "trackerlogic.h" 13#include "trackerlogic.h"
12#include "test.h" 14#include "test.h"
13 15
@@ -149,6 +151,105 @@ static void case_insert_compare_prefix(void) {
149 free(vector.data); 151 free(vector.data);
150} 152}
151 153
154/* ------------------------------------------------- bucket redistribution */
155
156/* A flat peer list of count distinct v4 peers. The low byte of the index goes
157 into the first address byte, so the peers are distinct but arrive in no
158 particular order: a list generated in ascending order would come back
159 sorted whether or not vector_redistribute_buckets sorted it. */
160static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) {
161 size_t i;
162
163 memset(peer_list, 0, sizeof(*peer_list));
164 peer_list->peers.data = malloc(count * OT_PEER_SIZE4);
165 peer_list->peers.size = count;
166 peer_list->peers.space = count;
167 peer_list->peer_count = count;
168
169 for (i = 0; i < count; ++i) {
170 uint8_t *peer = (uint8_t *)peer_list->peers.data + i * OT_PEER_SIZE4;
171
172 memset(peer, 0, OT_PEER_SIZE4);
173 peer[0] = (uint8_t)i;
174 peer[1] = (uint8_t)(i >> 8);
175 peer[2] = (uint8_t)(i >> 16);
176 peer[3] = 1;
177 }
178}
179
180static void case_redistribute_holds_every_peer(void) {
181 const size_t count = 513;
182 ot_peerlist peer_list;
183 size_t bucket, total_size = 0, empty = 0, largest = 0;
184 ot_vector *buckets;
185
186 test_case("a peer list past the threshold is split into buckets");
187 build_flat_peerlist(&peer_list, count);
188 CHECK(peer_list.peers.data != NULL);
189 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
190 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1);
191 CHECK_INT(peer_list.peers.size, 16);
192
193 buckets = peer_list.peers.data;
194 for (bucket = 0; bucket < peer_list.peers.size; ++bucket) {
195 total_size += buckets[bucket].size;
196 if (!buckets[bucket].size)
197 ++empty;
198 if (buckets[bucket].size > largest)
199 largest = buckets[bucket].size;
200 }
201
202 test_case("no peer is lost on the way");
203 CHECK_INT(total_size, count);
204
205 /* The point of the exercise is that a lookup only searches one bucket, so
206 a hash that piled everything into one would defeat it. The input and the
207 hash are both fixed, so this is not a matter of luck. */
208 test_case("and they are spread over the buckets rather than piled up");
209 CHECK_INT(empty, 0);
210 CHECK(largest < count / 2);
211
212 vector_clean_list(peer_list.peers.data, peer_list.peers.size);
213}
214
215static void case_redistribute_sorts_each_bucket(void) {
216 const size_t count = 1000;
217 ot_peerlist peer_list;
218 size_t bucket, i, out_of_order = 0;
219 ot_vector *buckets;
220
221 test_case("every bucket comes back sorted, so bsearch can be used on it");
222 build_flat_peerlist(&peer_list, count);
223 CHECK(peer_list.peers.data != NULL);
224 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
225 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1);
226
227 buckets = peer_list.peers.data;
228 for (bucket = 0; bucket < peer_list.peers.size; ++bucket)
229 for (i = 1; i < buckets[bucket].size; ++i) {
230 const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4;
231 const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4;
232
233 if (memcmp(prev, next, OT_PEER_COMPARE_SIZE4) >= 0)
234 ++out_of_order;
235 }
236 CHECK_INT(out_of_order, 0);
237
238 vector_clean_list(peer_list.peers.data, peer_list.peers.size);
239}
240
241static void case_redistribute_below_threshold(void) {
242 ot_peerlist peer_list;
243
244 test_case("a small peer list is left flat");
245 build_flat_peerlist(&peer_list, 100);
246 CHECK(peer_list.peers.data != NULL);
247 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
248 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 0);
249 CHECK_INT(peer_list.peers.size, 100);
250 free(peer_list.peers.data);
251}
252
152void test_vector(void) { 253void test_vector(void) {
153 case_search_finds_member(); 254 case_search_finds_member();
154 case_search_finds_first_and_last(); 255 case_search_finds_first_and_last();
@@ -158,4 +259,7 @@ void test_vector(void) {
158 case_insert_keeps_order(); 259 case_insert_keeps_order();
159 case_insert_finds_existing(); 260 case_insert_finds_existing();
160 case_insert_compare_prefix(); 261 case_insert_compare_prefix();
262 case_redistribute_holds_every_peer();
263 case_redistribute_sorts_each_bucket();
264 case_redistribute_below_threshold();
161} 265}