summaryrefslogtreecommitdiff
path: root/tests/t_clean.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/t_clean.c')
-rw-r--r--tests/t_clean.c140
1 files changed, 140 insertions, 0 deletions
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}