summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-05 13:52:17 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-05 13:52:17 +0100
commit47293a4f116e04a88b0797bafad0c98b4393b744 (patch)
treeb71d19aa8637382a6d3b453614a6a52a2c1b3245
parent9a3a01643fc78bf544a1e6f5ca5c7529e2026b25 (diff)
Clamp every elapsed time span at zero, using a set of helper functions
-rw-r--r--Mk/tests.mk4
-rw-r--r--src/opentracker.c2
-rw-r--r--src/ot_accesslist.c2
-rw-r--r--src/ot_clean.c2
-rw-r--r--src/ot_http.c2
-rw-r--r--src/ot_livesync.c4
-rw-r--r--src/ot_stats.c23
-rw-r--r--src/ot_udp.c4
-rw-r--r--src/trackerlogic.h24
-rw-r--r--tests/runner.c1
-rw-r--r--tests/stubs.c9
-rw-r--r--tests/t_clean.c140
-rw-r--r--tests/test.h1
13 files changed, 187 insertions, 31 deletions
diff --git a/Mk/tests.mk b/Mk/tests.mk
index 2efdd72..22ea69e 100644
--- a/Mk/tests.mk
+++ b/Mk/tests.mk
@@ -4,8 +4,8 @@ BUILDDEPS+=Mk/tests.mk
4# may reach a static function by including the source. tests/stubs.c supplies 4# may reach a static function by including the source. tests/stubs.c supplies
5# the few symbols those modules reference but the tests never reach. 5# the few symbols those modules reference but the tests never reach.
6TESTBIN=tests/runner 6TESTBIN=tests/runner
7TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_trackerlogic.c tests/t_workqueue.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c 7TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_trackerlogic.c tests/t_clean.c tests/t_workqueue.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
8TEST_UNITS=src/ot_urlencoded.c src/ot_fmt.c src/ot_event.c src/ot_batch.c src/ot_fdstate.c src/ot_buf.c src/ot_ip.c src/ot_socket.c src/ot_iovec.c src/ot_vector.c src/ot_random.c src/ot_hex.c src/ot_scan.c src/ot_accesslist.c src/ot_mutex.c src/ot_workqueue.c src/trackerlogic.c 8TEST_UNITS=src/ot_urlencoded.c src/ot_fmt.c src/ot_event.c src/ot_batch.c src/ot_fdstate.c src/ot_buf.c src/ot_ip.c src/ot_socket.c src/ot_iovec.c src/ot_vector.c src/ot_random.c src/ot_hex.c src/ot_scan.c src/ot_accesslist.c src/ot_mutex.c src/ot_workqueue.c src/ot_clean.c src/trackerlogic.c
9TEST_HEADERS=tests/test.h $(HEADERS) 9TEST_HEADERS=tests/test.h $(HEADERS)
10 10
11# A test binary is not a production binary, so it is built with the features 11# A test binary is not a production binary, so it is built with the features
diff --git a/src/opentracker.c b/src/opentracker.c
index 60a6d8c..8de3fc1 100644
--- a/src/opentracker.c
+++ b/src/opentracker.c
@@ -151,7 +151,7 @@ static void *server_mainloop(void *args) {
151 while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 151 while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
152 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 152 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
153 153
154 if (time_now_seconds() > next_timeout_check) { 154 if (time_is_past(next_timeout_check)) {
155 int cursor = 0; 155 int cursor = 0;
156 156
157 while ((sock = fdstate_next_expired(time_now_seconds(), &cursor)) != -1) 157 while ((sock = fdstate_next_expired(time_now_seconds(), &cursor)) != -1)
diff --git a/src/ot_accesslist.c b/src/ot_accesslist.c
index 86f9a7d..ea5bdba 100644
--- a/src/ot_accesslist.c
+++ b/src/ot_accesslist.c
@@ -91,7 +91,7 @@ static ot_accesslist *accesslist_make(ot_accesslist *next, size_t size) {
91 This will never delete head, because that might still be in use. */ 91 This will never delete head, because that might still be in use. */
92static void accesslist_clean(ot_accesslist *accesslist) { 92static void accesslist_clean(ot_accesslist *accesslist) {
93 while (accesslist && accesslist->next) { 93 while (accesslist && accesslist->next) {
94 if (accesslist->next->base + 5 < time_now_minutes()) 94 if (time_since_minutes(accesslist->next->base) > 5)
95 accesslist->next = accesslist_free(accesslist->next); 95 accesslist->next = accesslist_free(accesslist->next);
96 accesslist = accesslist->next; 96 accesslist = accesslist->next;
97 } 97 }
diff --git a/src/ot_clean.c b/src/ot_clean.c
index f181e83..2ebfd42 100644
--- a/src/ot_clean.c
+++ b/src/ot_clean.c
@@ -45,7 +45,7 @@ static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t pee
45 45
46int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) { 46int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) {
47 ot_vector *peer_vector = &peer_list->peers; 47 ot_vector *peer_vector = &peer_list->peers;
48 time_t timedout = (time_t)(time_now_minutes() - peer_list->base); 48 ot_time timedout = time_since_minutes(peer_list->base);
49 size_t num_buckets = 1, removed_seeders = 0; 49 size_t num_buckets = 1, removed_seeders = 0;
50 50
51 /* No need to clean empty torrent */ 51 /* No need to clean empty torrent */
diff --git a/src/ot_http.c b/src/ot_http.c
index 96b8f4e..533a876 100644
--- a/src/ot_http.c
+++ b/src/ot_http.c
@@ -414,7 +414,7 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
414 pthread_mutex_unlock(&g_modest_fullscrape_mutex); 414 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
415 HTTPERROR_500; 415 HTTPERROR_500;
416 } 416 }
417 if (exactmatch && (this_peer.last_fullscrape - new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) { 417 if (exactmatch && time_since_seconds(new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) {
418 pthread_mutex_unlock(&g_modest_fullscrape_mutex); 418 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
419 HTTPERROR_402_NOTMODEST; 419 HTTPERROR_402_NOTMODEST;
420 } 420 }
diff --git a/src/ot_livesync.c b/src/ot_livesync.c
index 85577f7..7d7208e 100644
--- a/src/ot_livesync.c
+++ b/src/ot_livesync.c
@@ -178,13 +178,13 @@ void livesync_ticker(void) {
178 178
179 /* livesync_issue_peersync sets g_next_packet_time */ 179 /* livesync_issue_peersync sets g_next_packet_time */
180 pthread_mutex_lock(&g_outbuf_mutex); 180 pthread_mutex_lock(&g_outbuf_mutex);
181 if (time_now_seconds() > g_v6_buf.next_packet_time && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t)) 181 if (time_is_past(g_v6_buf.next_packet_time) && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t))
182 livesync_issue_peersync(&g_v6_buf); 182 livesync_issue_peersync(&g_v6_buf);
183 else 183 else
184 pthread_mutex_unlock(&g_outbuf_mutex); 184 pthread_mutex_unlock(&g_outbuf_mutex);
185 185
186 pthread_mutex_lock(&g_outbuf_mutex); 186 pthread_mutex_lock(&g_outbuf_mutex);
187 if (time_now_seconds() > g_v4_buf.next_packet_time && g_v4_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t)) 187 if (time_is_past(g_v4_buf.next_packet_time) && g_v4_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t))
188 livesync_issue_peersync(&g_v4_buf); 188 livesync_issue_peersync(&g_v4_buf);
189 else 189 else
190 pthread_mutex_unlock(&g_outbuf_mutex); 190 pthread_mutex_unlock(&g_outbuf_mutex);
diff --git a/src/ot_stats.c b/src/ot_stats.c
index 2d6335b..7b5b1b9 100644
--- a/src/ot_stats.c
+++ b/src/ot_stats.c
@@ -373,7 +373,7 @@ void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
373static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } 373static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); }
374 374
375static void stats_connections_mrtg(char **reply, const char *reply_end) { 375static void stats_connections_mrtg(char **reply, const char *reply_end) {
376 ot_time t = time(NULL) - ot_start_time; 376 ot_time t = time_since_seconds(ot_start_time);
377 unsigned long long total[OT_COUNTER_COUNT]; 377 unsigned long long total[OT_COUNTER_COUNT];
378 378
379 counters_total(total); 379 counters_total(total);
@@ -386,7 +386,7 @@ static void stats_connections_mrtg(char **reply, const char *reply_end) {
386} 386}
387 387
388static void stats_udpconnections_mrtg(char **reply, const char *reply_end) { 388static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
389 ot_time t = time(NULL) - ot_start_time; 389 ot_time t = time_since_seconds(ot_start_time);
390 unsigned long long total[OT_COUNTER_COUNT]; 390 unsigned long long total[OT_COUNTER_COUNT];
391 391
392 counters_total(total); 392 counters_total(total);
@@ -398,7 +398,7 @@ static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
398} 398}
399 399
400static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) { 400static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
401 time_t t = time(NULL) - ot_start_time; 401 time_t t = time_since_seconds(ot_start_time);
402 unsigned long long total[OT_COUNTER_COUNT]; 402 unsigned long long total[OT_COUNTER_COUNT];
403 403
404 counters_total(total); 404 counters_total(total);
@@ -409,7 +409,7 @@ static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
409} 409}
410 410
411static void stats_scrape_mrtg(char **reply, const char *reply_end) { 411static void stats_scrape_mrtg(char **reply, const char *reply_end) {
412 time_t t = time(NULL) - ot_start_time; 412 time_t t = time_since_seconds(ot_start_time);
413 unsigned long long total[OT_COUNTER_COUNT]; 413 unsigned long long total[OT_COUNTER_COUNT];
414 414
415 counters_total(total); 415 counters_total(total);
@@ -420,7 +420,7 @@ static void stats_scrape_mrtg(char **reply, const char *reply_end) {
420} 420}
421 421
422static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) { 422static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) {
423 ot_time t = time(NULL) - ot_start_time; 423 ot_time t = time_since_seconds(ot_start_time);
424 unsigned long long total[OT_COUNTER_COUNT]; 424 unsigned long long total[OT_COUNTER_COUNT];
425 425
426 counters_total(total); 426 counters_total(total);
@@ -468,7 +468,7 @@ static void stats_return_renew_bucket(char **reply, const char *reply_end) {
468} 468}
469 469
470static void stats_return_sync_mrtg(char **reply, const char *reply_end) { 470static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
471 ot_time t = time(NULL) - ot_start_time; 471 ot_time t = time_since_seconds(ot_start_time);
472 unsigned long long total[OT_COUNTER_COUNT]; 472 unsigned long long total[OT_COUNTER_COUNT];
473 473
474 counters_total(total); 474 counters_total(total);
@@ -479,7 +479,7 @@ static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
479} 479}
480 480
481static void stats_return_completed_mrtg(char **reply, const char *reply_end) { 481static void stats_return_completed_mrtg(char **reply, const char *reply_end) {
482 ot_time t = time(NULL) - ot_start_time; 482 ot_time t = time_since_seconds(ot_start_time);
483 unsigned long long total[OT_COUNTER_COUNT]; 483 unsigned long long total[OT_COUNTER_COUNT];
484 484
485 counters_total(total); 485 counters_total(total);
@@ -510,7 +510,7 @@ static void stats_return_everything(char **reply, const char *reply_end) {
510 510
511 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 511 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
512 fmt_append(reply, reply_end, "<stats>\n"); 512 fmt_append(reply, reply_end, "<stats>\n");
513 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); 513 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)time_since_seconds(ot_start_time));
514 fmt_append(reply, reply_end, " <torrents>\n"); 514 fmt_append(reply, reply_end, " <torrents>\n");
515 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); 515 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count());
516 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); 516 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count);
@@ -785,7 +785,7 @@ void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FL
785 case EVENT_FULLSCRAPE_REQUEST: { 785 case EVENT_FULLSCRAPE_REQUEST: {
786 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ 786 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
787 char _debug[512]; 787 char _debug[512];
788 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); 788 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)time_since_seconds(ot_start_time) / 60);
789 off += ip_format(_debug + off, *ip); 789 off += ip_format(_debug + off, *ip);
790 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); 790 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
791 (void)write(2, _debug, off); 791 (void)write(2, _debug, off);
@@ -794,7 +794,7 @@ void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FL
794 case EVENT_FULLSCRAPE_REQUEST_GZIP: { 794 case EVENT_FULLSCRAPE_REQUEST_GZIP: {
795 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ 795 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
796 char _debug[512]; 796 char _debug[512];
797 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); 797 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)time_since_seconds(ot_start_time) / 60);
798 off += ip_format(_debug + off, *ip); 798 off += ip_format(_debug + off, *ip);
799 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); 799 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
800 (void)write(2, _debug, off); 800 (void)write(2, _debug, off);
@@ -804,7 +804,8 @@ void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FL
804 COUNT(OT_COUNTER_FAILED + event_data, 1); 804 COUNT(OT_COUNTER_FAILED + event_data, 1);
805 break; 805 break;
806 case EVENT_RENEW: 806 case EVENT_RENEW:
807 COUNT(OT_COUNTER_RENEWED + event_data, 1); 807 if (event_data < OT_PEER_TIMEOUT)
808 COUNT(OT_COUNTER_RENEWED + event_data, 1);
808 break; 809 break;
809 case EVENT_SYNC: 810 case EVENT_SYNC:
810 COUNT(OT_COUNTER_SYNCS, event_data); 811 COUNT(OT_COUNTER_SYNCS, event_data);
diff --git a/src/ot_udp.c b/src/ot_udp.c
index 644b71d..e67ca1f 100644
--- a/src/ot_udp.c
+++ b/src/ot_udp.c
@@ -39,12 +39,12 @@ static void udp_make_connectionid(uint32_t connid[2], const ot_ip6 remoteip, int
39 int i; 39 int i;
40 uint32_t current_key_of_the_hour; 40 uint32_t current_key_of_the_hour;
41 41
42 if (time_now_minutes() - g_hour_of_the_key >= 60) { 42 if (time_since_minutes(g_hour_of_the_key) >= 60) {
43 uint32_t old_key_of_the_hour = g_key_of_the_hour[0]; 43 uint32_t old_key_of_the_hour = g_key_of_the_hour[0];
44 uint32_t new_key_of_the_hour = random_uint32(); 44 uint32_t new_key_of_the_hour = random_uint32();
45 /* If in the meantime another thread has performed 45 /* If in the meantime another thread has performed
46 key rotation, do not overwrite their results */ 46 key rotation, do not overwrite their results */
47 if (time_now_minutes() - g_hour_of_the_key >= 60) { 47 if (time_since_minutes(g_hour_of_the_key) >= 60) {
48 /* Upgrade en bloc */ 48 /* Upgrade en bloc */
49 g_hour_of_the_key = time_now_minutes(); 49 g_hour_of_the_key = time_now_minutes();
50 g_key_of_the_hour[0] = new_key_of_the_hour; 50 g_key_of_the_hour[0] = new_key_of_the_hour;
diff --git a/src/trackerlogic.h b/src/trackerlogic.h
index 692b6af..c8d859c 100644
--- a/src/trackerlogic.h
+++ b/src/trackerlogic.h
@@ -61,10 +61,10 @@ typedef struct {
61#define OT_PEER_TIMEOUT 45 61#define OT_PEER_TIMEOUT 45
62 62
63/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs 63/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs
64 Sort key is, of course, its hash */ 64 Sort key is, of course, its hash.
65/* Set from the Makefile, 8 to 16. More buckets means shorter vectors and a 65 Can be tweked from the Makefile, sensible values are between 8 in 16. More
66 cheaper insert, which only pays for a tracker carrying millions of 66 buckets means shorter vectors and a cheaper insert, which only pays for a
67 torrents; see DESIGN. */ 67 tracker carrying millions of torrents. Consult the DESIGN document. */
68#ifndef OT_BUCKET_COUNT_BITS 68#ifndef OT_BUCKET_COUNT_BITS
69#define OT_BUCKET_COUNT_BITS 10 69#define OT_BUCKET_COUNT_BITS 10
70#endif 70#endif
@@ -95,6 +95,22 @@ static inline ot_time time_now_minutes(void) { return time_now_seconds() / 60; }
95/* For the deadlines the tracker sets on itself, which read better as the 95/* For the deadlines the tracker sets on itself, which read better as the
96 span they allow than as an addition to the current second */ 96 span they allow than as an addition to the current second */
97static inline ot_time time_future_seconds(int span) { return time_now_seconds() + span; } 97static inline ot_time time_future_seconds(int span) { return time_now_seconds() + span; }
98
99/* How long ago a stamp was taken. */
100_Static_assert((ot_time)-1 < 0, "ot_time has to be signed for time_since_* to clamp");
101
102static inline ot_time time_since_seconds(ot_time then) {
103 ot_time now = time_now_seconds();
104 return now > then ? now - then : 0;
105}
106
107static inline ot_time time_since_minutes(ot_time then) {
108 ot_time now = time_now_minutes();
109 return now > then ? now - then : 0;
110}
111
112/* Has a deadline the tracker set on itself gone by? */
113static inline int time_is_past(ot_time deadline) { return time_now_seconds() > deadline; }
98/* Cleared from the interrupt handler and read by every worker */ 114/* Cleared from the interrupt handler and read by every worker */
99extern _Atomic int g_opentracker_running; 115extern _Atomic int g_opentracker_running;
100 116
diff --git a/tests/runner.c b/tests/runner.c
index ae2565b..2398991 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,6 +27,7 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
27 {"ot_socket", test_socket}, 27 {"ot_socket", test_socket},
28 {"ot_mutex", test_mutex}, 28 {"ot_mutex", test_mutex},
29 {"trackerlogic", test_trackerlogic}, 29 {"trackerlogic", test_trackerlogic},
30 {"clean", test_clean},
30 {"ot_workqueue", test_workqueue}, 31 {"ot_workqueue", test_workqueue},
31 {"ot_batch", test_batch}, 32 {"ot_batch", test_batch},
32 {"ot_fdstate", test_fdstate}, 33 {"ot_fdstate", test_fdstate},
diff --git a/tests/stubs.c b/tests/stubs.c
index f7ac679..fd47a68 100644
--- a/tests/stubs.c
+++ b/tests/stubs.c
@@ -26,14 +26,11 @@ long io_trywrite(long d, const char *buf, long len) {
26/* trackerlogic.c is under test, and starting it up starts every module it 26/* trackerlogic.c is under test, and starting it up starts every module it
27 leans on. None of them is driven by these tests, so they are answered here 27 leans on. None of them is driven by these tests, so they are answered here
28 rather than linked in and left to run threads of their own. */ 28 rather than linked in and left to run threads of their own. */
29void clean_init(void) {} 29/* ot_clean.c is in the unit build for t_clean, so its thread control is the
30void clean_deinit(void) {} 30 real thing and only the stats hook it calls is stubbed here. */
31int clean_single_torrent(ot_torrent *torrent) {
32 (void)torrent;
33 return 0;
34}
35void fullscrape_init(void) {} 31void fullscrape_init(void) {}
36void fullscrape_deinit(void) {} 32void fullscrape_deinit(void) {}
33void stats_cleanup(void) {}
37void livesync_init(void) {} 34void livesync_init(void) {}
38void livesync_deinit(void) {} 35void livesync_deinit(void) {}
39void livesync_tell(struct ot_workstruct *ws) { (void)ws; } 36void livesync_tell(struct ot_workstruct *ws) { (void)ws; }
diff --git a/tests/t_clean.c b/tests/t_clean.c
new file mode 100644
index 0000000..7eca9aa
--- /dev/null
+++ b/tests/t_clean.c
@@ -0,0 +1,140 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#include <arpa/inet.h>
5#include <signal.h>
6#include <pthread.h>
7#include <string.h>
8
9#include "ot_clean.h"
10#include "ot_mutex.h"
11#include "trackerlogic.h"
12#include "test.h"
13
14/* A clock stepped backwards used to leave peer_list->base in the future, so
15 the span the cleaner works from came out negative. It was then added to a
16 peer's age and stored in the single byte that age lives in, where -1 reads
17 as 255 and the next pass threw the whole swarm away. */
18
19static void announce(struct ot_workstruct *ws, ot_hash *hash, uint8_t last_octet, int seeding) {
20 static const uint8_t v4mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
21
22 /* The peer list is written into the reply, which workstruct_init leaves
23 for its caller to point somewhere */
24 ws->reply = ws->outbuf;
25
26 memset(&ws->peer, 0, sizeof(ws->peer));
27 memcpy(&ws->peer, v4mapped, sizeof(v4mapped));
28 ((uint8_t *)&ws->peer)[15] = last_octet;
29 OT_SETPORT(&ws->peer, &(uint16_t){6881});
30 if (seeding)
31 OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING;
32 ws->hash = hash;
33 add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, 0);
34}
35
36static size_t peers_in(ot_hash *hash) {
37 char reply[1024];
38 size_t len = return_udp_scrape_for_torrent(*hash, reply);
39 uint32_t seeders, leechers;
40
41 if (len < 12)
42 return 0;
43 memcpy(&seeders, reply, 4);
44 memcpy(&leechers, reply + 8, 4);
45 return (size_t)ntohl(seeders) + (size_t)ntohl(leechers);
46}
47
48static ot_torrent *torrent_of(ot_hash *hash) {
49 ot_vector *bucket = mutex_bucket_lock_by_hash(*hash);
50 ot_torrent *t = (ot_torrent *)bucket->data;
51 ot_torrent *found = NULL;
52 size_t i;
53
54 for (i = 0; i < bucket->size; ++i)
55 if (!memcmp(t[i].hash, hash, sizeof(ot_hash)))
56 found = t + i;
57 mutex_bucket_unlock_by_hash(*hash, 0);
58 return found;
59}
60
61/* What a clock stepped back by `minutes` leaves behind */
62static void step_clock_back(ot_torrent *torrent, int minutes) {
63 torrent->peer_list6->base += minutes;
64 torrent->peer_list4->base += minutes;
65}
66
67/* What the next minute of a well behaved clock looks like */
68static void age_by_a_minute(ot_torrent *torrent) {
69 torrent->peer_list6->base -= 1;
70 torrent->peer_list4->base -= 1;
71}
72
73static void case_a_clock_that_went_back_keeps_the_swarm(void) {
74 struct ot_workstruct ws;
75 ot_hash hash;
76 ot_torrent *torrent;
77
78 test_case("a swarm survives the clock being stepped backwards");
79 memset(hash, 0x31, sizeof(hash));
80 CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
81
82 announce(&ws, &hash, 1, 1);
83 announce(&ws, &hash, 2, 0);
84 CHECK_INT(peers_in(&hash), 2);
85
86 torrent = torrent_of(&hash);
87 CHECK(torrent != NULL);
88 step_clock_back(torrent, 5);
89
90 /* The pass that sees the step keeps everybody either way: unclamped it
91 writes a negative span into each peer's age byte, where it reads as 251
92 rather than as a removal. */
93 CHECK_INT(clean_single_torrent(torrent), 0);
94 CHECK_INT(peers_in(&hash), 2);
95
96 /* A minute later, and this is where a poisoned age byte took the swarm:
97 251 plus one minute is past any timeout. */
98 age_by_a_minute(torrent);
99 CHECK_INT(clean_single_torrent(torrent), 0);
100 CHECK_INT(peers_in(&hash), 2);
101
102 workstruct_deinit(&ws);
103}
104
105static void case_an_idle_swarm_still_ages_out(void) {
106 struct ot_workstruct ws;
107 ot_hash hash;
108 ot_torrent *torrent;
109
110 test_case("a swarm nobody touched is still cleaned away");
111 memset(hash, 0x32, sizeof(hash));
112 CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
113
114 announce(&ws, &hash, 3, 1);
115 CHECK_INT(peers_in(&hash), 1);
116
117 torrent = torrent_of(&hash);
118 CHECK(torrent != NULL);
119 step_clock_back(torrent, -OT_PEER_TIMEOUT);
120
121 CHECK_INT(clean_single_torrent(torrent), 0);
122 CHECK_INT(peers_in(&hash), 0);
123
124 workstruct_deinit(&ws);
125}
126
127void test_clean(void) {
128 sigset_t hup;
129
130 /* accesslist_deinit sends SIGHUP to its reader, trackerlogic_init starts
131 the accesslist, and the test process must not take it */
132 sigemptyset(&hup);
133 sigaddset(&hup, SIGHUP);
134 pthread_sigmask(SIG_BLOCK, &hup, NULL);
135
136 trackerlogic_init();
137 case_a_clock_that_went_back_keeps_the_swarm();
138 case_an_idle_swarm_still_ages_out();
139 trackerlogic_deinit();
140}
diff --git a/tests/test.h b/tests/test.h
index 82ec178..9b569c7 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,6 +86,7 @@ void test_ip(void);
86void test_socket(void); 86void test_socket(void);
87void test_mutex(void); 87void test_mutex(void);
88void test_trackerlogic(void); 88void test_trackerlogic(void);
89void test_clean(void);
89void test_workqueue(void); 90void test_workqueue(void);
90void test_batch(void); 91void test_batch(void);
91void test_fdstate(void); 92void test_fdstate(void);