From 47293a4f116e04a88b0797bafad0c98b4393b744 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Thu, 5 Nov 2026 13:52:17 +0100 Subject: Clamp every elapsed time span at zero, using a set of helper functions --- tests/runner.c | 1 + tests/stubs.c | 9 ++-- tests/t_clean.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.h | 1 + 4 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 tests/t_clean.c (limited to 'tests') 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}, {"ot_socket", test_socket}, {"ot_mutex", test_mutex}, {"trackerlogic", test_trackerlogic}, + {"clean", test_clean}, {"ot_workqueue", test_workqueue}, {"ot_batch", test_batch}, {"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) { /* trackerlogic.c is under test, and starting it up starts every module it leans on. None of them is driven by these tests, so they are answered here rather than linked in and left to run threads of their own. */ -void clean_init(void) {} -void clean_deinit(void) {} -int clean_single_torrent(ot_torrent *torrent) { - (void)torrent; - return 0; -} +/* ot_clean.c is in the unit build for t_clean, so its thread control is the + real thing and only the stats hook it calls is stubbed here. */ void fullscrape_init(void) {} void fullscrape_deinit(void) {} +void stats_cleanup(void) {} void livesync_init(void) {} void livesync_deinit(void) {} void 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 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. */ + +#include +#include +#include +#include + +#include "ot_clean.h" +#include "ot_mutex.h" +#include "trackerlogic.h" +#include "test.h" + +/* A clock stepped backwards used to leave peer_list->base in the future, so + the span the cleaner works from came out negative. It was then added to a + peer's age and stored in the single byte that age lives in, where -1 reads + as 255 and the next pass threw the whole swarm away. */ + +static void announce(struct ot_workstruct *ws, ot_hash *hash, uint8_t last_octet, int seeding) { + static const uint8_t v4mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; + + /* The peer list is written into the reply, which workstruct_init leaves + for its caller to point somewhere */ + ws->reply = ws->outbuf; + + memset(&ws->peer, 0, sizeof(ws->peer)); + memcpy(&ws->peer, v4mapped, sizeof(v4mapped)); + ((uint8_t *)&ws->peer)[15] = last_octet; + OT_SETPORT(&ws->peer, &(uint16_t){6881}); + if (seeding) + OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING; + ws->hash = hash; + add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, 0); +} + +static size_t peers_in(ot_hash *hash) { + char reply[1024]; + size_t len = return_udp_scrape_for_torrent(*hash, reply); + uint32_t seeders, leechers; + + if (len < 12) + return 0; + memcpy(&seeders, reply, 4); + memcpy(&leechers, reply + 8, 4); + return (size_t)ntohl(seeders) + (size_t)ntohl(leechers); +} + +static ot_torrent *torrent_of(ot_hash *hash) { + ot_vector *bucket = mutex_bucket_lock_by_hash(*hash); + ot_torrent *t = (ot_torrent *)bucket->data; + ot_torrent *found = NULL; + size_t i; + + for (i = 0; i < bucket->size; ++i) + if (!memcmp(t[i].hash, hash, sizeof(ot_hash))) + found = t + i; + mutex_bucket_unlock_by_hash(*hash, 0); + return found; +} + +/* What a clock stepped back by `minutes` leaves behind */ +static void step_clock_back(ot_torrent *torrent, int minutes) { + torrent->peer_list6->base += minutes; + torrent->peer_list4->base += minutes; +} + +/* What the next minute of a well behaved clock looks like */ +static void age_by_a_minute(ot_torrent *torrent) { + torrent->peer_list6->base -= 1; + torrent->peer_list4->base -= 1; +} + +static void case_a_clock_that_went_back_keeps_the_swarm(void) { + struct ot_workstruct ws; + ot_hash hash; + ot_torrent *torrent; + + test_case("a swarm survives the clock being stepped backwards"); + memset(hash, 0x31, sizeof(hash)); + CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0); + + announce(&ws, &hash, 1, 1); + announce(&ws, &hash, 2, 0); + CHECK_INT(peers_in(&hash), 2); + + torrent = torrent_of(&hash); + CHECK(torrent != NULL); + step_clock_back(torrent, 5); + + /* The pass that sees the step keeps everybody either way: unclamped it + writes a negative span into each peer's age byte, where it reads as 251 + rather than as a removal. */ + CHECK_INT(clean_single_torrent(torrent), 0); + CHECK_INT(peers_in(&hash), 2); + + /* A minute later, and this is where a poisoned age byte took the swarm: + 251 plus one minute is past any timeout. */ + age_by_a_minute(torrent); + CHECK_INT(clean_single_torrent(torrent), 0); + CHECK_INT(peers_in(&hash), 2); + + workstruct_deinit(&ws); +} + +static void case_an_idle_swarm_still_ages_out(void) { + struct ot_workstruct ws; + ot_hash hash; + ot_torrent *torrent; + + test_case("a swarm nobody touched is still cleaned away"); + memset(hash, 0x32, sizeof(hash)); + CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0); + + announce(&ws, &hash, 3, 1); + CHECK_INT(peers_in(&hash), 1); + + torrent = torrent_of(&hash); + CHECK(torrent != NULL); + step_clock_back(torrent, -OT_PEER_TIMEOUT); + + CHECK_INT(clean_single_torrent(torrent), 0); + CHECK_INT(peers_in(&hash), 0); + + workstruct_deinit(&ws); +} + +void test_clean(void) { + sigset_t hup; + + /* accesslist_deinit sends SIGHUP to its reader, trackerlogic_init starts + the accesslist, and the test process must not take it */ + sigemptyset(&hup); + sigaddset(&hup, SIGHUP); + pthread_sigmask(SIG_BLOCK, &hup, NULL); + + trackerlogic_init(); + case_a_clock_that_went_back_keeps_the_swarm(); + case_an_idle_swarm_still_ages_out(); + trackerlogic_deinit(); +} 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); void test_socket(void); void test_mutex(void); void test_trackerlogic(void); +void test_clean(void); void test_workqueue(void); void test_batch(void); void test_fdstate(void); -- cgit v1.3